<?php
/**
 * Date: 23.11.15
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Parser\Ast\ArgumentValue;

use Youshido\GraphQL\Parser\Ast\AbstractAst;
use Youshido\GraphQL\Parser\Ast\Interfaces\ValueInterface;
use Youshido\GraphQL\Parser\Location;

class Variable extends AbstractAst implements ValueInterface
{

    /** @var  string */
    private $name;

    /** @var  mixed */
    private $value;

    /** @var string */
    private $type;

    /** @var bool */
    private $nullable = false;

    /** @var bool */
    private $isArray = false;

    /** @var bool */
    private $used = false;

    /** @var bool */
    private $arrayElementNullable = true;

    /** @var bool */
    private $hasDefaultValue = false;

    /** @var mixed */
    private $defaultValue = null;

    /**
     * @param string   $name
     * @param string   $type
     * @param bool     $nullable
     * @param bool     $isArray
     * @param bool     $arrayElementNullable
     * @param Location $location
     */
    public function __construct($name, $type, $nullable, $isArray, $arrayElementNullable = true, Location $location)
    {
        parent::__construct($location);

        $this->name                 = $name;
        $this->type                 = $type;
        $this->isArray              = $isArray;
        $this->nullable             = $nullable;
        $this->arrayElementNullable = $arrayElementNullable;
    }

    /**
     * @return mixed
     *
     * @throws \LogicException
     */
    public function getValue()
    {
        if (null === $this->value) {
            if ($this->hasDefaultValue()) {
                return $this->defaultValue;
            }
            throw new \LogicException('Value is not set for variable "' . $this->name . '"');
        }

        return $this->value;
    }

    /**
     * @param mixed $value
     */
    public function setValue($value)
    {
        $this->value = $value;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @param string $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * @return string
     */
    public function getTypeName()
    {
        return $this->type;
    }

    /**
     * @param string $type
     */
    public function setTypeName($type)
    {
        $this->type = $type;
    }

    /**
     * @return boolean
     */
    public function isArray()
    {
        return $this->isArray;
    }

    /**
     * @param boolean $isArray
     */
    public function setIsArray($isArray)
    {
        $this->isArray = $isArray;
    }

    /**
     * @return boolean
     */
    public function isNullable()
    {
        return $this->nullable;
    }

    /**
     * @param boolean $nullable
     */
    public function setNullable($nullable)
    {
        $this->nullable = $nullable;
    }

    /**
     * @return bool
     */
    public function hasDefaultValue()
    {
        return $this->hasDefaultValue;
    }

    /**
     * @return mixed
     */
    public function getDefaultValue()
    {
        return $this->defaultValue;
    }

    /**
     * @param mixed $defaultValue
     */
    public function setDefaultValue($defaultValue)
    {
        $this->hasDefaultValue = true;

        $this->defaultValue = $defaultValue;
    }

    /**
     * @return boolean
     */
    public function isUsed()
    {
        return $this->used;
    }

    /**
     * @param boolean $used
     *
     * @return $this
     */
    public function setUsed($used)
    {
        $this->used = $used;

        return $this;
    }

    /**
     * @return bool
     */
    public function isArrayElementNullable()
    {
        return $this->arrayElementNullable;
    }

    /**
     * @param bool $arrayElementNullable
     */
    public function setArrayElementNullable($arrayElementNullable)
    {
        $this->arrayElementNullable = $arrayElementNullable;
    }
}


<?php
/**
 * Date: 3/17/17
 *
 * @author Volodymyr Rashchepkin <rashepkin@gmail.com>
 */

namespace Youshido\GraphQL\Parser\Ast;

trait AstDirectivesTrait
{

    /** @var Directive[] */
    protected $directives;

    private $directivesCache = null;

    public function hasDirectives()
    {
        return (bool)count($this->directives);
    }

    public function hasDirective($name)
    {
        return array_key_exists($name, $this->directives);
    }

    /**
     * @param $name
     *
     * @return null|Directive
     */
    public function getDirective($name)
    {
        $directive = null;
        if (isset($this->directives[$name])) {
            $directive = $this->directives[$name];
        }

        return $directive;
    }

    /**
     * @return Directive[]
     */
    public function getDirectives()
    {
        return $this->directives;
    }

    /**
     * @param $directives Directive[]
     */
    public function setDirectives(array $directives)
    {
        $this->directives      = [];
        $this->directivesCache = null;

        foreach ($directives as $directive) {
            $this->addDirective($directive);
        }
    }

    public function addDirective(Directive $directive)
    {
        $this->directives[$directive->getName()] = $directive;
    }
}


<?php
/*
* This file is a part of graphql-youshido project.
*
* @author Portey Vasil <portey@gmail.com>
* created: 11/23/15 1:22 AM
*/

namespace Youshido\GraphQL\Parser;

use Youshido\GraphQL\Exception\Parser\SyntaxErrorException;
use Youshido\GraphQL\Parser\Ast\Argument;
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputList;
use Youshido\GraphQL\Parser\Ast\ArgumentValue\InputObject;
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Literal;
use Youshido\GraphQL\Parser\Ast\ArgumentValue\Variable;
use Youshido\GraphQL\Parser\Ast\ArgumentValue\VariableReference;
use Youshido\GraphQL\Parser\Ast\Directive;
use Youshido\GraphQL\Parser\Ast\Field;
use Youshido\GraphQL\Parser\Ast\Fragment;
use Youshido\GraphQL\Parser\Ast\FragmentReference;
use Youshido\GraphQL\Parser\Ast\Mutation;
use Youshido\GraphQL\Parser\Ast\Query;
use Youshido\GraphQL\Parser\Ast\TypedFragmentReference;

class Parser extends Tokenizer
{

    /** @var array */
    private $data = [];

    public function parse($source = null)
    {
        $this->init($source);

        while (!$this->end()) {
            $tokenType = $this->peek()->getType();

            switch ($tokenType) {
                case Token::TYPE_LBRACE:
                    foreach ($this->parseBody() as $query) {
                        $this->data['queries'][] = $query;
                    }

                    break;
                case Token::TYPE_QUERY:
                    $queries = $this->parseOperation(Token::TYPE_QUERY);
                    foreach ($queries as $query) {
                        $this->data['queries'][] = $query;
                    }

                    break;
                case Token::TYPE_MUTATION:
                    $mutations = $this->parseOperation(Token::TYPE_MUTATION);
                    foreach ($mutations as $query) {
                        $this->data['mutations'][] = $query;
                    }

                    break;

                case Token::TYPE_FRAGMENT:
                    $this->data['fragments'][] = $this->parseFragment();

                    break;

                default:
                    throw new SyntaxErrorException('Incorrect request syntax', $this->getLocation());
            }
        }

        return $this->data;
    }

    private function init($source = null)
    {
        $this->initTokenizer($source);

        $this->data = [
            'queries'            => [],
            'mutations'          => [],
            'fragments'          => [],
            'fragmentReferences' => [],
            'variables'          => [],
            'variableReferences' => [],
        ];
    }

    protected function parseOperation($type = Token::TYPE_QUERY)
    {
        $operation  = null;
        $directives = [];

        if ($this->matchMulti([Token::TYPE_QUERY, Token::TYPE_MUTATION])) {
            $this->lex();

            $this->eat(Token::TYPE_IDENTIFIER);

            if ($this->match(Token::TYPE_LPAREN)) {
                $this->parseVariables();
            }

            if ($this->match(Token::TYPE_AT)) {
                $directives = $this->parseDirectiveList();
            }

        }

        $this->lex();

        $fields = [];

        while (!$this->match(Token::TYPE_RBRACE) && !$this->end()) {
            $this->eatMulti([Token::TYPE_COMMA]);

            $operation = $this->parseBodyItem($type, true);
            $operation->setDirectives($directives);

            $fields[] = $operation;
        }

        $this->expect(Token::TYPE_RBRACE);

        return $fields;
    }

    protected function parseBody($token = Token::TYPE_QUERY, $highLevel = true)
    {
        $fields = [];

        $this->lex();

        while (!$this->match(Token::TYPE_RBRACE) && !$this->end()) {
            $this->eatMulti([Token::TYPE_COMMA]);

            if ($this->match(Token::TYPE_FRAGMENT_REFERENCE)) {
                $this->lex();

                if ($this->eat(Token::TYPE_ON)) {
                    $fields[] = $this->parseBodyItem(Token::TYPE_TYPED_FRAGMENT, $highLevel);
                } else {
                    $fields[] = $this->parseFragmentReference();
                }
            } else {
                $fields[] = $this->parseBodyItem($token, $highLevel);
            }
        }

        $this->expect(Token::TYPE_RBRACE);

        return $fields;
    }

    protected function parseVariables()
    {
        $this->eat(Token::TYPE_LPAREN);

        while (!$this->match(Token::TYPE_RPAREN) && !$this->end()) {
            $this->eat(Token::TYPE_COMMA);

            $variableToken = $this->eat(Token::TYPE_VARIABLE);
            $nameToken     = $this->eatIdentifierToken();
            $this->eat(Token::TYPE_COLON);

            $isArray              = false;
            $arrayElementNullable = true;

            if ($this->match(Token::TYPE_LSQUARE_BRACE)) {
                $isArray = true;

                $this->eat(Token::TYPE_LSQUARE_BRACE);
                $type = $this->eatIdentifierToken()->getData();

                if ($this->match(Token::TYPE_REQUIRED)) {
                    $arrayElementNullable = false;
                    $this->eat(Token::TYPE_REQUIRED);
                }

                $this->eat(Token::TYPE_RSQUARE_BRACE);
            } else {
                $type = $this->eatIdentifierToken()->getData();
            }

            $required = false;
            if ($this->match(Token::TYPE_REQUIRED)) {
                $required = true;
                $this->eat(Token::TYPE_REQUIRED);
            }

            $variable = new Variable(
                $nameToken->getData(),
                $type,
                $required,
                $isArray,
                $arrayElementNullable,
                new Location($variableToken->getLine(), $variableToken->getColumn())
            );

            if ($this->match(Token::TYPE_EQUAL)) {
                $this->eat(Token::TYPE_EQUAL);
                $variable->setDefaultValue($this->parseValue());
            }

            $this->data['variables'][] = $variable;
        }

        $this->expect(Token::TYPE_RPAREN);
    }

    protected function expectMulti($types)
    {
        if ($this->matchMulti($types)) {
            return $this->lex();
        }

        throw $this->createUnexpectedException($this->peek());
    }

    protected function parseVariableReference()
    {
        $startToken = $this->expectMulti([Token::TYPE_VARIABLE]);

        if ($this->match(Token::TYPE_NUMBER) || $this->match(Token::TYPE_IDENTIFIER) || $this->match(Token::TYPE_QUERY)) {
            $name = $this->lex()->getData();

            $variable = $this->findVariable($name);
            if ($variable) {
                $variable->setUsed(true);
            }

            $variableReference = new VariableReference($name, $variable, new Location($startToken->getLine(), $startToken->getColumn()));

            $this->data['variableReferences'][] = $variableReference;

            return $variableReference;
        }

        throw $this->createUnexpectedException($this->peek());
    }

    protected function findVariable($name)
    {
        foreach ((array) $this->data['variables'] as $variable) {
            /** @var $variable Variable */
            if ($variable->getName() === $name) {
                return $variable;
            }
        }

        return null;
    }

    protected function parseFragmentReference()
    {
        $nameToken         = $this->eatIdentifierToken();
        $fragmentReference = new FragmentReference($nameToken->getData(), new Location($nameToken->getLine(), $nameToken->getColumn()));

        $this->data['fragmentReferences'][] = $fragmentReference;

        return $fragmentReference;
    }

    protected function eatIdentifierToken()
    {
        return $this->expectMulti([
            Token::TYPE_IDENTIFIER,
            Token::TYPE_MUTATION,
            Token::TYPE_QUERY,
            Token::TYPE_FRAGMENT,
        ]);
    }

    protected function parseBodyItem($type = Token::TYPE_QUERY, $highLevel = true)
    {
        $nameToken = $this->eatIdentifierToken();
        $alias     = null;

        if ($this->eat(Token::TYPE_COLON)) {
            $alias     = $nameToken->getData();
            $nameToken = $this->eatIdentifierToken();
        }

        $bodyLocation = new Location($nameToken->getLine(), $nameToken->getColumn());
        $arguments    = $this->match(Token::TYPE_LPAREN) ? $this->parseArgumentList() : [];
        $directives   = $this->match(Token::TYPE_AT) ? $this->parseDirectiveList() : [];

        if ($this->match(Token::TYPE_LBRACE)) {
            $fields = $this->parseBody($type === Token::TYPE_TYPED_FRAGMENT ? Token::TYPE_QUERY : $type, false);

            if (!$fields) {
                throw $this->createUnexpectedTokenTypeException($this->lookAhead->getType());
            }

            if ($type === Token::TYPE_QUERY) {
                return new Query($nameToken->getData(), $alias, $arguments, $fields, $directives, $bodyLocation);
            } elseif ($type === Token::TYPE_TYPED_FRAGMENT) {
                return new TypedFragmentReference($nameToken->getData(), $fields, $directives, $bodyLocation);
            } else {
                return new Mutation($nameToken->getData(), $alias, $arguments, $fields, $directives, $bodyLocation);
            }
        } else {
            if ($highLevel && $type === Token::TYPE_MUTATION) {
                return new Mutation($nameToken->getData(), $alias, $arguments, [], $directives, $bodyLocation);
            } elseif ($highLevel && $type === Token::TYPE_QUERY) {
                return new Query($nameToken->getData(), $alias, $arguments, [], $directives, $bodyLocation);
            }

            return new Field($nameToken->getData(), $alias, $arguments, $directives, $bodyLocation);
        }
    }

    protected function parseArgumentList()
    {
        $args = [];

        $this->expect(Token::TYPE_LPAREN);

        while (!$this->match(Token::TYPE_RPAREN) && !$this->end()) {
            $this->eat(Token::TYPE_COMMA);
            $args[] = $this->parseArgument();
        }

        $this->expect(Token::TYPE_RPAREN);

        return $args;
    }

    protected function parseArgument()
    {
        $nameToken = $this->eatIdentifierToken();
        $this->expect(Token::TYPE_COLON);
        $value = $this->parseValue();

        return new Argument($nameToken->getData(), $value, new Location($nameToken->getLine(), $nameToken->getColumn()));
    }

    protected function parseDirectiveList()
    {
        $directives = [];

        while ($this->match(Token::TYPE_AT)) {
            $directives[] = $this->parseDirective();
            $this->eat(Token::TYPE_COMMA);
        }

        return $directives;
    }

    protected function parseDirective()
    {
        $this->expect(Token::TYPE_AT);

        $nameToken = $this->eatIdentifierToken();
        $args      = $this->match(Token::TYPE_LPAREN) ? $this->parseArgumentList() : [];

        return new Directive($nameToken->getData(), $args, new Location($nameToken->getLine(), $nameToken->getColumn()));
    }

    /**
     * @return array|InputList|InputObject|Literal|VariableReference
     *
     * @throws SyntaxErrorException
     */
    protected function parseValue()
    {
        switch ($this->lookAhead->getType()) {
            case Token::TYPE_LSQUARE_BRACE:
                return $this->parseList();

            case Token::TYPE_LBRACE:
                return $this->parseObject();

            case Token::TYPE_VARIABLE:
                return $this->parseVariableReference();

            case Token::TYPE_NUMBER:
            case Token::TYPE_STRING:
            case Token::TYPE_IDENTIFIER:
            case Token::TYPE_NULL:
            case Token::TYPE_TRUE:
            case Token::TYPE_FALSE:
                $token = $this->lex();

                return new Literal($token->getData(), new Location($token->getLine(), $token->getColumn()));
        }

        throw $this->createUnexpectedException($this->lookAhead);
    }

    protected function parseList($createType = true)
    {
        $startToken = $this->eat(Token::TYPE_LSQUARE_BRACE);

        $list = [];
        while (!$this->match(Token::TYPE_RSQUARE_BRACE) && !$this->end()) {
            $list[] = $this->parseListValue();

            $this->eat(Token::TYPE_COMMA);
        }

        $this->expect(Token::TYPE_RSQUARE_BRACE);

        return $createType ? new InputList($list, new Location($startToken->getLine(), $startToken->getColumn())) : $list;
    }

    protected function parseListValue()
    {
        switch ($this->lookAhead->getType()) {
            case Token::TYPE_NUMBER:
            case Token::TYPE_STRING:
            case Token::TYPE_TRUE:
            case Token::TYPE_FALSE:
            case Token::TYPE_NULL:
            case Token::TYPE_IDENTIFIER:
                return $this->expect($this->lookAhead->getType())->getData();

            case Token::TYPE_VARIABLE:
                return $this->parseVariableReference();

            case Token::TYPE_LBRACE:
                return $this->parseObject(true);

            case Token::TYPE_LSQUARE_BRACE:
                return $this->parseList(false);
        }

        throw new SyntaxErrorException('Can\'t parse argument', $this->getLocation());
    }

    protected function parseObject($createType = true)
    {
        $startToken = $this->eat(Token::TYPE_LBRACE);

        $object = [];
        while (!$this->match(Token::TYPE_RBRACE) && !$this->end()) {
            $key = $this->expectMulti([Token::TYPE_STRING, Token::TYPE_IDENTIFIER])->getData();
            $this->expect(Token::TYPE_COLON);
            $value = $this->parseListValue();

            $this->eat(Token::TYPE_COMMA);

            $object[$key] = $value;
        }

        $this->eat(Token::TYPE_RBRACE);

        return $createType ? new InputObject($object, new Location($startToken->getLine(), $startToken->getColumn())) : $object;
    }

    protected function parseFragment()
    {
        $this->lex();
        $nameToken = $this->eatIdentifierToken();

        $this->eat(Token::TYPE_ON);

        $model = $this->eatIdentifierToken();

        $directives = $this->match(Token::TYPE_AT) ? $this->parseDirectiveList() : [];

        $fields = $this->parseBody(Token::TYPE_QUERY, false);

        return new Fragment($nameToken->getData(), $model->getData(), $directives, $fields, new Location($nameToken->getLine(), $nameToken->getColumn()));
    }

    protected function eat($type)
    {
        if ($this->match($type)) {
            return $this->lex();
        }

        return null;
    }

    protected function eatMulti($types)
    {
        if ($this->matchMulti($types)) {
            return $this->lex();
        }

        return null;
    }

    protected function matchMulti($types)
    {
        foreach ((array) $types as $type) {
            if ($this->peek()->getType() === $type) {
                return true;
            }
        }

        return false;
    }
}


<?php
/**
 * Date: 17.05.16
 *
 * @author Portey Vasil <portey@gmail.com>
 */

namespace Youshido\GraphQL\Relay;

use Youshido\GraphQL\Execution\ResolveInfo;
use Youshido\GraphQL\Field\Field;
use Youshido\GraphQL\Field\InputField;
use Youshido\GraphQL\Type\InputObject\InputObjectType;
use Youshido\GraphQL\Type\NonNullType;
use Youshido\GraphQL\Type\Object\AbstractObjectType;
use Youshido\GraphQL\Type\Object\ObjectType;
use Youshido\GraphQL\Type\Scalar\StringType;

class RelayMutation
{

    /**
     * @param string                   $name
     * @param array                    $args
     * @param AbstractObjectType|array $output
     * @param callable                 $resolveFunction
     *
     * @return Field
     *
     * @throws \Exception
     */
    public static function buildMutation($name, array $args, $output, callable $resolveFunction)
    {
        if (!is_array($output) || (is_object($output) && !($output instanceof AbstractObjectType))) {
            throw new \Exception('Output can be instance of AbstractObjectType or array of fields');
        }

        return new Field([
            'name'    => $name,
            'args'    => [
                new InputField([
                    'name' => 'input',
                    'type' => new NonNullType(new InputObjectType([
                        'name'   => ucfirst($name) . 'Input',
                        'fields' => array_merge(
                            $args,
                            [new InputField(['name' => 'clientMutationId', 'type' => new NonNullType(new StringType())])]
                        )
                    ]))
                ])
            ],
            'type'    => new ObjectType([
                'fields' => is_object($output) ? $output : array_merge(
                    $output,
                    [new Field(['name' => 'clientMutationId', 'type' => new NonNullType(new StringType())])]
                ),
                'name'   => ucfirst($name) . 'Payload'
            ]),
            'resolve' => function ($value, $args, ResolveInfo $info) use ($resolveFunction) {
                $resolveValue = $resolveFunction($value, $args['input'], $args, $info);

                if (is_object($resolveValue)) {
                    $resolveValue->clientMutationId = $args['input']['clientMutationId'];
                } elseif (is_array($resolveValue)) {
                    $resolveValue['clientMutationId'] = $args['input']['clientMutationId'];
                }

                return $resolveValue;
            }
        ]);
    }

}


<?php
/*
* Concrete implementation of query visitor.
*
* Enforces maximum complexity on a query, computed from "cost" functions on
* the fields touched by that query.
*
* @author Ben Roberts <bjr.roberts@gmail.com>
* created: 7/11/16 11:05 AM
*/

namespace Youshido\GraphQL\Execution\Visitor;

use Youshido\GraphQL\Config\Field\FieldConfig;

class MaxComplexityQueryVisitor extends AbstractQueryVisitor
{

    /**
     * @var int max score allowed before throwing an exception (causing processing to stop)
     */
    public $maxScore;

    /**
     * @var int default score for nodes without explicit cost functions
     */
    protected $defaultScore = 1;

    /**
     * MaxComplexityQueryVisitor constructor.
     *
     * @param int $max max allowed complexity score
     */
    public function __construct($max)
    {
        parent::__construct();

        $this->maxScore = $max;
    }

    /**
     * {@inheritdoc}
     */
    public function visit(array $args, FieldConfig $fieldConfig, $childScore = 0)
    {
        $cost = $fieldConfig->get('cost', null);
        if (is_callable($cost)) {
            $cost = $cost($args, $fieldConfig, $childScore);
        }

        $cost = is_null($cost) ? $this->defaultScore : $cost;
        $this->memo += $cost;

        if ($this->memo > $this->maxScore) {
            throw new \Exception('query exceeded max allowed complexity of ' . $this->maxScore);
        }

        return $cost;
    }
}


<?php
/*
* This file is a part of graphql-youshido project.
*
* @author Alexandr Viniychuk <a@viniychuk.com>
* created: 11/27/15 2:31 AM
*/

namespace Youshido\GraphQL\Config;

use Youshido\GraphQL\Exception\ConfigurationException;
use Youshido\GraphQL\Exception\ValidationException;
use Youshido\GraphQL\Validator\ConfigValidator\ConfigValidator;

/**
 * Class Config
 *
 * @package Youshido\GraphQL\Config
 */
abstract class AbstractConfig
{

    /**
     * @var array
     */
    protected $data = [];

    protected $contextObject;

    protected $finalClass = false;

    protected $extraFieldsAllowed = null;

    /**
     * TypeConfig constructor.
     *
     * @param array $configData
     * @param mixed $contextObject
     * @param bool  $finalClass
     *
     * @throws ConfigurationException
     * @throws ValidationException
     */
    public function __construct(array $configData, $contextObject = null, $finalClass = false)
    {
        if (empty($configData)) {
            throw new ConfigurationException('Config for Type should be an array');
        }

        $this->contextObject = $contextObject;
        $this->data          = $configData;
        $this->finalClass    = $finalClass;

        $this->build();
    }

    public function validate()
    {
        $validator = ConfigValidator::getInstance();

        if (!$validator->validate($this->data, $this->getContextRules(), $this->extraFieldsAllowed)) {
            throw new ConfigurationException('Config is not valid for ' . ($this->contextObject ? get_class($this->contextObject) : null) . "\n" . implode("\n", $validator->getErrorsArray(false)));
        }
    }

    public function getContextRules()
    {
        $rules = $this->getRules();
        if ($this->finalClass) {
            foreach ($rules as $name => $info) {
                if (!empty($info['final'])) {
                    $rules[$name]['required'] = true;
                }
            }
        }

        return $rules;
    }

    abstract public function getRules();

    public function getName()
    {
        return $this->get('name');
    }

    public function getType()
    {
        return $this->get('type');
    }

    public function getData()
    {
        return $this->data;
    }

    public function getContextObject()
    {
        return $this->contextObject;
    }

    public function isFinalClass()
    {
        return $this->finalClass;
    }

    public function isExtraFieldsAllowed()
    {
        return $this->extraFieldsAllowed;
    }

    /**
     * @return null|callable
     */
    public function getResolveFunction()
    {
        return $this->get('resolve', null);
    }

    protected function build()
    {
    }

    /**
     * @param      $key
     * @param null $defaultValue
     *
     * @return mixed|null|callable
     */
    public function get($key, $defaultValue = null)
    {
        return $this->has($key) ? $this->data[$key] : $defaultValue;
    }

    public function set($key, $value)
    {
        $this->data[$key] = $value;

        return $this;
    }

    public function has($key)
    {
        return array_key_exists($key, $this->data);
    }

    public function __call($method, $arguments)
    {
        if (substr($method, 0, 3) == 'get') {
            $propertyName = lcfirst(substr($method, 3));
        } elseif (substr($method, 0, 3) == 'set') {
            $propertyName = lcfirst(substr($method, 3));
            $this->set($propertyName, $arguments[0]);

            return $this;
        } elseif (substr($method, 0, 2) == 'is') {
            $propertyName = lcfirst(substr($method, 2));
        } else {
            throw new \Exception('Call to undefined method ' . $method);
        }

        return $this->get($propertyName);
    }

}


<?php
/*
* This file is a part of GraphQL project.
*
* @author Alexandr Viniychuk <a@viniychuk.com>
* created: 4/30/16 9:11 PM
*/

namespace Youshido\GraphQL\Validator\SchemaValidator;

use Youshido\GraphQL\Exception\ConfigurationException;
use Youshido\GraphQL\Field\Field;
use Youshido\GraphQL\Schema\AbstractSchema;
use Youshido\GraphQL\Type\InterfaceType\AbstractInterfaceType;
use Youshido\GraphQL\Type\Object\AbstractObjectType;
use Youshido\GraphQL\Validator\ConfigValidator\ConfigValidator;

class SchemaValidator
{

    /** @var ConfigValidator */
    private $configValidator = null;
    /**
     * @param AbstractSchema $schema
     *
     * @throws ConfigurationException
     */
    public function validate(AbstractSchema $schema)
    {
        if (!$schema->getQueryType()->hasFields()) {
            throw new ConfigurationException('Schema has to have fields');
        }

        $this->configValidator = ConfigValidator::getInstance();

        foreach ($schema->getQueryType()->getConfig()->getFields() as $field) {
            $this->configValidator->assertValidConfig($field->getConfig());

            if ($field->getType() instanceof AbstractObjectType) {
                $this->assertInterfaceImplementationCorrect($field->getType());
            }
        }
    }

    /**
     * @param AbstractObjectType $type
     *
     * @throws ConfigurationException
     */
    protected function assertInterfaceImplementationCorrect(AbstractObjectType $type)
    {
        if (!$type->getInterfaces()) {
            return;
        }

        foreach ($type->getInterfaces() as $interface) {
            foreach ($interface->getConfig()->getFields() as $intField) {
                $this->assertFieldsIdentical($intField, $type->getConfig()->getField($intField->getName()), $interface);
            }
        }
    }

    /**
     * @param Field                 $intField
     * @param Field                 $objField
     * @param AbstractInterfaceType $interface
     *
     * @throws ConfigurationException
     */
    protected function assertFieldsIdentical($intField, $objField, AbstractInterfaceType $interface)
    {
        $isValid = true;
        if ($intField->getType()->isCompositeType() !== $objField->getType()->isCompositeType()) {
            $isValid = false;
        }
        if ($intField->getType()->getNamedType()->getName() != $objField->getType()->getNamedType()->getName()) {
            $isValid = false;
        }

        if (!$isValid) {
            throw new ConfigurationException(sprintf('Implementation of %s is invalid for the field %s', $interface->getName(), $objField->getName()));
        }
    }
}


<?php

declare(strict_types = 1);

namespace PhpCsFixerCustomFixers\Fixer;

use PhpCsFixer\Fixer\ConfigurationDefinitionFixerInterface;
use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
use PhpCsFixer\FixerDefinition\CodeSample;
use PhpCsFixer\FixerDefinition\FixerDefinition;
use PhpCsFixer\FixerDefinition\FixerDefinitionInterface;
use PhpCsFixer\Preg;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;

final class OperatorLinebreakFixer extends AbstractFixer implements ConfigurationDefinitionFixerInterface, DeprecatingFixerInterface
{
    private const BOOLEAN_OPERATORS = [
        '&&' => T_BOOLEAN_AND,
        '||' => T_BOOLEAN_OR,
        'and' => T_LOGICAL_AND,
        'or' => T_LOGICAL_OR,
        'xor' => T_LOGICAL_XOR,
    ];

    private const NON_BOOLEAN_OPERATORS = [
        '=' => true,
        '.' => true,
        '*' => true,
        '/' => true,
        '%' => true,
        '<' => true,
        '>' => true,
        '|' => true,
        '^' => true,
        '+' => true,
        '-' => true,
        '&' => true,
        '&=' => true,
        '.=' => true,
        '/=' => true,
        '=>' => true,
        '==' => true,
        '>=' => true,
        '===' => true,
        '!=' => true,
        '<>' => true,
        '!==' => true,
        '<=' => true,
        '-=' => true,
        '%=' => true,
        '*=' => true,
        '|=' => true,
        '+=' => true,
        '<<' => true,
        '<<=' => true,
        '>>' => true,
        '>>=' => true,
        '^=' => true,
        '**' => true,
        '**=' => true,
        '<=>' => true,
        '??' => true,
        '?' => true,
        ':' => true,
    ];

    /** @var array<string, int|true> */
    private $operators;

    /** @var string */
    private $position = 'beginning';

    public function __construct()
    {
        $this->operators = \array_merge(self::BOOLEAN_OPERATORS, self::NON_BOOLEAN_OPERATORS);
    }

    public function getDefinition(): FixerDefinitionInterface
    {
        return new FixerDefinition(
            'Binary operators must always be at the beginning or at the end of the line.',
            [new CodeSample('<?php
function foo() {
    return $bar ||
        $baz;
}
')]
        );
    }

    public function getConfigurationDefinition(): FixerConfigurationResolver
    {
        return new FixerConfigurationResolver([
            (new FixerOptionBuilder('only_booleans', 'whether to limit operators to only boolean ones'))
                ->setDefault(false)
                ->setAllowedTypes(['bool'])
                ->getOption(),
            (new FixerOptionBuilder('position', 'whether to place operators at the beginning or at the end of the line'))
                ->setDefault($this->position)
                ->setAllowedValues(['beginning', 'end'])
                ->getOption(),
        ]);
    }

    public function configure(?array $configuration = null): void
    {
        if (isset($configuration['only_booleans']) && $configuration['only_booleans'] === true) {
            $this->operators = self::BOOLEAN_OPERATORS;
        }

        $this->position = $configuration['position'] ?? $this->position;
    }

    public function isCandidate(Tokens $tokens): bool
    {
        return true;
    }

    public function isRisky(): bool
    {
        return false;
    }

    public function fix(\SplFileInfo $file, Tokens $tokens): void
    {
        if ($this->position === 'beginning') {
            $this->fixMoveToTheBeginning($tokens);
        } else {
            $this->fixMoveToTheEnd($tokens);
        }
    }

    public function getPriority(): int
    {
        return 0;
    }

    public function getPullRequestId(): int
    {
        return 4021;
    }

    private function fixMoveToTheBeginning(Tokens $tokens): void
    {
        for ($index = 0; $index < $tokens->count(); $index++) {
            $indices = $this->getOperatorIndices($tokens, $index);
            if ($indices === null) {
                continue;
            }

            /** @var int $prevIndex */
            $prevIndex = $tokens->getNonEmptySibling(\min($indices), -1);

            /** @var int $nextIndex */
            $nextIndex = $tokens->getNextMeaningfulToken(\max($indices));

            for ($i = $nextIndex - 1; $i > $index; $i--) {
                if ($tokens[$i]->isWhitespace() && Preg::match('/\R/u', $tokens[$i]->getContent()) === 1) {
                    $isWhitespaceBefore = $tokens[$prevIndex]->isWhitespace();
                    $inserts = $this->getReplacementsAndClear($tokens, $indices, -1);
                    if ($isWhitespaceBefore) {
                        $inserts[] = new Token([T_WHITESPACE, ' ']);
                    }
                    $tokens->insertAt($nextIndex, $inserts);

                    break;
                }
            }
            $index = $nextIndex;
        }
    }

    private function fixMoveToTheEnd(Tokens $tokens): void
    {
        for ($index = $tokens->count() - 1; $index > 0; $index--) {
            $indices = $this->getOperatorIndices($tokens, $index);
            if ($indices === null) {
                continue;
            }

            /** @var int $prevIndex */
            $prevIndex = $tokens->getPrevMeaningfulToken(\min($indices));

            /** @var int $nextIndex */
            $nextIndex = $tokens->getNonEmptySibling(\max($indices), 1);

            for ($i = $prevIndex + 1; $i < $index; $i++) {
                if ($tokens[$i]->isWhitespace() && Preg::match('/\R/u', $tokens[$i]->getContent()) === 1) {
                    $isWhitespaceAfter = $tokens[$nextIndex]->isWhitespace();
                    $inserts = $this->getReplacementsAndClear($tokens, $indices, 1);
                    if ($isWhitespaceAfter) {
                        \array_unshift($inserts, new Token([T_WHITESPACE, ' ']));
                    }
                    $tokens->insertAt($prevIndex + 1, $inserts);

                    break;
                }
            }
            $index = $prevIndex;
        }
    }

    /**
     * @return null|int[]
     */
    private function getOperatorIndices(Tokens $tokens, int $index): ?array
    {
        if (!isset($this->operators[\strtolower($tokens[$index]->getContent())])) {
            return null;
        }

        if (isset($this->operators['?']) && $tokens[$index]->getContent() === '?') {
            /** @var int $nextIndex */
            $nextIndex = $tokens->getNextMeaningfulToken($index);
            if ($tokens[$nextIndex]->getContent() === ':') {
                return [$index, $nextIndex];
            }
        }

        if (isset($this->operators[':']) && $tokens[$index]->getContent() === ':') {
            /** @var int $prevIndex */
            $prevIndex = $tokens->getPrevMeaningfulToken($index);
            if ($tokens[$prevIndex]->getContent() === '?') {
                return [$prevIndex, $index];
            }
            $prevIndex = $tokens->getPrevTokenOfKind($prevIndex, [[T_CASE], '?']);
            if ($prevIndex === null || $tokens[$prevIndex]->isGivenKind(T_CASE)) {
                return null;
            }
        }

        return [$index];
    }

    /**
     * @param int[] $indices
     *
     * @return Token[]
     */
    private function getReplacementsAndClear(Tokens $tokens, array $indices, int $direction): array
    {
        return \array_map(
            static function (int $index) use ($tokens, $direction): Token {
                $clone = $tokens[$index];
                if ($tokens[$index + $direction]->isWhitespace()) {
                    $tokens->clearAt($index + $direction);
                }
                $tokens->clearAt($index);

                return $clone;
            },
            $indices
        );
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Protocol;

use Leevel\Http\IRequest;
use Leevel\Http\Request;
use Leevel\Router\IRouter;
use Swoole\Http\Request as SwooleHttpRequest;
use Swoole\Websocket\Frame as SwooleWebsocketFrame;
use Swoole\Websocket\Server as SwooleWebsocketServer;

/**
 * Websocket 服务
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.12.30
 * @see https://wiki.swoole.com/wiki/page/397.html
 *
 * @version 1.0
 */
class WebsocketServer extends HttpServer implements IServer
{
    /**
     * 新客户接入回调.
     *
     * @var string
     */
    const OPEN = 'open';

    /**
     * 收到客户端数据帧回调.
     *
     * @var string
     */
    const MESSAGE = 'message';

    /**
     * 客户端关闭回调.
     *
     * @var string
     */
    const CLOSE = 'close';

    /**
     * 客户连接 pathInfo 前缀
     *
     * @var string
     */
    const PATHINFO = 'websocket_pathinfo_';

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        // 监听 IP 地址
        // see https://wiki.swoole.com/wiki/page/p-server.html
        // see https://wiki.swoole.com/wiki/page/327.html
        'host' => '0.0.0.0',

        // 监听端口
        // see https://wiki.swoole.com/wiki/page/p-server.html
        // see https://wiki.swoole.com/wiki/page/327.html
        'port' => '9501',

        // swoole 进程名称
        'process_name' => 'leevel.websocket',

        // swoole 进程保存路径
        'pid_path' => '',

        // 设置启动的 worker 进程数
        // see https://wiki.swoole.com/wiki/page/275.html
        'worker_num' => 8,

        // 守护进程化
        // see https://wiki.swoole.com/wiki/page/278.html
        'daemonize' => 0,

        // 自定义进程
        'processes' => [],
    ];

    /**
     * 服务回调事件.
     *
     * @var array
     */
    protected $serverEvent = [
        'start',
        'connect',
        'workerStart',
        'managerStart',
        'workerStop',
        'shutdown',
        'open',
        'message',
        'task',
        'finish',
        'close',
    ];

    /**
     * WebSocket 客户端与服务器建立连接并完成握手后.
     *
     * @param \Swoole\Websocket\Server $server
     * @param \Swoole\Http\Request     $swooleRequest
     *
     * @see https://wiki.swoole.com/wiki/page/401.html
     */
    public function onOpen(SwooleWebsocketServer $server, SwooleHttpRequest $swooleRequest): void
    {
        $this->log(
            sprintf('Server: handshake success with fd %s', $swooleRequest->fd)
        );

        $this->setClientPathInfo($swooleRequest->fd, $swooleRequest->server['path_info']);

        $request = $this->normalizeRequest($swooleRequest);
        $request->setPathInfo($this->normalizePathInfo($request->getPathInfo(), self::OPEN));

        $this->setPreRequestMatched($request, [$server, $request, $swooleRequest->fd]);

        $response = $this->dispatchRouter($request);
    }

    /**
     * 监听服务器收到来自客户端的数据帧.
     *
     * @param \Swoole\Websocket\Server $server
     * @param \Swoole\Websocket\Frame  $frame
     *
     * @see https://wiki.swoole.com/wiki/page/397.html
     */
    public function onMessage(SwooleWebsocketServer $server, SwooleWebsocketFrame $frame): void
    {
        $this->log(
            sprintf(
                'Receive from fd %d:%s,opcode:%d,fin:%d',
                $frame->fd, $frame->data, $frame->opcode, $frame->finish
            )
        );

        if (false === ($pathInfo = $this->getClientPathInfo($frame->fd))) {
            return;
        }

        $request = $this->createRequestWithPathInfo($pathInfo, self::MESSAGE);

        $this->setPreRequestMatched($request, [$server, $frame, $frame->fd]);

        $response = $this->dispatchRouter($request);
    }

    /**
     * 监听连接关闭事件
     * 每个浏览器连接关闭时执行一次, reload 时连接不会断开, 也就不会触发该事件.
     *
     * @param \Swoole\Websocket\Server $server
     * @param int                      $fd
     * @param int                      $reactorId
     *
     * @see https://wiki.swoole.com/wiki/page/p-event/onClose.html
     */
    public function onWebsocketClose(SwooleWebsocketServer $server, int $fd, int $reactorId): void
    {
        $this->log(
            sprintf('Server close, fd %d, reactorId %d.', $fd, $reactorId)
        );

        /**
         * 未连接
         * WEBSOCKET_STATUS_CONNECTION = 1，连接进入等待握手
         * WEBSOCKET_STATUS_HANDSHAKE = 2，正在握手
         * WEBSOCKET_STATUS_FRAME = 3，已握手成功等待浏览器发送数据帧.
         *
         * @see https://wiki.swoole.com/wiki/page/413.html
         */
        $clientInfo = $this->server->getClientInfo($fd);

        if ($clientInfo['websocket_status'] <= 0) {
            return;
        }

        // 不存在任何连接
        if (!$this->count()) {
            return;
        }

        if (false === ($pathInfo = $this->getClientPathInfo($fd))) {
            return;
        }

        $request = $this->createRequestWithPathInfo($pathInfo, self::CLOSE);

        $this->setPreRequestMatched($request, [$server, $fd, $reactorId]);

        $response = $this->dispatchRouter($request);
    }

    /**
     * 获取客户端连接数.
     *
     * @return int
     */
    public function count(): int
    {
        return count($this->server->connections);
    }

    /**
     * 设置路由匹配数据.
     *
     * @param \Leevel\Http\IRequest $request
     * @param array                 $data
     */
    protected function setPreRequestMatched(IRequest $request, array $data): void
    {
        $this->container->make(IRouter::class)->

        setPreRequestMatched($request, [IRouter::VARS => $data]);
    }

    /**
     * 根据 pathInfo 创建 HTTP 请求对象
     *
     * @param string $pathInfo
     * @param string $type
     *
     * @return \Leevel\Http\IRequest
     */
    protected function createRequestWithPathInfo(string $pathInfo, string $type): IRequest
    {
        $request = new Request();
        $request->setPathInfo($this->normalizePathInfo($pathInfo, $type));

        return $request;
    }

    /**
     * 格式化 pathInfo.
     *
     * @param string $pathInfo
     * @param string $type
     *
     * @return string
     */
    protected function normalizePathInfo(string $pathInfo, string $type): string
    {
        return '/'.trim($pathInfo, '/').'/'.$type;
    }

    /**
     * 创建 websocket server.
     */
    protected function createServer(): void
    {
        $this->server = new SwooleWebsocketServer(
            $this->option['host'],
            (int) ($this->option['port'])
        );

        $this->initServer();
    }

    /**
     * 获取客户端连接 PathInfo.
     *
     * @param int $fd
     *
     * @return false|string
     */
    protected function getClientPathInfo(int $fd)
    {
        $key = self::PATHINFO.$fd;
        $pathInfo = $this->container->make($key);

        if ($key === $pathInfo) {
            return false;
        }

        return $pathInfo;
    }

    /**
     * 设置客户端连接 PathInfo.
     *
     * @param int    $fd
     * @param string $pathInfo
     */
    protected function setClientPathInfo(int $fd, string $pathInfo): void
    {
        $this->container->instance(self::PATHINFO.$fd, $pathInfo);
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Filesystem;

use InvalidArgumentException;
use League\Flysystem\Adapter\Local as AdapterLocal;

/**
 * filesystem.local.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.08.29
 * @see https://flysystem.thephpleague.com/adapter/local/
 *
 * @version 1.0
 */
class Local extends Connect implements IConnect
{
    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'path'          => '',
        'write_flags'   => LOCK_EX,
        'link_handling' => AdapterLocal::DISALLOW_LINKS,
        'permissions'   => [],
    ];

    /**
     * 创建连接.
     *
     * @return \League\Flysystem\AdapterInterface
     */
    public function makeConnect()
    {
        if (empty($this->option['path'])) {
            throw new InvalidArgumentException(
                'The local requires path option.'
            );
        }

        return new AdapterLocal(
            $this->option['path'],
            $this->option['write_flags'],
            $this->option['link_handling'],
            $this->option['permissions']
        );
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\View\Console;

use Leevel\Console\Command;
use Leevel\Kernel\IApp;
use Leevel\Option\IOption;
use Leevel\View\Compiler;
use Leevel\View\IView;
use Leevel\View\Parser;
use RuntimeException;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Finder\Finder;

/**
 * 视图缓存.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.09.01
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
class Cache extends Command
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'view:cache';

    /**
     * 命令行描述.
     *
     * @var string
     */
    protected $description = 'Compile all view files.';

    /**
     * IOC 容器.
     *
     * @var \Leevel\Kernel\IApp
     */
    protected $app;

    /**
     * 视图分析器.
     *
     * @var \Leevel\View\Parser
     */
    protected $parser;

    /**
     * 视图 HTML 仓储.
     *
     * @var \Leevel\View\IView
     */
    protected $html;

    /**
     * 响应命令.
     *
     * @param \Leevel\Kernel\IApp    $app
     * @param \Leevel\Option\IOption $option
     */
    public function handle(IApp $app, IOption $option): void
    {
        $this->app = $app;
        $this->parser = $this->createParser();
        $this->html = $this->getHtmlView();

        $this->line('Start to cache view.');

        foreach ($this->paths() as $path) {
            $this->compiles($this->findFiles([$path]), $path);
        }

        $this->info('View files cache successed.');
    }

    /**
     * 编译视图文件.
     *
     * @param \Symfony\Component\Finder\Finder $finder
     * @param string                           $path
     */
    protected function compiles(Finder $finder, string $path): void
    {
        if (0 === count($finder)) {
            $this->comment(
                sprintf('Compile files not found in path `%s` and skipped.', $path)
            );

            return;
        }

        $this->info(sprintf('Start to compiles path `%s`', $path));

        $progressBar = new ProgressBar(new ConsoleOutput(), count($finder));
        $progressBar->setFormat('[View:cache]%current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%');

        foreach ($finder as $file) {
            $progressBar->advance();

            $this->parser->doCompile(
                $file->getRealPath(),
                $this->html->getCachePath($file->getRealPath())
            );
        }

        $progressBar->finish();
        $this->line('');
    }

    /**
     * 查找视图目录中的视图文件.
     *
     * @param array $paths
     *
     * @return \Symfony\Component\Finder\Finder
     */
    protected function findFiles(array $paths): Finder
    {
        return (new Finder())->
            in($paths)->
            exclude(['vendor', 'node_modules'])->
            name('*.html')->
            files();
    }

    /**
     * 获取视图扫描目录.
     *
     * @return array
     */
    protected function paths(): array
    {
        return array_merge([$this->app->themesPath()], $this->composerPaths());
    }

    /**
     * 取得应用的 composer 配置.
     *
     * @return array
     */
    protected function composerPaths(): array
    {
        $path = $this->app->path().'/composer.json';

        if (!is_file($path)) {
            return [];
        }

        $options = $this->getFileContent($path);

        $paths = !empty($options['extra']['leevel-console']['view-cache']['paths']) ?
            $options['extra']['leevel-console']['view-cache']['paths'] : [];

        $path = $this->app->path();

        $paths = array_map(function (string $value) use ($path) {
            if (!is_file($value)) {
                $value = $path.'/'.$value;
            }

            if (!is_dir($value)) {
                throw new RuntimeException(
                    sprintf('View dir %s is not exist.', $value)
                );
            }

            return $value;
        }, $paths);

        return $paths;
    }

    /**
     * 获取配置信息.
     *
     * @param string $path
     *
     * @return array
     */
    protected function getFileContent(string $path): array
    {
        return json_decode(file_get_contents($path), true);
    }

    /**
     * 创建模板分析器.
     *
     * @return \Leevel\View\Parser
     */
    protected function createParser(): Parser
    {
        return (new Parser(new Compiler()))->
        registerCompilers()->

        registerParsers();
    }

    /**
     * 获取 HTML 视图仓储.
     *
     * @return \Leevel\View\IView
     */
    protected function getHtmlView(): IView
    {
        return $this->app->make('view.views')->connect('html');
    }

    /**
     * 命令参数.
     *
     * @return array
     */
    protected function getArguments(): array
    {
        return [];
    }

    /**
     * 命令配置.
     *
     * @return array
     */
    protected function getOptions(): array
    {
        return [];
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Protocol\Process;

use Leevel\Protocol\IServer;

/**
 * Swoole 服务状态.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.12.13
 *
 * @version 1.0
 */
class Status extends Process
{
    /**
     * 进程名字.
     *
     * @var string
     */
    protected $name = 'status';

    /**
     * 响应句柄.
     *
     * @param \Leevel\Protocol\IServer $server
     * @param \Swoole\Process          $worker
     */
    public function handle(IServer $server): void
    {
        while (true) {
            sleep(1);

            $this->status($server);
        }
    }

    /**
     * 查看当前服务状态.
     *
     * @param \Leevel\Protocol\IServer $server
     */
    public function status(IServer $server): void
    {
        // see https://wiki.swoole.com/wiki/page/288.html
        $result = $server->stats();

        $this->log('Item  Value');

        foreach ($result as $key => $val) {
            if ('start_time' === $key) {
                $val = date('Y-m-d H:i:s', $val);
            }

            $this->log(sprintf('%s %s', $key, $val));
        }
    }

    /**
     * 记录日志.
     *
     * @param string $log
     */
    protected function log(string $log): void
    {
        fwrite(STDOUT, $log.PHP_EOL);
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Throttler;

use Countable;
use Leevel\Cache\ICache;
use Leevel\Support\IArray;
use RuntimeException;

/**
 * RateLimiter 入口.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.08.07
 *
 * @version 1.0
 */
class RateLimiter implements IRateLimiter, IArray, Countable
{
    /**
     * 缓存接口.
     *
     * @var \Leevel\Cache\ICache
     */
    protected $cache;

    /**
     * 缓存键值
     *
     * @var string
     */
    protected $key;

    /**
     * 指定时间内允许的最大请求次数.
     *
     * @var int
     */
    protected $limit = 60;

    /**
     * 指定时间长度.
     *
     * @var int
     */
    protected $time = 60;

    /**
     * 构造函数.
     *
     * @param \Leevel\Cache\ICache $cache
     * @param string               $key
     * @param string               $limit
     * @param string               $time
     */
    public function __construct(ICache $cache, string $key, int $limit = 60, int $time = 60)
    {
        $this->cache = $cache;
        $this->key = $key;
        $this->limit = $limit;
        $this->time = $time;
    }

    /**
     * 验证请求
     *
     * @return bool
     */
    public function attempt(): bool
    {
        if (!($result = $this->tooManyAttempt())) {
            $this->hit();
        }

        return $result;
    }

    /**
     * 判断资源是否被耗尽.
     *
     * @return bool
     */
    public function tooManyAttempt(): bool
    {
        return $this->retryAfterReal() && $this->remainingReal() < 0;
    }

    /**
     * 执行请求
     *
     * @return $this
     */
    public function hit(): IRateLimiter
    {
        $this->saveData($this->count() + 1);

        return $this;
    }

    /**
     * 请求返回 HEADER.
     *
     * @return array
     */
    public function header(): array
    {
        return [
            'X-RateLimit-Time'       => $this->time, // 指定时间长度
            'X-RateLimit-Limit'      => $this->limit, // 指定时间内允许的最大请求次数
            'X-RateLimit-Remaining'  => $this->remaining(), // 指定时间内剩余请求次数
            'X-RateLimit-RetryAfter' => $this->retryAfter(), // 距离下一次请求等待时间
            'X-RateLimit-Reset'      => $this->endTime(), // 下次重置时间
        ];
    }

    /**
     * 距离下一次请求等待时间.
     *
     * @return int
     */
    public function retryAfter(): int
    {
        return $this->remainingReal() ? 0 : ($this->retryAfterReal() ?: 0);
    }

    /**
     * 指定时间内剩余请求次数.
     *
     * @return int
     */
    public function remaining(): int
    {
        return $this->remainingReal() ?: 0;
    }

    /**
     * 指定时间长度.
     *
     * @param int $limit
     *
     * @return $this
     */
    public function limit(int $limit = 60): IRateLimiter
    {
        $this->limit = $limit;

        return $this;
    }

    /**
     * 指定时间内允许的最大请求次数.
     *
     * @param int $time
     *
     * @return $this
     */
    public function time(int $time = 60): IRateLimiter
    {
        $this->time = $time;

        return $this;
    }

    /**
     * 返回缓存组件.
     *
     * @return \Leevel\Cache\ICache
     */
    public function getCache(): ICache
    {
        return $this->cache;
    }

    /**
     * 下次重置时间.
     *
     * @return int
     */
    public function endTime(): int
    {
        return $this->getData()[0];
    }

    /**
     * 请求次数.
     *
     * @return int
     */
    public function count(): int
    {
        return $this->getData()[1];
    }

    /**
     * 对象转数组.
     *
     * @return array
     */
    public function toArray(): array
    {
        return $this->header();
    }

    /**
     * 距离下一次请求等待时间
     * 实际，可能扣减为负数.
     *
     * @return int
     */
    protected function retryAfterReal(): int
    {
        return $this->endTime() - time();
    }

    /**
     * 指定时间内剩余请求次数
     * 实际，可能扣减为负数.
     *
     * @return int
     */
    protected function remainingReal(): int
    {
        return $this->limit - $this->count();
    }

    /**
     * 保存缓存数据.
     *
     * @param int $count
     */
    protected function saveData(int $count): void
    {
        $this->cache->set(
            $this->getKey(),
            $this->getImplodeData($this->endTime(), $count)
        );
    }

    /**
     * 读取缓存数据.
     *
     * @return array
     */
    protected function getData(): array
    {
        if (($data = $this->cache->get($this->getKey()))) {
            $data = $this->getExplodeData($data);
        } else {
            $data = [
                $this->getInitEndTime(),
                $this->getInitCount(),
            ];
        }

        return $data;
    }

    /**
     * 组装缓存数据.
     *
     * @param int $endTime
     * @param int $count
     *
     * @return string
     */
    protected function getImplodeData(int $endTime, int $count): string
    {
        return $endTime.static::SEPARATE.$count;
    }

    /**
     * 分隔缓存数据.
     *
     * @param array $data
     *
     * @return array
     */
    protected function getExplodeData(string $data): array
    {
        return array_map(function ($v) {
            return (int) ($v);
        }, explode(static::SEPARATE, $data));
    }

    /**
     * 获取 key.
     *
     * @return string
     */
    protected function getKey(): string
    {
        if (!$this->key) {
            throw new RuntimeException('Key is not set.');
        }

        return $this->key;
    }

    /**
     * 初始化下一次重置时间.
     *
     * @return int
     */
    protected function getInitEndTime(): int
    {
        return time() + $this->limit;
    }

    /**
     * 初始化点击.
     *
     * @return int
     */
    protected function getInitCount(): int
    {
        return 0;
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Debug;

use Spiral\Debug;
use Symfony\Component\VarDumper\VarDumper;

/**
 * 调试一个变量.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.04.05
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
class Dump
{
    /**
     * 调试变量.
     *
     * @param mixed $var
     * @param array $moreVars
     *
     * @return mixed
     */
    public static function dump($var, ...$moreVars)
    {
        VarDumper::dump($var);

        foreach ($moreVars as $var) {
            VarDumper::dump($var);
        }

        if (1 < func_num_args()) {
            return func_get_args();
        }

        return $var;
    }

    /**
     * 调试变量并中断.
     *
     * @param mixed $var
     * @param array $moreVars
     */
    public static function dumpDie($var, ...$moreVars): void
    {
        static::dump($var, ...$moreVars);

        die;
    }

    /**
     * 调试 RoadRunner 变量.
     *
     * @param mixed $var
     * @param array $moreVars
     *
     * @return mixed
     */
    public static function dumpRoadRunner($var, ...$moreVars)
    {
        static $dumper;

        if (null === $dumper) {
            $dumper = new Debug\Dumper();
            $dumper->setRenderer(Debug\Dumper::ERROR_LOG, new Debug\Renderer\ConsoleRenderer());
        }

        $dumper->dump($var, Debug\Dumper::ERROR_LOG);

        foreach ($moreVars as $var) {
            $dumper->dump($var);
        }

        if (1 < func_num_args()) {
            return func_get_args();
        }

        return $var;
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Kernel\Console;

use Leevel\Console\Argument;
use Leevel\Console\Command;
use Leevel\Kernel\IApp;
use Leevel\Option\IOption;

/**
 * 解析内部单元测试用例为 Markdown 文档.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2019.03.23
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
class DocFramework extends Command
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'make:docwithin';

    /**
     * 命令行描述.
     *
     * @var string
     */
    protected $description = 'Markdown generation based on within test cases.';

    /**
     * 响应命令.
     *
     * @param \Leevel\Kernel\IApp    $app
     * @param \Leevel\Option\IOption $option
     */
    public function handle(IApp $app, IOption $option): void
    {
        $input = [
            'path'      => $this->path(),
            'testsdir'  => $app->path('vendor/hunzhiwange/framework/tests'),
            'outputdir' => $option->get('console\\framework_doc_outputdir'),
        ];

        $this->call('make:doc', $input);
    }

    /**
     * 取得测试用例文件或者目录相对路径.
     *
     * @return string
     */
    protected function path(): string
    {
        return $this->argument('path');
    }

    /**
     * 命令参数.
     *
     * @return array
     */
    protected function getArguments(): array
    {
        return [
            [
                'path',
                Argument::REQUIRED,
                'This is the tests file or dir path.',
            ],
        ];
    }

    /**
     * 命令配置.
     *
     * @return array
     */
    protected function getOptions(): array
    {
        return [];
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Cache\Redis;

use Redis;
use RuntimeException;

/**
 * php redis 扩展缓存.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.07.29
 *
 * @version 1.0
 */
class PhpRedis implements IConnect
{
    /**
     * redis 句柄.
     *
     * @var handle
     */
    protected $handle;

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'host'        => '127.0.0.1',
        'port'        => 6379,
        'password'    => '',
        'select'      => 0,
        'timeout'     => 0,
        'persistent'  => false,
    ];

    /**
     * 构造函数.
     *
     * @param array $option
     */
    public function __construct(array $option = [])
    {
        if (!extension_loaded('redis')) {
            // @codeCoverageIgnoreStart
            throw new RuntimeException('Redis extension must be loaded before use.');
            // @codeCoverageIgnoreEnd
        }

        $this->option = array_merge($this->option, $option);

        $this->handle = $this->createRedis();

        $this->handle->{$this->option['persistent'] ? 'pconnect' : 'connect'}(
            $this->option['host'],
            (int) ($this->option['port']),
            $this->option['timeout']
        );

        if ($this->option['password']) {
            // @codeCoverageIgnoreStart
            $this->handle->auth($this->option['password']);
            // @codeCoverageIgnoreEnd
        }

        if ($this->option['select']) {
            $this->handle->select($this->option['select']);
        }
    }

    /**
     * 返回缓存句柄.
     *
     * @return mixed
     */
    public function handle()
    {
        return $this->handle;
    }

    /**
     * 获取缓存.
     *
     * @param string $name
     *
     * @return mixed
     */
    public function get(string $name)
    {
        return $this->handle->get($name);
    }

    /**
     * 设置缓存.
     *
     * @param string $name
     * @param mixed  $data
     * @param int    $expire
     */
    public function set(string $name, $data, ?int $expire = null): void
    {
        if ($expire) {
            $this->handle->setex($name, $expire, $data);
        } else {
            $this->handle->set($name, $data);
        }
    }

    /**
     * 清除缓存.
     *
     * @param string $name
     */
    public function delete(string $name): void
    {
        $this->handle->delete($name);
    }

    /**
     * 关闭 redis.
     */
    public function close(): void
    {
        $this->handle->close();
        $this->handle = null;
    }

    /**
     * 返回 redis 对象
     *
     * @return \Redis
     */
    protected function createRedis(): Redis
    {
        return new Redis();
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Mail;

use Swift_SmtpTransport;

/**
 * mail.smtp.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.08.26
 *
 * @version 1.0
 */
class Smtp extends Connect implements IConnect
{
    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'host'       => 'smtp.qq.com',
        'port'       => 465,
        'username'   => null,
        'password'   => '',
        'encryption' => 'ssl',
    ];

    /**
     * 创建 transport.
     *
     * @return mixed
     */
    public function makeTransport()
    {
        $transport = new Swift_SmtpTransport(
            $this->option['host'],
            $this->option['port']
        );

        if (null !== $this->option['encryption']) {
            $transport->setEncryption(
                $this->option['encryption']
            );
        }

        if (null !== $this->option['username']) {
            $transport->setUsername(
                $this->option['username']
            );

            $transport->setPassword(
                $this->option['password']
            );
        }

        return $transport;
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Database\Ddd\Relation;

use Leevel\Collection\Collection;

/**
 * 关联模型实体 HasOne.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.09.28
 *
 * @version 1.0
 */
class HasOne extends HasMany
{
    /**
     * 查询关联对象
     *
     * @return mixed
     */
    public function sourceQuery()
    {
        return $this->select->findOne();
    }

    /**
     * 匹配关联查询数据到模型实体.
     *
     * @param \Leevel\Database\Ddd\IEntity[] $entitys
     * @param \Leevel\Collection\Collection  $result
     * @param string                         $relation
     *
     * @return array
     */
    public function matchPreLoad(array $entitys, Collection $result, string $relation): array
    {
        return $this->matchPreLoadOneOrMany($entitys, $result, $relation, 'one');
    }
}


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Kernel\Console;

use InvalidArgumentException;
use Leevel\Console\Command;
use Leevel\Console\Option;
use Leevel\Filesystem\Fso\create_file;
use function Leevel\Filesystem\Fso\create_file;
use Leevel\Kernel\IApp;

/**
 * 优化 composer 自动加载.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.09.01
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
class Autoload extends Command
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'autoload';

    /**
     * 命令行描述.
     *
     * @var string
     */
    protected $description = 'Optimize base on composer dump-autoload -o.';

    /**
     * IOC 容器.
     *
     * @var \Leevel\Kernel\IApp
     */
    protected $app;

    /**
     * Composer 生成的随机 hash.
     *
     * @var string
     */
    protected $composerHash;

    /**
     * 响应命令.
     *
     * @param \Leevel\Kernel\IApp $app
     */
    public function handle(IApp $app): void
    {
        $this->app = $app;

        $this->line('Start to cache autoload.');

        if (false === $this->ignore()) {
            $this->line('Exec composer dump-autoload -o.');
            exec($this->normalizeComposerCommand());
        }

        $data = $this->data();

        $cachePath = $this->autoloadCachedPath();

        $this->writeCache($cachePath, $data);

        $this->info(sprintf('Autoload file %s cache successed.', $cachePath));
    }

    /**
     * 取得 composer 优化命令.
     *
     * @return string
     */
    protected function normalizeComposerCommand(): string
    {
        return sprintf(
            '%s dump-autoload -o',
            escapeshellarg($this->composer())
        );
    }

    /**
     * 取得缓存路径.
     */
    protected function autoloadCachedPath(): string
    {
        return $this->app->path('vendor/autoloadLeevel.php');
    }

    /**
     * 获取缓存数据.
     *
     * @return string
     */
    protected function data(): string
    {
        $data = [];

        $data[] = $this->replaceIncludeFile(file_get_contents($this->app->path('vendor/composer/ClassLoader.php')));
        $data[] = $this->replaceComposerPath($this->dataInit());
        $data[] = $this->dataHelper();
        $data[] = $this->dataLoader();

        unlink($this->app->path('vendor/composer/ComposerStaticInit.php'));

        return implode(PHP_EOL.PHP_EOL, $data).PHP_EOL;
    }

    /**
     * 替换导入文件.
     *
     * @param string $content
     *
     * @return string
     */
    protected function replaceIncludeFile(string $content): string
    {
        return str_replace('include $file;', 'include_once $file;', $content);
    }

    /**
     * 替换 composer 路径.
     *
     * @param string $content
     *
     * @return string
     */
    protected function replaceComposerPath(string $content): string
    {
        return str_replace(
            '\''.$this->app->path('vendor/composer/'),
            'LEEVEL_COMPOSER . \'/',
            $content
        );
    }

    /**
     * 取得初始化数据.
     *
     * @return string
     */
    protected function dataInit(): string
    {
        list($lengths, $prefixs) = $this->prefixesPsr4();

        return '/**
 * Define the root path.
 */
define(\'LEEVEL_COMPOSER\', __DIR__.\'/composer\');

/**
 * generated by `php leevel autoload`.
 */
class ComposerStaticInit
{
    public static $files = '.var_export($this->files(), true).';

    public static $prefixLengthsPsr4 = '.var_export($lengths, true).';

    public static $prefixDirsPsr4 = '.var_export($prefixs, true).';

    public static $prefixesPsr0 = [];

    public static $classMap = '.var_export($this->classMap(), true).';

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInit::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInit::$prefixDirsPsr4;
            $loader->prefixesPsr0 = ComposerStaticInit::$prefixesPsr0;
            $loader->classMap = ComposerStaticInit::$classMap;

        }, null, ClassLoader::class);
    }
}';
    }

    /**
     * 取得助手文件数据.
     *
     * @return string
     */
    protected function dataHelper(): string
    {
        $autoloadFiles = [];

        foreach ($this->files() as $file) {
            foreach ($this->appHelpers() as $whiteFile) {
                if (false !== strpos($file, $whiteFile)) {
                    $autoloadFiles[] = "require_once '".$this->replaceComposerPath($file)."';";
                }
            }
        }

        $autoloadFiles = $this->replaceComposerPath(implode(PHP_EOL, $autoloadFiles));

        return <<<eot
            /**
             * Ignore the helper functions.
             * Because most of them are useless.
             */
            foreach (ComposerStaticInit::\$files as \$fileIdentifier => \$_) {
                \$GLOBALS['__composer_autoload_files'][\$fileIdentifier] = true;
            }

            {$autoloadFiles}
            eot;
    }

    /**
     * 取得载入数据.
     *
     * @return string
     */
    protected function dataLoader(): string
    {
        $data = <<<'eot'
            /**
             * Set loader.
             */
            if (!isset($GLOBALS['__composer_autoload_leevel'])) {
                $GLOBALS['__composer_autoload_leevel'] = new ClassLoader();
            }

            $loader =  $GLOBALS['__composer_autoload_leevel'];
            call_user_func(ComposerStaticInit::getInitializer($loader));
            $loader->register(true);

            spl_autoload_register(function (string $className) use(&$loader) {
                static $loaded;

                /**
                 * Just a function.
                 */
                if (preg_match('/\\\\([a-z])+/', $className)) {
                    return;
                }

                if (null === $loaded) {
                    /**
                     * Load all if necessary.
                     */
                    require_once __DIR__ . '/composer/autoload_static.php';
                    call_user_func(ComposerStaticInit{HASH}::getInitializer($loader));
                    $loaded = true;
                }

                $loader->loadClass($className);
             });

            return $loader;
            eot;

        $data = str_replace('{HASH}', $this->composerHash, $data);

        return $data;
    }

    /**
     * 获取 psr4 前缀.
     *
     * @return array
     */
    protected function prefixesPsr4(): array
    {
        $lengths = $prefixs = [];

        $composerStaticClass = $this->composerStaticClass();
        $prefixesPsr4 = $composerStaticClass::$prefixDirsPsr4;

        foreach ($this->optimizeNamespaces() as $prefix) {
            $first = $prefix[0];
            $prefix .= '\\';

            $lengths[$first][$prefix] = strlen($prefix);

            if (isset($prefixesPsr4[$prefix])) {
                $prefixs[$prefix] = $prefixesPsr4[$prefix];
            }
        }

        return [$lengths, $prefixs];
    }

    /**
     * 获取类映射.
     *
     * @return array
     */
    protected function classMap(): array
    {
        $result = [];

        $optimizeNamespaces = $this->optimizeNamespaces();
        $composerStaticClass = $this->composerStaticClass();
        $classMap = $composerStaticClass::$classMap;

        foreach ($classMap as $key => $value) {
            list($namespace) = explode('\\', $key);

            if (in_array($namespace, $optimizeNamespaces, true)) {
                $result[$key] = $value;
            }
        }

        return $result;
    }

    /**
     * 获取载入助手函数.
     *
     * @return array
     */
    protected function files(): array
    {
        $this->optimizeNamespaces();
        $composerStaticClass = $this->composerStaticClass();

        return $composerStaticClass::$files;
    }

    /**
     * 取得 composer 静态类.
     *
     * @return string
     */
    protected function composerStaticClass(): string
    {
        static $staticClass;

        if ($staticClass) {
            return $staticClass;
        }

        $content = file_get_contents($this->app->path('vendor/autoload.php'));

        if (preg_match('/ComposerAutoloaderInit(\S{32})::getLoader/', $content, $matches)) {
            $staticContent = file_get_contents(
                $this->app->path('vendor/composer/autoload_static.php')
            );

            file_put_contents(
                $staticPath = $this->app->path('vendor/composer/ComposerStaticInit.php'),
                str_replace('ComposerStaticInit'.$matches[1], 'ComposerStaticInit', $staticContent)
            );

            $this->composerHash = $matches[1];

            include $staticPath;

            return $staticClass = '\\Composer\\Autoload\\ComposerStaticInit';
        }

        throw new InvalidArgumentException('Composer autoload is invalid.');
    }

    /**
     * 取得优化的命名空间.
     *
     * @return array
     */
    protected function optimizeNamespaces(): array
    {
        static $result;

        if ($result) {
            return $result;
        }

        return $result = $this->appNamespaces();
    }

    /**
     * 取得应用的 composer 命名空间配置.
     *
     * @return array
     */
    protected function appNamespaces(): array
    {
        $path = $this->app->path().'/composer.json';

        if (!is_file($path)) {
            return [];
        }

        $options = $this->getFileContent($path);

        $appNamespaces = $options['autoload']['psr-4'] ?? [];

        if ($appNamespaces) {
            $appNamespaces = array_map(function (string $value) {
                return rtrim($value, '\\');
            }, array_keys($appNamespaces));
        }

        $extraNamespaces = $options['extra']['leevel-console']['autoload']['namespaces'] ?? [];

        return array_merge($appNamespaces, $extraNamespaces);
    }

    /**
     * 取得应用的 composer 自动载入助手文件配置.
     *
     * @return array
     */
    protected function appHelpers(): array
    {
        $path = $this->app->path().'/composer.json';

        if (!is_file($path)) {
            return [];
        }

        $options = $this->getFileContent($path);

        return $options['extra']['leevel-console']['autoload']['files'] ?? [];
    }

    /**
     * 获取配置信息.
     *
     * @param string $path
     *
     * @return array
     */
    protected function getFileContent(string $path): array
    {
        return json_decode(file_get_contents($path), true);
    }

    /**
     * 写入缓存.
     *
     * @param string $cachePath
     * @param array  $data
     */
    protected function writeCache(string $cachePath, string $data): void
    {
        create_file($cachePath, $data);
    }

    /**
     * 取得 Composer 路径.
     *
     * @return string
     */
    protected function composer(): string
    {
        return $this->option('composer');
    }

    /**
     * 取得忽略 composer 自身命令.
     *
     * @return bool
     */
    protected function ignore(): bool
    {
        return $this->option('ignore');
    }

    /**
     * 命令参数.
     *
     * @return array
     */
    protected function getArguments(): array
    {
        return [];
    }

    /**
     * 命令配置.
     *
     * @return array
     */
    protected function getOptions(): array
    {
        return [
            [
                'composer',
                null,
                Option::VALUE_OPTIONAL,
                'Where is composer.',
                'composer',
            ],
            [
                'ignore',
                'i',
                Option::VALUE_NONE,
                'Ignore composer itself.',
            ],
        ];
    }
}

// import fn.
class_exists(create_file::class);


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Router\Console;

use Leevel\Console\Argument;
use Leevel\Console\Make;
use Leevel\Console\Option;
use Leevel\Router\IRouter;
use function Leevel\Support\Str\camelize;
use Leevel\Support\Str\camelize;

/**
 * 生成控制器.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.05.03
 *
 * @version 1.0
 */
class Controller extends Make
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'make:controller';

    /**
     * 命令描述.
     *
     * @var string
     */
    protected $description = 'Create a new controller.';

    /**
     * 命令帮助.
     *
     * @var string
     */
    protected $help = <<<'EOF'
        The <info>%command.name%</info> command to make controller with app namespace:

          <info>php %command.full_name% name action</info>

        You can also by using the <comment>--namespace</comment> option:

          <info>php %command.full_name% name action --namespace=common</info>
        EOF;

    /**
     * 响应命令.
     *
     * @param \Leevel\Router\IRouter $router
     */
    public function handle(IRouter $router): void
    {
        // 处理命名空间路径
        $this->parseNamespace();

        // 设置模板路径
        $this->setTemplatePath(__DIR__.'/stub/controller');

        $controllerNamespace = $router->getControllerDir();
        $controllerName = ucfirst(camelize($this->argument('name')));

        $this->setCustomReplaceKeyValue('file_name', $controllerName);

        $this->setCustomReplaceKeyValue('controller_dir', $controllerNamespace);

        $this->setCustomReplaceKeyValue('action', $this->normalizeAction($this->argument('action')));

        // 保存路径
        $this->setSaveFilePath(
            $this->getNamespacePath().
            str_replace('\\', '/', $controllerNamespace).'/'.
            $controllerName.'.php'
        );

        // 设置类型
        $this->setMakeType('controller');

        // 执行
        $this->create();
    }

    /**
     * 格式化方法名.
     *
     * @param string $action
     *
     * @return string
     */
    protected function normalizeAction(string $action): string
    {
        if (false !== strpos($action, '-')) {
            $action = str_replace('-', '_', $action);
        }

        if (false !== strpos($action, '_')) {
            $action = '_'.str_replace('_', ' ', $action);
            $action = ltrim(str_replace(' ', '', ucwords($action)), '_');
        }

        return $action;
    }

    /**
     * 命令参数.
     *
     * @return array
     */
    protected function getArguments(): array
    {
        return [
            [
                'name',
                Argument::REQUIRED,
                'This is the controller name.',
            ],
            [
                'action',
                Argument::OPTIONAL,
                'This is the action name.',
                'index',
            ],
        ];
    }

    /**
     * 命令配置.
     *
     * @return array
     */
    protected function getOptions(): array
    {
        return [
            [
                'namespace',
                null,
                Option::VALUE_OPTIONAL,
                'Apps namespace registered to system,default namespace is these (Common,App,Admin)',
                'app',
            ],
        ];
    }
}

// import fn.
class_exists(camelize::class);


<?php

declare(strict_types=1);

/*
 * This file is part of the ************************ package.
 * _____________                           _______________
 *  ______/     \__  _____  ____  ______  / /_  _________
 *   ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
 *    __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
 *      \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
 *         \_\                /_/_/         /_/
 *
 * The PHP Framework For Code Poem As Free As Wind. <Query Yet Simple>
 * (c) 2010-2019 http://queryphp.com All rights reserved.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Leevel\Pipeline;

use Closure;
use Generator;
use InvalidArgumentException;
use Leevel\Di\IContainer;

/**
 * 管道实现类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.05.25
 *
 * @version 1.0
 */
class Pipeline implements IPipeline
{
    /**
     * 容器.
     *
     * @var \Leevel\Di\IContainer
     */
    protected $container;

    /**
     * 管道传递的对象
     *
     * @var array
     */
    protected $passed = [];

    /**
     * 管道中所有执行工序.
     *
     * @var array
     */
    protected $stage = [];

    /**
     * 迭代器.
     *
     * @var \Generator
     */
    protected $generator;

    /**
     * 创建一个管道.
     *
     * @param \Leevel\Di\IContainer $container
     */
    public function __construct(IContainer $container)
    {
        $this->container = $container;
    }

    /**
     * 将传输对象传入管道.
     *
     * @param array $passed
     *
     * @return $this
     */
    public function send(array $passed): IPipeline
    {
        foreach ($passed as $item) {
            $this->passed[] = $item;
        }

        return $this;
    }

    /**
     * 设置管道中的执行工序.
     *
     * @param array $stage
     *
     * @return $this
     */
    public function through(array $stage): IPipeline
    {
        foreach ($stage as $item) {
            $this->stage[] = $item;
        }

        return $this;
    }

    /**
     * 执行管道工序响应结果.
     *
     * @param \Closure $end
     *
     * @since 2018.01.03
     *
     * @return mixed
     */
    public function then(Closure $end = null)
    {
        $stage = $this->stage;

        if ($end) {
            $stage[] = $end;
        }

        $this->generator = $this->stageGenerator($stage);

        return $this->traverseGenerator(...$this->passed);
    }

    /**
     * 遍历迭代器.
     *
     * @since 2018.01.03
     *
     * @return mixed
     */
    protected function traverseGenerator(...$args)
    {
        if (!$this->generator->valid() ||
            $this->generator->next() ||
            !$this->generator->valid()) {
            return;
        }

        array_unshift($args, function (...$args) {
            return $this->traverseGenerator(...$args);
        });

        $current = $this->generator->current();

        // Pipeline 内部创建 [stage, method, params]
        if (is_array($current) && 3 === count($current)) {
            $params = array_pop($current);
        } else {
            $params = [];
        }

        return $current(...$args, ...$params);
    }

    /**
     * 工序迭代器
     * 添加一个空的迭代器，第一次迭代 next 自动移除.
     *
     * @param array $stage
     *
     * @return \Generator
     */
    protected function stageGenerator(array $stage): Generator
    {
        array_unshift($stage, null);

        foreach ($stage as $item) {
            yield $this->stageCallback($item);
        }
    }

    /**
     * 工序回调.
     *
     * @param mixed $stages
     *
     * @return null|callable
     */
    protected function stageCallback($stages)
    {
        if (null === $stages) {
            return;
        }

        if (is_callable($stages)) {
            return $stages;
        }

        list($stage, $params) = $this->parse($stages);

        if (false !== strpos($stage, '@')) {
            list($stage, $method) = explode('@', $stage);
        } else {
            $method = 'handle';
        }

        if (!is_object($stage = $this->container->make($stage))) {
            throw new InvalidArgumentException('Stage is invalid.');
        }

        return [$stage, $method, $params];
    }

    /**
     * 解析工序.
     *
     * @param string $stage
     *
     * @return array
     */
    protected function parse(string $stage): array
    {
        list($name, $args) = array_pad(explode(':', $stage, 2), 2, []);

        if (is_string($args)) {
            $args = explode(',', $args);
        }

        $args = array_map(function (string $item) {
            return ctype_digit($item) ? (int) $item : $item;
        }, $args);

        return [$name, $args];
    }
}