<?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\Validate;

use DateTime;
use Exception;
use InvalidArgumentException;

/**
 * 日期验证.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.11.26
 *
 * @version 1.0
 */
trait Date
{
    /**
     * 数据验证器.
     *
     * @var \Leevel\Validate\IValidator
     */
    protected $validator;

    /**
     * 校验日期.
     *
     * @param mixed                       $value
     * @param array                       $parameter
     * @param \Leevel\Validate\IValidator $validator
     * @param string                      $field
     * @param bool                        $before
     *
     * @return bool
     */
    public function validateDate($value, array $parameter, IValidator $validator, string $field, bool $before = false): bool
    {
        if (!is_string($value)) {
            return false;
        }

        if (!array_key_exists(0, $parameter)) {
            throw new InvalidArgumentException('Missing the first element of parameter.');
        }

        $this->validator = $validator;

        if ($format = $this->getDateFormat($field)) {
            return $this->doWithFormat($format, $value, $parameter, $before);
        }

        if (!($time = strtotime($parameter[0]))) {
            if (null === ($_ = $validator->getFieldValue($parameter[0]))) {
                return false;
            }

            return $this->compareTime(strtotime($value), strtotime($_), $before);
        }

        return $this->compareTime(strtotime($value), $time, $before);
    }

    /**
     * 比较时间.
     *
     * @param int  $left
     * @param int  $right
     * @param bool $before
     *
     * @return bool
     */
    protected function compareTime(int $left, int $right, bool $before = false): bool
    {
        if (true === $before) {
            return $left < $right;
        }

        return $left > $right;
    }

    /**
     * 获取时间格式化.
     *
     * @param string $field
     *
     * @return string|void
     */
    protected function getDateFormat(string $field)
    {
        if ($result = $this->validator->getParseRule($field, 'date_format')) {
            return $result[1][0];
        }
    }

    /**
     * 验证在给定日期之后.
     *
     * @param string $format
     * @param mixed  $value
     * @param array  $parameter
     * @param bool   $before
     *
     * @return bool
     */
    protected function doWithFormat(string $format, $value, array $parameter, bool $before = false): bool
    {
        $parameter[0] = $this->validator->getFieldValue($parameter[0]) ?: $parameter[0];

        if (true === $before) {
            list($parameter[0], $value) = [$value, $parameter[0]];
        }

        return $this->doCheckDate($format, $parameter[0], $value);
    }

    /**
     * 验证日期顺序.
     *
     * @param string $format
     * @param string $first
     * @param string $second
     *
     * @return bool
     */
    protected function doCheckDate(string $format, string $first, string $second): bool
    {
        $before = $this->makeDateTimeFormat($format, $first);
        $after = $this->makeDateTimeFormat($format, $second);

        return $before && $after && $before < $after;
    }

    /**
     * 创建 DateTime 实例.
     *
     * @param string $format
     * @param string $value
     *
     * @return \DateTime|void
     */
    protected function makeDateTimeFormat(string $format, string $value)
    {
        $date = DateTime::createFromFormat($format, $value);

        if ($date) {
            return $date;
        }

        try {
            return new DateTime($value);
        } catch (Exception $e) {
        }
    }
}


<?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;

use Leevel\Cache\Redis\PhpRedis;
use Leevel\Manager\Manager as Managers;

/**
 * 缓存入口.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.02.15
 *
 * @version 1.0
 */
class Manager extends Managers
{
    /**
     * 取得配置命名空间.
     *
     * @return string
     */
    protected function normalizeOptionNamespace(): string
    {
        return 'cache';
    }

    /**
     * 创建连接对象
     *
     * @param object $connect
     *
     * @return object
     */
    protected function createConnect(object $connect): object
    {
        return new Cache($connect);
    }

    /**
     * 创建文件缓存.
     *
     * @param array $options
     *
     * @return \Leevel\Cache\File
     */
    protected function makeConnectFile(array $options = []): File
    {
        return new File(
            $this->normalizeConnectOption('file', $options)
        );
    }

    /**
     * 创建 redis 缓存.
     *
     * @param array $options
     *
     * @return \Leevel\Cache\Redis
     */
    protected function makeConnectRedis(array $options = []): Redis
    {
        $options = $this->normalizeConnectOption('redis', $options);

        return new Redis(new PhpRedis($options), $options);
    }

    /**
     * 分析连接配置.
     *
     * @param string $connect
     *
     * @return array
     */
    protected function getConnectOption(string $connect): array
    {
        return $this->filterNullOfOption(
            parent::getConnectOption($connect)
        );
    }
}


<?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;

use InvalidArgumentException;
use Leevel\Filesystem\Fso\create_file;
use function Leevel\Filesystem\Fso\create_file;
use Leevel\Stack\Stack;

/**
 * 分析模板
 * This class borrows heavily from the JeCat Framework and is part of the JeCat package.
 * 模板引擎分析器和编译器实现技术原理来源于 Jecat 框架.
 * 一款无与伦比的技术大餐，有幸在 2010 接触到这个框架，通过这个框架学到了很多.
 * 它的模板引擎实现了可以将 GLADE3 的 xml 文件编译成 PHP-Gtk 的组件，也支持 Html 编译，非常震撼.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.19
 *
 * @version 1.0
 *
 * @see http://jecat.cn
 */
class Parser implements IParser
{
    /**
     * 编译器.
     *
     * @var \Leevel\View\ICompiler
     */
    protected $compiler;

    /**
     * 成对节点栈.
     *
     * @var \Leevel\Stack\Stack
     */
    protected $nodeStack;

    /**
     * js 风格 和 node 共用分析器.
     *
     * @var bool
     */
    protected $jsNode = false;

    /**
     * 编译器.
     *
     * @var array
     */
    protected $compilers = [];

    /**
     * 分析器.
     *
     * @var array
     */
    protected $parses = [];

    /**
     * 分析器定界符.
     *
     * @var array
     */
    protected $tags = [
        // 全局
        'global' => [
            'left'  => '[<\{]',
            'right' => '[\}>]',
        ],

        // js 风格代码
        'js' => [
            'left'  => '{%',
            'right' => '%}',
        ],

        // js 风格变量代码
        'jsvar' => [
            'left'  => '{{',
            'right' => '}}',
        ],

        // 代码
        'code' => [
            'left'  => '{',
            'right' => '}',
        ],

        // 节点
        'node' => [
            'left'  => '<',
            'right' => '>',
        ],

        // 反向
        'revert' => [],

        // 全局反向
        'globalrevert' => [],
    ];

    /**
     * 模板树结构.
     *
     * @var array
     */
    protected $themeTree = [];

    /**
     * 模板项结构.
     *
     * @var array
     */
    protected static $themeStruct = [
        // 原模板
        'source'  => '',
        'content' => '',

        // 编译器
        'compiler' => null,
        'children' => [],
        'position' => [],
    ];

    /**
     * 当前编译源文件.
     *
     * @var string
     */
    protected $sourceFile;

    /**
     * 当前编译缓存文件.
     *
     * @var string
     */
    protected $cachePath;

    /**
     * 构造函数.
     *
     * @param \Leevel\View\ICompiler $compiler
     */
    public function __construct(ICompiler $compiler)
    {
        $this->compiler = $compiler;
    }

    /**
     * 注册视图编译器.
     *
     * @return $this
     */
    public function registerCompilers(): IParser
    {
        foreach ($this->compiler->getCompilers() as $compiler) {
            foreach ((array) $compiler[1] as $name) {
                $this->registerCompiler($compiler[0], $name, $compiler[2]);
            }
        }

        return $this;
    }

    /**
     * 注册视图分析器.
     *
     * @return $this
     */
    public function registerParsers(): IParser
    {
        foreach ($this->tags as $key => $value) {
            $this->registerParser($key);
        }

        return $this;
    }

    /**
     * 执行编译.
     *
     * @param string      $file
     * @param null|string $cachePath
     * @param bool        $isContent
     *
     * @return string|void
     */
    public function doCompile(string $file, ?string $cachePath = null, bool $isContent = false)
    {
        // 源码
        if (false === $isContent) {
            if (!is_file($file)) {
                throw new InvalidArgumentException(
                    sprintf('File %s is not exits.', $file)
                );
            }

            $cache = file_get_contents($file);

            $this->sourceFile = $file;
            $this->cachePath = $cachePath;
        } else {
            $cache = $file;
        }

        // 逐个载入分析器编译模板
        foreach ($this->parses as $parser) {
            // 清理对象 & 构建顶层树对象
            $this->clearThemeTree();

            $theme = [
                'source'   => $cache,
                'content'  => $cache,
                'position' => $this->getPosition($cache, '', 0),
            ];

            $theme = $this->normalizeThemeStruct($theme);
            $this->topTheme($theme);

            // 分析模板生成模板树
            $parser = $parser.'Parse';

            // 分析
            $this->{$parser}($cache);

            // 编译模板树
            $cache = $this->compileThemeTree();
        }

        // 生成编译文件
        if (null !== $cachePath) {
            $this->makeCacheFile($cachePath, $cache);
        } else {
            return $cache;
        }
    }

    /**
     * code 编译编码，后还原
     *
     * @param string $content
     *
     * @return string
     */
    public static function revertEncode(string $content): string
    {
        $rand = rand(1000000, 9999999);

        return "__##revert##START##{$rand}@".
            base64_encode($content).
            '##END##revert##__';
    }

    /**
     * tagself 编译编码，后还原
     *
     * @param string $content
     *
     * @return string
     */
    public static function globalEncode(string $content): string
    {
        $rand = rand(1000000, 9999999);

        return "__##global##START##{$rand}@".
            base64_encode($content).
            '##END##global##__';
    }

    /**
     * 全局编译器 tagself.
     *
     * @param string $compiled
     */
    protected function globalParse(string &$compiled): void
    {
        $tag = $this->getTag('global');

        if (preg_match_all(
            "/{$tag['left']}tagself{$tag['right']}(.+?){$tag['left']}\\/tagself{$tag['right']}/isx",
            $compiled, $res)) {
            $startPos = 0;

            foreach ($res[1] as $index => $encode) {
                $source = trim($res[0][$index]);
                $content = trim($res[1][$index]);

                $theme = [
                    'source'   => $source,
                    'content'  => $content,
                    'compiler' => 'global', // 编译器
                    'children' => [],
                ];

                $theme['position'] = $this->getPosition($compiled, $source, $startPos);
                $startPos = $theme['position']['end'] + 1;
                $theme = $this->normalizeThemeStruct($theme);

                // 将模板数据加入到树结构中
                $this->addTheme($theme);
            }
        }
    }

    /**
     * js 风格 变量分析器.
     *
     * @param string $compiled
     */
    protected function jsvarParse(string &$compiled): void
    {
        $tag = $this->getTag('jsvar');

        if (preg_match_all("/{$tag['left']}(.+?){$tag['right']}/isx",
            $compiled, $res)) {
            $startPos = 0;

            foreach ($res[1] as $index => $encode) {
                $source = trim($res[0][$index]);
                $content = trim($res[1][$index]);

                $theme = [
                    'source'   => $source,
                    'content'  => $content,
                    'compiler' => 'jsvar', // 编译器
                    'children' => [],
                ];

                $theme['position'] = $this->getPosition($compiled, $source, $startPos);
                $startPos = $theme['position']['end'] + 1;
                $theme = $this->normalizeThemeStruct($theme);

                // 将模板数据加入到树结构中
                $this->addTheme($theme);
            }
        }
    }

    /**
     * code 方式分析器.
     *
     * @param string $compiled
     */
    protected function codeParse(string &$compiled): void
    {
        foreach ($this->compilers['code'] as $compilers => $tag) {
            // 处理一些正则表达式中有特殊意义的符号
            $names[] = $this->escapeRegexCharacter($compilers);
        }

        // 正则分析
        $tag = $this->getTag('code');
        $names = implode('|', $names);
        $regex = '/'.$tag['left']."\\s*({$names})(|.+?)".$tag['right'].'/s';

        if (preg_match_all($regex, $compiled, $res)) {
            $startPos = 0;

            foreach ($res[0] as $index => &$source) {
                $type = trim($res[1][$index]);
                !$type && $type = '/';

                $content = trim($res[2][$index]);

                $theme = [
                    'source'   => $source,
                    'content'  => $content,
                    'compiler' => $this->compilers['code'][$type].'Code', // 编译器
                    'children' => [],
                ];

                $theme['position'] = $this->getPosition($compiled, $source, $startPos);
                $startPos = $theme['position']['end'] + 1;
                $theme = $this->normalizeThemeStruct($theme);

                // 将模板数据加入到树结构中
                $this->addTheme($theme);
            }
        }
    }

    /**
     * js 风格分析器 与 node 公用分析器.
     *
     * @param string $compiled
     */
    protected function jsParse(string &$compiled): void
    {
        $this->jsNode = true;

        $this->normalizeNodeParse($compiled);
    }

    /**
     * node 分析器.
     *
     * @param string $compiled
     */
    protected function nodeParse(string &$compiled): void
    {
        $this->jsNode = false;

        $this->normalizeNodeParse($compiled);
    }

    /**
     * 格式化 node 分析器.
     *
     * @param string $compiled
     */
    protected function normalizeNodeParse(string &$compiled): void
    {
        // 查找分析 Node 的标签
        $this->findNodeTag($compiled);

        // 用标签组装 Node
        $this->packNode($compiled);
    }

    /**
     * code 还原分析器.
     *
     * @param string $compiled
     */
    protected function revertParse(string &$compiled): void
    {
        if (preg_match_all(
            '/__##revert##START##\d+@(.+?)##END##revert##__/',
            $compiled, $res)) {
            $startPos = 0;

            foreach ($res[1] as $index => $encode) {
                $source = $res[0][$index];

                $theme = [
                    'source'   => $source,
                    'content'  => $encode,
                    'compiler' => 'revert', // 编译器
                    'children' => [],
                ];

                $theme['position'] = $this->getPosition($compiled, $source, $startPos);
                $startPos = $theme['position']['end'] + 1;
                $theme = $this->normalizeThemeStruct($theme);

                // 将模板数据加入到树结构中
                $this->addTheme($theme);
            }
        }
    }

    /**
     * tagself 还原分析器.
     *
     * @param string $compiled
     */
    protected function globalrevertParse(string &$compiled): void
    {
        if (preg_match_all(
            '/__##global##START##\d+@(.+?)##END##global##__/',
            $compiled, $res)) {
            $startPos = 0;

            foreach ($res[1] as $index => $encode) {
                $source = $res[0][$index];
                $content = $res[1][$index];

                $theme = [
                    'source'   => $source,
                    'content'  => $content,
                    'compiler' => 'globalrevert', // 编译器
                    'children' => [],
                ];

                $theme['position'] = $this->getPosition($compiled, $source, $startPos);
                $startPos = $theme['position']['end'] + 1;
                $theme = $this->normalizeThemeStruct($theme);

                // 将模板数据加入到树结构中
                $this->addTheme($theme);
            }
        }
    }

    /**
     * 查找成对节点.
     *
     * @param string $compiled
     */
    protected function findNodeTag(string &$compiled): void
    {
        // 设置一个栈
        $this->nodeStack = new Stack(['array']);

        // 判断是那种编译器
        $nodeType = true === $this->jsNode ? 'js' : 'node';

        // 所有一级节点名称
        foreach ($this->compilers[$nodeType] as $compilers => $tag) {
            // 处理一些正则表达式中有特殊意义的符号
            $names[] = $this->escapeRegexCharacter($compilers);
        }

        // 正则分析
        $tag = $this->getTag($nodeType);
        $names = implode('|', $names);
        $regex = "/{$tag['left']}\\s*(\\/?)(({$names})(:[^\\s".
            (true === $this->jsNode ? '' : '\\>').
            '\\}]+)?)(\\s[^'.
            (true === $this->jsNode ? '' : '>').
            "\\}]*?)?\\/?{$tag['right']}/isx";

        // 标签名称位置
        $nodeNameIndex = 2;

        // 标签顶级名称位置
        $nodeTopNameIndex = 3;

        // 尾标签斜线位置
        $tailSlasheIndex = 1;

        // 标签属性位置
        $tagAttributeIndex = 5;

        if (true === $this->jsNode) {
            $compiler = $this->compilers['js'];
        } else {
            $compiler = $this->compilers['node'];
        }

        // 依次创建标签对象
        if (preg_match_all($regex, $compiled, $res)) {
            $startPos = 0;

            foreach ($res[0] as $index => &$tagSource) {
                $nodeName = $res[$nodeNameIndex][$index];
                $nodeTopName = $res[$nodeTopNameIndex][$index];
                $nodeType = '/' === $res[$tailSlasheIndex][$index] ? 'tail' : 'head';

                // 将节点名称统一为小写
                $nodeName = strtolower($nodeName);
                $nodeTopName = strtolower($nodeTopName);

                $theme = [
                    'source' => $tagSource,
                    'name'   => $compiler[$nodeTopName],
                    'type'   => $nodeType,
                ];

                // 头标签的属性
                if ('head' === $nodeType) {
                    $theme['attribute'] = $res[$tagAttributeIndex][$index];
                } else {
                    $theme['attribute'] = '';
                }

                $theme['content'] = $theme['attribute'];
                $theme['position'] = $this->getPosition($compiled, $tagSource, $startPos);
                $startPos = $theme['position']['end'] + 1;
                $theme = $this->normalizeThemeStruct($theme);

                // 加入到标签栈
                $this->nodeStack->in($theme);
            }
        }
    }

    /**
     * 装配节点.
     *
     * @param string $compiled
     */
    protected function packNode(string &$compiled): void
    {
        if (true === $this->jsNode) {
            $nodeTag = $this->compiler->getJsTagHelp();
            $compiler = 'Js';
        } else {
            $nodeTag = $this->compiler->getNodeTagHelp();
            $compiler = 'Node';
        }

        // 尾标签栈
        $tailStack = new Stack(['array']);

        // 载入节点属性分析器 & 依次处理所有标签
        while (null !== ($tag = $this->nodeStack->out())) {
            // 尾标签，加入到尾标签中
            if ('tail' === $tag['type']) {
                $tailStack->in($tag);

                continue;
            }

            // 从尾标签栈取出一项
            $tailTag = $tailStack->out();

            // 单标签节点
            if (!$tailTag or !$this->findHeadTag($tag, $tailTag)) {
                if (true !== $nodeTag[$tag['name']]['single']) {
                    throw new InvalidArgumentException(
                        sprintf(
                            '%s type nodes must be used in pairs, and no corresponding tail tags are found.',
                            $tag['name']
                        ).
                        '<br />'.
                        $this->getLocation($tag['position'])
                    );
                }

                // 退回栈中
                if ($tailTag) {
                    $tailStack->in($tailTag);
                }

                $themeNode = [
                    'content'  => $tag['content'],
                    'compiler' => $tag['name'].$compiler, // 编译器
                    'source'   => $tag['source'],
                    'name'     => $tag['name'],
                ];

                $themeNode['position'] = $tag['position'];
                $themeNode = $this->normalizeThemeStruct($themeNode);
            }

            // 成对标签
            else {
                // 头尾标签中间为整个标签内容
                $start = $tag['position']['start'];
                $len = $tailTag['position']['end'] - $start + 1;
                $source = substr($compiled, $start, $len);

                $themeNode = [
                    'content'  => $source,
                    'compiler' => $tag['name'].$compiler, // 编译器
                    'source'   => $source,
                    'name'     => $tag['name'],
                ];

                $themeNode['position'] = $this->getPosition($compiled, $source, $start);
                $themeNode = $this->normalizeThemeStruct($themeNode);

                // 标签 body
                $start = $tag['position']['end'] + 1;
                $len = $tailTag['position']['start'] - $start;

                if ($len > 0) {
                    $body = substr($compiled, $start, $len);

                    $themeBody = [
                        'content'  => $body,
                        'compiler' => null, // 编译器
                        'source'   => $body,
                        'is_body'  => true,
                    ];

                    $themeBody['position'] = $this->getPosition($compiled, $body, $start);
                    $themeBody = $this->normalizeThemeStruct($themeBody);
                    $themeNode = $this->addThemeTree($themeNode, $themeBody);
                }
            }

            // 标签属性
            $themeAttr = [
                'content'        => $tag['content'],
                'compiler'       => 'attributeNode', // 编译器
                'source'         => $tag['source'],
                'attribute_list' => [],
                'is_attribute'   => true,
                'parent_name'    => $themeNode['name'],
                'is_js'          => $this->jsNode,
            ];

            $themeAttr['position'] = $this->getPosition($compiled, $tag['source'], 0);
            $themeAttr = $this->normalizeThemeStruct($themeAttr);
            $themeNode = $this->addThemeTree($themeNode, $themeAttr);

            // 将模板数据加入到树结构中
            $this->addTheme($themeNode);
        }
    }

    /**
     * 查找 node 标签.
     *
     * @param array $tag
     * @param array $tailTag
     *
     * @return bool
     */
    protected function findHeadTag(array $tag, array $tailTag): bool
    {
        return preg_match("/^{$tailTag['name']}/i", $tag['name']) > 0;
    }

    /**
     * 注册分析器.
     *
     * @param string $tag
     */
    protected function registerParser(string $tag): void
    {
        $this->parses[] = $tag;
    }

    /**
     * 注册编译器 code 和 node 编译器注册.
     *
     * @param string $type
     * @param string $name
     * @param string $tag
     */
    protected function registerCompiler(string $type, string $name, string $tag): void
    {
        $this->compilers[$type][$name] = $tag;
    }

    /**
     * 逐个编译模板树.
     *
     * @return string
     */
    protected function compileThemeTree(): string
    {
        $cache = '';

        foreach ($this->themeTree as $theme) {
            $this->compileTheme($theme);
            $cache .= $theme['content'];
        }

        return $cache;
    }

    /**
     * 分析模板调用编译器编译.
     *
     * @param array $theme
     */
    protected function compileTheme(array &$theme): void
    {
        foreach ($theme['children'] as $key => $value) {
            $source = $value['source'];

            // 编译子对象
            $this->compileTheme($value);
            $theme['children'][$key] = $value;

            // 置换对象
            $start = strpos($theme['content'], $source);
            $len = $value['position']['end'] - $value['position']['start'] + 1;

            $theme['content'] = substr_replace(
                $theme['content'], $value['content'],
                $start, $len);
        }

        // 编译自身
        if ($theme['compiler']) {
            $compilers = $theme['compiler'].'Compiler';
            $this->compiler->{$compilers}($theme);
        }
    }

    /**
     * 创建缓存文件.
     *
     * @param string $cachePath
     * @param string $compiled
     */
    protected function makeCacheFile(string $cachePath, string &$compiled): void
    {
        $content = '<?'.'php /* '.date('Y-m-d H:i:s').
            ' */ ?'.'>'.PHP_EOL.$compiled;

        create_file($cachePath, $content);
    }

    /**
     * 取得模板分析器定界符.
     *
     * @param string $type
     *
     * @return array
     */
    protected function getTag(string $type): array
    {
        return $this->tags[$type];
    }

    /**
     * 将模板结构加入树结构中去.
     *
     * @param array $theme
     */
    protected function addTheme(array $theme): void
    {
        $top = &$this->themeTree[0];
        $top = $this->addThemeTree($top, $theme);
    }

    /**
     * 清理模板树对象.
     */
    protected function clearThemeTree(): void
    {
        $this->themeTree = [];
    }

    /**
     * 添加顶层树对象
     *
     * @param array $theme
     */
    protected function topTheme(array $theme): void
    {
        $this->themeTree[] = $theme;
    }

    /**
     * 将新的模板加入到树结构中去.
     *
     * @param array $top
     * @param array $new
     *
     * @return array
     */
    protected function addThemeTree(array $top, array $new): array
    {
        $result = [];

        foreach ($top['children'] as $child) {
            if ($new) {
                $relative = $this->positionRelative($new['position'], $child['position']);

                switch ($relative) {
                    /*
                     * 新增的和上次处于平级关系直接加入上级的 children 容器中
                     * new 在前 child 在后面
                     */
                    case 'front':
                        $result[] = $new;
                        $result[] = $child;
                        $new = null;

                        break;
                    /*
                     * 新增的和上次处于平级关系直接加入上级的 children 容器中
                     * child 在前 new 在后面
                     */
                    case 'behind':
                        $result[] = $child;

                        break;
                    /*
                     * new 处于 child 内部
                     * new 在 child 内部
                     */
                    case 'in':
                        $child = $this->addThemeTree($child, $new);
                        $result[] = $child;
                        $new = null;

                        break;
                    /*
                     * child 处于 new 内部
                     * child 在 new 内部
                     */
                    case 'out':
                        $new = $this->addThemeTree($new, $child);

                        break;
                }
            } else {
                $result[] = $child;
            }
        }

        if ($new) {
            $result[] = $new;
        }

        $top['children'] = $result;

        return $top;
    }

    /**
     * 分析匹配标签的位置.
     *
     * @param string $content 待编译的模板
     * @param string $find    匹配的标签
     * @param int    $start   起始查找的位置
     *
     * @return array start 标签开始的位置（字节数）
     * @note int end 标签结束的位置（字节数）
     * @notenote int start_line 标签开始的行（行数）
     * @note int end_line 标签结束的行（行数）
     * @note int start_in 标签开始的所在的行的起始字节数
     * @note int end_in 标签结束的所在的行的起始字节数
     */
    protected function getPosition(string $content, string $find, int $start): array
    {
        /*
         *
         * ======= start =======
         *
         * {tagself}
         * yes
         * {/tagself}
         *
         * ======== end =======
         *
         * Array
         * (
         * [start] => 27
         * [end] => 64
         * [start_line] => 2
         * [end_line] => 4
         * [start_in] => 2
         * [end_in] => 17
         * )
         */
        $data = [];

        // 空
        if (empty($find)) {
            $data['start'] = -1;
            $data['end'] = -1;
            $data['start_line'] = -1;
            $data['end_line'] = -1;
            $data['start_in'] = -1;
            $data['end_in'] = -1;

            return $data;
        }

        $total = strlen($content);

        // 起止字节位置
        $start = strpos($content, $find, $start);
        $end = $start + strlen($find) - 1;

        // 起止行数
        $startLine = $start <= 0 ? 0 : substr_count($content, "\n", 0, $start);
        $endLine = $end <= 0 ? 0 : substr_count($content, "\n", 0, $end);

        /**
         * 匹配模块范围圈（在这个字节里面的都是它的子模快）
         * 找到开始和结束的地方就确定了这个模块所在区域范围.
         */

        // 起点的行首换行位置 && 结束点的行首位置
        $lineStartFirst = strrpos(substr($content, 0, $start), "\n") + 1;
        $lineEndFirst = strrpos(substr($content, 0, $end), "\n") + 1;
        $startIn = $start - $lineStartFirst;
        $endIn = $end - $lineEndFirst;

        // 返回结果
        $data['start'] = $start;
        $data['end'] = $end;
        $data['start_line'] = $startLine;
        $data['end_line'] = $endLine;
        $data['start_in'] = $startIn;
        $data['end_in'] = $endIn;

        return $data;
    }

    /**
     * 对比两个模板相对位置
     * 这个和两个时间段之间的关系一样，其中交叉在模板引擎中是不被支持，因为无法实现
     * 除掉交叉，剩下包含、被包含、前面和后面，通过位置组装成一颗树结构.
     *
     * @param array $value
     * @param array $beyond
     *
     * @return string
     * @note string front 第一个在第二个前面
     * @note string behind 第一个在第二个后面
     * @note string in 第一个在第二里面，成为它的子模板
     * @note string out 第一个在第一个里面，成为它的子模板
     */
    protected function positionRelative(array $value, array $beyond): string
    {
        /*
         * 第一个匹配的标签在第二个前面
         * 条件：第一个结束字节位置 <= 第二个开始位置
         */

        /*
         * ======= start =======
         *
         * {if}
         * value
         * {/if}
         *
         * {for}
         * beyond
         * {/for}
         *
         * ======== end =======
         */
        if ($value['end'] <= $beyond['start']) {
            return 'front';
        }

        /*
         * 第一个匹配的标签在第二个后面
         * 条件：第一个开始字节位置 >= 第二个结束位置
         */

        /*
         * ======= start =======
         *
         * {for}
         * beyond
         * {/for}
         *
         * {if}
         * value
         * {/if}
         *
         * ======== end =======
         */
        if ($value['start'] >= $beyond['end']) {
            return 'behind';
        }

        /*
         * 第一个匹配的标签在第二个里面
         * 条件：第一个开始字节位置 >= 第二个开始位置
         */

        /*
         * ======= start =======
         *
         * {for}
         * beyond
         *
         * {if}
         * value
         * {/if}
         *
         * {/for}
         *
         * ======== end =======
         */
        if ($value['start'] >= $beyond['start'] &&
            $value['end'] <= $beyond['end']) {
            return 'in';
        }

        /*
         * 第一个匹配的标签在第二个外面
         * 条件：第一个开始字节位置 <= 第二个开始位置
         */

        /*
         * ======= start =======
         *
         * {if}
         * value
         *
         * {for}
         * beyond
         * {/for}
         *
         * {/if}
         *
         * ======== end =======
         */
        if ($value['start'] <= $beyond['start'] &&
            $value['end'] >= $beyond['end']) {
            return 'out';
        }

        // 交叉（两个时间段相互关系）
        throw new InvalidArgumentException(
            'Template engine tag library does not support cross.'
        );
    }

    /**
     * 整理模板项结构.
     *
     * @param array $theme
     *
     * @return array
     */
    protected function normalizeThemeStruct(array $theme): array
    {
        return array_merge(self::$themeStruct, $theme);
    }

    /**
     * 转义正则表达式特殊字符.
     *
     * @param string $txt
     *
     * @return string
     */
    protected function escapeRegexCharacter(string $txt): string
    {
        return preg_quote($txt, '/');
    }

    /**
     * 取得模板位置.
     *
     * @param array $position
     *
     * @return string
     */
    protected function getLocation(array $position): string
    {
        return sprintf(
                'Line:%s; column:%s; file:%s.',
                $position['start_line'],
                $position['start_in'],
                $this->sourceFile ?: null
            ).
            ($this->sourceFile ? $this->getLocationSource($position) : null);
    }

    /**
     * 取得模板位置源码
     *
     * @param array $position
     *
     * @return string
     */
    protected function getLocationSource(array $position): string
    {
        $line = explode(
            PHP_EOL,
            htmlentities(
                substr(
                    file_get_contents($this->sourceFile),
                    $position['start'],
                    $position['end']
                )
            )
        );

        $line[] = '<div class="template-key">'.
            array_pop($line).
            '</div>';

        return '<pre><code>'.
            implode(PHP_EOL, $line).
            '</code></pre>';
    }
}

// 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\Log\Middleware;

use Closure;
use Leevel\Http\IRequest;
use Leevel\Http\IResponse;
use Leevel\Log\Manager;

/**
 * Log 中间件.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.11.14
 *
 * @version 1.0
 */
class Log
{
    /**
     * log 管理.
     *
     * @var \Leevel\Log\Manager
     */
    protected $manager;

    /**
     * 构造函数.
     *
     * @param \Leevel\Log\Manager $manager
     */
    public function __construct(Manager $manager)
    {
        $this->manager = $manager;
    }

    /**
     * 响应.
     *
     * @param \Closure               $next
     * @param \Leevel\Http\IRequest  $request
     * @param \Leevel\Http\IResponse $response
     */
    public function terminate(Closure $next, IRequest $request, IResponse $response): void
    {
        $this->saveLog();

        $next($request, $response);
    }

    /**
     * 保存日志.
     */
    protected function saveLog(): void
    {
        $this->manager->flush();
    }
}


<?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\ZipArchive\ZipArchiveAdapter;

/**
 * filesystem.zip.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.08.29
 * @see https://flysystem.thephpleague.com/adapter/zip-archive/
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
class Zip extends Connect implements IConnect
{
    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'path' => '',
    ];

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

        if (!class_exists('League\Flysystem\ZipArchive\ZipArchiveAdapter')) {
            throw new InvalidArgumentException(
                'Please run composer require league/flysystem-ziparchive.'
            );
        }

        return new ZipArchiveAdapter($this->option['path']);
    }
}


<?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;

use Leevel\View\IView as IViews;

/**
 * 视图.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.19
 *
 * @version 1.0
 */
class View implements IView
{
    /**
     * 视图模板
     *
     * @var \Leevel\View\IView
     */
    protected $theme;

    /**
     * 构造函数.
     *
     * @param \Leevel\View\IView $theme
     */
    public function __construct(IViews $theme)
    {
        $this->theme = $theme;
    }

    /**
     * 切换视图.
     *
     * @param \Leevel\View\IView $theme
     *
     * @return $this
     */
    public function switchView(IViews $theme): IView
    {
        $assign = $this->getAssign();

        $this->theme = $theme;
        $this->assign($assign);

        return $this;
    }

    /**
     * 变量赋值
     *
     * @param mixed $name
     * @param mixed $value
     *
     * @return $this
     */
    public function assign($name, $value = null): IView
    {
        $this->theme->setVar($name, $value);

        return $this;
    }

    /**
     * 获取变量赋值.
     *
     * @param null|string $name
     *
     * @return mixed
     */
    public function getAssign(?string $name = null)
    {
        return $this->theme->getVar($name);
    }

    /**
     * 删除变量值.
     *
     * @param array $name
     *
     * @return $this
     */
    public function deleteAssign(array $name): IView
    {
        $this->theme->deleteVar($name);

        return $this;
    }

    /**
     * 清空变量值.
     *
     * @param null|string $name
     *
     * @return $this
     */
    public function clearAssign(): IView
    {
        $this->theme->clearVar();

        return $this;
    }

    /**
     * 加载视图文件.
     *
     * @param string $file
     * @param array  $vars
     * @param string $ext
     *
     * @return string
     */
    public function display(string $file, array $vars = [], ?string $ext = null): string
    {
        return $this->theme->display($file, $vars, $ext, false);
    }
}


<?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\Http;

use ArrayObject;
use InvalidArgumentException;
use JsonSerializable;
use Leevel\Support\IArray;
use Leevel\Support\IJson;

/**
 * JSON 响应请求
 * This class borrows heavily from the Symfony4 Framework and is part of the symfony package.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.02.27
 *
 * @version 1.0
 *
 * @see Symfony\Component\HttpFoundation (https://github.com/symfony/symfony)
 */
class JsonResponse extends Response
{
    /**
     * 默认 JSON 格式化参数.
     *
     * @var int
     */
    const DEFAULT_ENCODING_OPTIONS = 256;
    /**
     * 响应内容.
     *
     * @var sting
     */
    protected $data;

    /**
     * JSON 格式化参数.
     *
     * @var int
     */
    protected $encodingOptions = self::DEFAULT_ENCODING_OPTIONS;

    /**
     * JSONP 回调.
     *
     * @var string
     */
    protected $callback;

    /**
     * 构造函数.
     *
     * @param string $data
     * @param int    $status
     * @param array  $headers
     * @param bool   $json
     */
    public function __construct($data = null, int $status = 200, array $headers = [], bool $json = false)
    {
        parent::__construct('', $status, $headers);

        if (null === $data) {
            $data = new ArrayObject();
        }

        $json ? $this->setJson($data) : $this->setData($data);

        $this->isJson = true;
    }

    /**
     * 创建一个 JSON 响应.
     *
     * @param mixed $data
     * @param int   $status
     * @param array $headers
     *
     * @return static
     */
    public static function create($data = null, int $status = 200, array $headers = []): IResponse
    {
        return new static($data, $status, $headers);
    }

    /**
     * 从 JSON 字符串创建响应对象
     *
     * @param string $data
     * @param int    $status
     * @param array  $headers
     *
     * @return static
     */
    public static function fromJsonString(?string $data = null, $status = 200, $headers = []): IResponse
    {
        return new static($data, $status, $headers, true);
    }

    /**
     * 设置 JSONP 回调.
     *
     * @param null|string $callback
     *
     * @return $this
     */
    public function setCallback(?string $callback = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->callback = $callback;

        return $this->updateContent();
    }

    /**
     * 设置原生 JSON 数据.
     *
     * @param mixed $json
     *
     * @return $this
     */
    public function setJson($json): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        if (!$this->isJsonData($json)) {
            throw new InvalidArgumentException('The method setJson need a json data.');
        }

        $this->data = $json;

        return $this->updateContent();
    }

    /**
     * 设置数据作为 JSON.
     *
     * @param mixed $data
     * @param int   $encodingOptions
     *
     * @return $this
     */
    public function setData($data = [], ?int $encodingOptions = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        if (null !== $encodingOptions) {
            $this->encodingOptions = $encodingOptions;
        }

        if ($data instanceof IArray) {
            $this->data = json_encode($data->toArray(), $this->encodingOptions);
        } elseif (is_object($data) && $data instanceof IJson) {
            $this->data = $data->toJson($this->encodingOptions);
        } elseif (is_object($data) && $data instanceof JsonSerializable) {
            $this->data = json_encode($data->jsonSerialize(), $this->encodingOptions);
        } else {
            $this->data = json_encode($data, $this->encodingOptions);
        }

        if (JSON_ERROR_NONE !== json_last_error()) {
            throw new InvalidArgumentException(json_last_error_msg());
        }

        return $this->updateContent();
    }

    /**
     * 取回数据.
     *
     * @param bool $assoc
     * @param int  $depth
     *
     * @return mixed
     */
    public function getData(bool $assoc = true, int $depth = 512)
    {
        return json_decode($this->data, $assoc, $depth);
    }

    /**
     * 获取编码参数.
     *
     * @return int
     */
    public function getEncodingOptions(): int
    {
        return $this->encodingOptions;
    }

    /**
     * 设置编码参数.
     *
     * @param int $encodingOptions
     *
     * @return $this
     */
    public function setEncodingOptions(int $encodingOptions): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->encodingOptions = (int) $encodingOptions;

        return $this->setData($this->getData());
    }

    /**
     * 验证是否为正常的 JSON 字符串.
     *
     * @param mixed $data
     *
     * @return bool
     */
    protected function isJsonData($data): bool
    {
        if (!is_scalar($data) && !method_exists($data, '__toString')) {
            return false;
        }

        json_decode((string) ($data));

        return JSON_ERROR_NONE === json_last_error();
    }

    /**
     * 更新响应内容.
     *
     * @return $this
     */
    protected function updateContent(): IResponse
    {
        if (null !== $this->callback) {
            $this->headers->set('Content-Type', 'text/javascript');

            return $this->setContent(sprintf(';%s(%s);', $this->callback, $this->data));
        }

        if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) {
            $this->headers->set('Content-Type', 'application/json');
        }

        return $this->setContent($this->data);
    }
}


<?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\Console;

use function Leevel\Filesystem\Fso\create_file;
use Leevel\Filesystem\Fso\create_file;
use RuntimeException;

/**
 * 生成器基类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.05.02
 *
 * @version 1.0
 */
abstract class Make extends Command
{
    /**
     * 创建类型.
     *
     * @var string
     */
    protected $makeType;

    /**
     * 文件保存路径.
     *
     * @var string
     */
    protected $saveFilePath;

    /**
     * 模板路径.
     *
     * @var string
     */
    protected $templatePath;

    /**
     * 模板源码
     *
     * @var string
     */
    protected $templateSource;

    /**
     * 保存的模板结果.
     *
     * @var string
     */
    protected $templateResult;

    /**
     * 自定义替换.
     *
     * @var array
     */
    protected $customReplaceKeyValue = [];

    /**
     * 全局替换.
     *
     * @var array
     */
    protected static $globalReplace = [];

    /**
     * 命令空间.
     *
     * @var string
     */
    protected $namespace;

    /**
     * 设置全局变量替换.
     *
     * @param array $replace
     */
    public static function setGlobalReplace(array $replace): void
    {
        static::$globalReplace = $replace;
    }

    /**
     * 获取全局变量替换.
     *
     * @param array $replace
     *
     * @return array
     */
    public static function getGlobalReplace(): array
    {
        return static::$globalReplace;
    }

    /**
     * 创建文件.
     */
    protected function create(): void
    {
        // 替换模板变量
        $this->replaceTemplateSource();

        // 保存文件
        $this->saveTemplateResult();

        // 保存成功输出消息
        $this->info(sprintf('%s <%s> created successfully.', $this->getMakeType(), $this->argument('name')));
        $this->comment($this->formatFile($this->getSaveFilePath()));
    }

    /**
     * 替换模板变量.
     */
    protected function replaceTemplateSource(): void
    {
        // 解析模板源码
        $this->parseTemplateSource();

        // 获取替换变量
        $sourceAndReplace = $this->parseSourceAndReplace();

        // 第一次替换基本变量
        // 第二次替换基本变量中的变量
        $templateSource = str_replace($sourceAndReplace[0], $sourceAndReplace[1], $this->getTemplateSource());
        $this->templateResult = str_replace($sourceAndReplace[0], $sourceAndReplace[1], $templateSource);
    }

    /**
     * 保存模板.
     */
    protected function saveTemplateResult(): void
    {
        $saveFilePath = $this->getSaveFilePath();

        if (is_file($saveFilePath)) {
            $e = 'File is already exits.'.PHP_EOL.
                $this->formatFile($saveFilePath);

            throw new RuntimeException($e);
        }

        create_file($saveFilePath, $this->getTemplateResult());
    }

    /**
     * 获取模板编译结果.
     *
     * @return string
     */
    protected function getTemplateResult(): string
    {
        return $this->templateResult;
    }

    /**
     * 分析模板源码.
     */
    protected function parseTemplateSource(): void
    {
        $templateSource = $this->getTemplatePath();

        if (!is_file($templateSource)) {
            throw new RuntimeException(
                'Stub not found.'.PHP_EOL.
                $this->formatFile($templateSource)
            );
        }

        $this->templateSource = file_get_contents($templateSource);
    }

    /**
     * 获取模板源码
     *
     * @return string
     */
    protected function getTemplateSource(): string
    {
        return $this->templateSource;
    }

    /**
     * 分析变量替换.
     *
     * @return array
     */
    protected function parseSourceAndReplace(): array
    {
        $replaceKeyValue = array_merge(static::$globalReplace, $this->getDefaultReplaceKeyValue());

        $sourceKey = array_map(function ($item) {
            return '{{'.$item.'}}';
        }, array_keys($replaceKeyValue));

        $replace = array_values($replaceKeyValue);

        return [$sourceKey, $replace];
    }

    /**
     * 取得系统的替换变量.
     *
     * @return array
     */
    protected function getDefaultReplaceKeyValue(): array
    {
        $defaultReplace = [
            'namespace' => $this->getNamespace(),
            'date_y'    => date('Y'),
        ];

        return array_merge($defaultReplace, $this->getCustomReplaceKeyValue());
    }

    /**
     * 设置文件保存路径.
     *
     * @param string $saveFilePath
     */
    protected function setSaveFilePath(string $saveFilePath): void
    {
        $this->saveFilePath = $saveFilePath;
    }

    /**
     * 读取文件保存路径.
     *
     * @return string
     */
    protected function getSaveFilePath(): string
    {
        return $this->saveFilePath;
    }

    /**
     * 获取命名空间路径.
     *
     * @return string
     */
    protected function getNamespacePath(): string
    {
        $namespacePath = $this
            ->getContainer()
            ->namespacePath($this->getNamespace().'\\index', true).'/';

        return $namespacePath;
    }

    /**
     * 分析命名空间.
     */
    protected function parseNamespace(): void
    {
        $namespace = $this->option('namespace');

        if (empty($namespace)) {
            $namespace = 'app';
        }

        $namespace = ucfirst($namespace);

        $this->setNamespace($namespace);
    }

    /**
     * 设置命名空间.
     *
     * @param string $namespace
     */
    protected function setNamespace(string $namespace): void
    {
        $this->namespace = $namespace;
    }

    /**
     * 读取命名空间.
     *
     * @return string
     */
    protected function getNamespace(): ?string
    {
        return $this->namespace;
    }

    /**
     * 设置创建类型.
     *
     * @param string $makeType
     */
    protected function setMakeType(string $makeType): void
    {
        $this->makeType = $makeType;
    }

    /**
     * 读取创建类型.
     *
     * @return string
     */
    protected function getMakeType(): string
    {
        return $this->makeType;
    }

    /**
     * 设置模板文件路径.
     *
     * @param string $templatePath
     */
    protected function setTemplatePath(string $templatePath): void
    {
        $this->templatePath = $templatePath;
    }

    /**
     * 读取模板文件路径.
     *
     * @return string
     */
    protected function getTemplatePath(): string
    {
        return $this->templatePath;
    }

    /**
     * 设置自定义变量替换.
     *
     * @param mixed $key
     * @param mixed $value
     */
    protected function setCustomReplaceKeyValue($key, $value = null)
    {
        if (is_array($key)) {
            $this->customReplaceKeyValue = array_merge($this->customReplaceKeyValue, $key);
        } else {
            $this->customReplaceKeyValue[$key] = $value;
        }
    }

    /**
     * 读取自定义变量替换.
     *
     * @return array
     */
    protected function getCustomReplaceKeyValue(): array
    {
        return $this->customReplaceKeyValue;
    }

    /**
     * 格式化文件路径.
     *
     * @param string $file
     *
     * @return string
     */
    protected function formatFile(string $file): string
    {
        return $file;
    }
}

// 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\Protocol;

use Leevel\Di\ICoroutine;
use Swoole\Coroutine as SwooleCoroutine;

/**
 * 协程实现.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.12.14
 *
 * @version 1.0
 */
class Coroutine implements ICoroutine
{
    /**
     * 是否处于协程上下文.
     *
     * @param string $className
     *
     * @return bool
     */
    public function context(string $className): bool
    {
        if (!class_exists($className)) {
            return false;
        }

        if (method_exists($className, 'coroutineContext') &&
            true === $className::coroutineContext()) {
            return true;
        }

        return false;
    }

    /**
     * 当前协程 ID.
     *
     * @return int
     */
    public function uid(): int
    {
        return SwooleCoroutine::getuid();
    }
}


<?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 Closure;
use DebugBar\DataCollector\ConfigCollector;
use DebugBar\DataCollector\ExceptionsCollector;
use DebugBar\DataCollector\MemoryCollector;
use DebugBar\DataCollector\MessagesCollector;
use DebugBar\DataCollector\PhpInfoCollector;
use DebugBar\DataCollector\RequestDataCollector;
use DebugBar\DataCollector\TimeDataCollector;
use DebugBar\DebugBar;
use Exception;
use Leevel\Database\IConnect;
use Leevel\Debug\DataCollector\FilesCollector;
use Leevel\Debug\DataCollector\LeevelCollector;
use Leevel\Debug\DataCollector\LogsCollector;
use Leevel\Debug\DataCollector\SessionCollector;
use Leevel\Event\IDispatch;
use Leevel\Http\ApiResponse;
use Leevel\Http\IRequest;
use Leevel\Http\IResponse;
use Leevel\Http\JsonResponse;
use Leevel\Http\RedirectResponse;
use Leevel\Kernel\IApp;
use Leevel\Log\File;
use Leevel\Log\ILog;
use Throwable;

/**
 * 调试器.
 *
 * I actually copied a lot of ideas from laravel-debugbar app.
 *
 * @method void emergency($message)
 * @method void alert($message)
 * @method void critical($message)
 * @method void error($message)
 * @method void warning($message)
 * @method void notice($message)
 * @method void info($message)
 * @method void debug($message)
 * @method void log($message)
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.09.20
 *
 * @version 1.0
 */
class Debug extends DebugBar
{
    /**
     * IOC 容器.
     *
     * @var \Leevel\Kernel\IApp
     */
    protected $app;

    /**
     * 是否启用调试.
     *
     * @var bool
     */
    protected $enabled = true;

    /**
     * 是否已经初始化引导
     *
     * @var bool
     */
    protected $isBootstrap = false;

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'json'       => true,
        'console'    => true,
        'javascript' => true,
    ];

    /**
     * 构造函数.
     *
     * @param \Leevel\Kernel\IApp $app
     * @param array               $option
     */
    public function __construct(IApp $app, array $option = [])
    {
        $this->app = $app;

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

    /**
     * call.
     *
     * @param string $method
     * @param array  $args
     *
     * @return mixed
     */
    public function __call(string $method, array $args)
    {
        $messageLevels = [
            'emergency', 'alert', 'critical',
            'error', 'warning', 'notice',
            'info', 'debug', 'log',
        ];

        if (in_array($method, $messageLevels, true)) {
            foreach ($args as $arg) {
                $this->message($arg, $method);
            }
        }
    }

    /**
     * 返回应用管理.
     *
     * @return \Leevel\Kernel\IApp
     */
    public function getApp(): IApp
    {
        return $this->app;
    }

    /**
     * 设置配置.
     *
     * @param string $name
     * @param mixed  $value
     *
     * @return $this
     */
    public function setOption(string $name, $value): self
    {
        $this->option[$name] = $value;

        return $this;
    }

    /**
     * 响应.
     *
     * @param \Leevel\Http\IRequest  $request
     * @param \Leevel\Http\IResponse $response
     */
    public function handle(IRequest $request, IResponse $response): void
    {
        if (!$this->enabled) {
            return;
        }

        if (
                $request->isJson() ||
                $response instanceof ApiResponse ||
                $response instanceof JsonResponse ||
                $response->isJson()
            ) {
            if ($this->option['json'] && is_array($data = $response->getData())) {
                $jsonRenderer = $this->getJsonRenderer();

                if (array_values($data) !== $data) {
                    $data[':trace'] = $jsonRenderer->render();
                } else {
                    $data[] = [':trace' => $jsonRenderer->render()];
                }

                $response->setData($data);
            }
        } elseif (!($response instanceof RedirectResponse)) {
            if ($this->option['javascript']) {
                $javascriptRenderer = $this->getJavascriptRenderer('/debugbar');
                $response->appendContent(
                    $javascriptRenderer->renderHead().$javascriptRenderer->render()
                );
            }

            if ($this->option['console']) {
                $consoleRenderer = $this->getConsoleRenderer();
                $response->appendContent($consoleRenderer->render());
            }
        }
    }

    /**
     * 关闭调试.
     */
    public function disable(): void
    {
        $this->enabled = false;
    }

    /**
     * 启用调试.
     */
    public function enable(): void
    {
        $this->enabled = true;

        if (!$this->isBootstrap) {
            $this->bootstrap();
        }
    }

    /**
     * 添加一条消息.
     *
     * @param mixed  $message
     * @param string $label
     */
    public function message($message, string $label = 'info'): void
    {
        $this->getCollector('messages')->addMessage($message, $label);
    }

    /**
     * 开始调试时间.
     *
     * @param string $name
     * @param string $label
     */
    public function time(string $name, ?string $label = null): void
    {
        $this->getCollector('time')->startMeasure($name, $label);
    }

    /**
     * 停止调试时间.
     *
     * @param string $name
     */
    public function end(string $name): void
    {
        try {
            $this->getCollector('time')->stopMeasure($name);
        } catch (Exception $e) {
        }
    }

    /**
     * 添加一个时间调试.
     *
     * @param string $label
     * @param float  $start
     * @param float  $end
     */
    public function addTime(string $label, float $start, float $end): void
    {
        $this->getCollector('time')->addMeasure($label, $start, $end);
    }

    /**
     * 调试闭包执行时间.
     *
     * @param string   $label
     * @param \Closure $closure
     */
    public function closureTime(string $label, Closure $closure): void
    {
        $this->getCollector('time')->measure($label, $closure);
    }

    /**
     * 添加异常.
     *
     * @param \Throwable $e
     */
    public function exception(Throwable $e): void
    {
        $this->getCollector('exceptions')->addThrowable($e);
    }

    /**
     * 获取 JSON 渲染.
     */
    public function getJsonRenderer(): JsonRenderer
    {
        return new JsonRenderer($this);
    }

    /**
     * 获取 Console 渲染.
     */
    public function getConsoleRenderer(): ConsoleRenderer
    {
        return new ConsoleRenderer($this);
    }

    /**
     * {@inheritdoc}
     */
    public function getJavascriptRenderer($baseUrl = null, $basePath = null): JavascriptRenderer
    {
        return new JavascriptRenderer($this, $baseUrl, $basePath);
    }

    /**
     * 初始化.
     */
    public function bootstrap(): void
    {
        if ($this->isBootstrap) {
            return;
        }

        $this->addCollector(new PhpInfoCollector());
        $this->addCollector(new MessagesCollector());
        $this->addCollector(new RequestDataCollector());
        $this->addCollector(new TimeDataCollector());
        $this->addCollector(new MemoryCollector());
        $this->addCollector(new ExceptionsCollector());
        $this->addCollector(new ConfigCollector());
        $this->addCollector(new LeevelCollector($this->app));
        $this->addCollector(new SessionCollector($this->app->make('session')));
        $this->addCollector(new FilesCollector($this->app));
        $this->addCollector(new LogsCollector());

        $this->initData();
        $this->databaseEventDispatch();
        $this->logEventDispatch();

        $this->isBootstrap = true;
    }

    /**
     * 是否初始化.
     *
     * @return bool
     */
    public function isBootstrap(): bool
    {
        return $this->isBootstrap;
    }

    /**
     * 初始化数据.
     */
    protected function initData(): void
    {
        $this->message('Starts from this moment with QueryPHP.', '');

        $this->getCollector('config')->setData($this->app->make('option')->all());
    }

    /**
     * 设置数据库 SQL 监听器.
     */
    protected function databaseEventDispatch(): void
    {
        $this->getEventDispatch()->

        register(IConnect::SQL_EVENT, function (string $event, string $sql, array $bindParams = []) {
            $this->getCollector('logs')->addMessage($sql.': '.json_encode($bindParams, JSON_UNESCAPED_UNICODE), 'sql');
        });
    }

    /**
     * 设置日志监听器.
     */
    protected function logEventDispatch(): void
    {
        $this->getEventDispatch()->

        register(ILog::LOG_EVENT, function (string $event, string $level, string $message, array $context = []) {
            $this->getCollector('logs')->addMessage(File::formatMessage($level, $message, $context), $level);
        });
    }

    /**
     * 获取事件处理器.
     *
     * @return \Leevel\Event\IDispatch
     */
    protected function getEventDispatch(): IDispatch
    {
        return $this->app->make(IDispatch::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\Log\Console;

use Leevel\Console\Command;
use function Leevel\Filesystem\Fso\delete_directory;
use Leevel\Filesystem\Fso\delete_directory;
use Leevel\Kernel\IApp;

/**
 * log 文件缓存清理.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.09.04
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
class Clear extends Command
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'log:clear';

    /**
     * 命令行描述.
     *
     * @var string
     */
    protected $description = 'Clear cache of log.';

    /**
     * 响应命令.
     *
     * @param \Leevel\Kernel\IApp $app
     */
    public function handle(IApp $app): void
    {
        $this->line('Start to clear cache log.');

        delete_directory($cachePath = $app->runtimePath('log'), true);

        $this->info(sprintf('Log files in path %s cache clear successed.', $cachePath));
    }

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

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

// import fn.
class_exists(delete_directory::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\Kernel\Console;

use DirectoryIterator;
use InvalidArgumentException;
use Leevel\Console\Argument;
use Leevel\Console\Command;
use function Leevel\Filesystem\Fso\list_directory;
use Leevel\Filesystem\Fso\list_directory;
use Leevel\Kernel\Utils\ClassParser;
use Leevel\Kernel\Utils\Doc as UtilsDoc;

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

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

    /**
     * 响应命令.
     */
    public function handle(): void
    {
        $this->includeBootstrapFile();

        if (!($files = $this->parseFiles())) {
            throw new InvalidArgumentException('Files was not found.');
        }

        $this->utilsDoc = new UtilsDoc($this->outputDir());
        $this->classParser = new ClassParser();

        $succeedCount = 0;

        foreach ($files as $file) {
            if (true === $this->convertMarkdown($file)) {
                $succeedCount++;
            }
        }

        $this->info(sprintf('A total of <comment>%d</comment> files generate succeed.', $succeedCount));
    }

    /**
     * 生成 markdown 文件.
     *
     * @param string $file
     *
     * @return bool
     */
    protected function convertMarkdown(string $file): bool
    {
        $className = $this->classParser->handle($file);

        if (!class_exists($className)) {
            return false;
        }

        $result = $this->utilsDoc->handleAndSave($className);

        if (true === $result) {
            $this->line(sprintf('Class <info>%s</info> was generate succeed.', $className));
        }

        return $result;
    }

    /**
     * 分析测试用例文件.
     *
     * @return array
     */
    protected function parseFiles(): array
    {
        $fileOrDir = dirname($this->testsDir()).'/'.$this->path();

        $result = [];

        if (is_file($fileOrDir)) {
            $result[] = $fileOrDir;
        } elseif (is_dir($fileOrDir)) {
            list_directory($fileOrDir, true, function (DirectoryIterator $file) use (&$result) {
                if ($file->isFile()) {
                    $result[] = $file->getPathname();
                }
            });
        }

        return $result;
    }

    /**
     * 载入测试用例启动文件.
     */
    protected function includeBootstrapFile(): void
    {
        $bootstrap = $this->testsDir().'/bootstrap.php';

        if (is_file($bootstrap)) {
            include $bootstrap;
        }
    }

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

    /**
     * 取得测试用例基础目录.
     *
     * @return string
     */
    protected function testsDir(): string
    {
        return $this->argument('testsdir');
    }

    /**
     * 取得生成的 markdown 输出目录.
     *
     * @return string
     */
    protected function outputDir(): string
    {
        return $this->argument('outputdir');
    }

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

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

// import fn.
class_exists(list_directory::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\Database\Console;

use Leevel\Console\Argument;
use Leevel\Console\Make;
use Leevel\Console\Option;
use Leevel\Database\Manager;
use function Leevel\Support\Str\camelize;
use Leevel\Support\Str\camelize;
use function Leevel\Support\Str\un_camelize;
use Leevel\Support\Str\un_camelize;

/**
 * 生成模型实体.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.05.02
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
class Entity extends Make
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'make:entity';

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

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

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

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

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

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

          <info>php %command.full_name% name --table=test</info>
        EOF;

    /**
     * 数据库仓储.
     *
     * @var \Leevel\Database\Manager
     */
    protected $database;

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

        // 处理命名空间路径
        $this->parseNamespace();

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

        // 保存路径
        $this->setSaveFilePath(
            $this->getNamespacePath().
            'Domain/Entity/'.
            ucfirst(camelize($this->argument('name'))).'.php'
        );

        $this->setCustomReplaceKeyValue($this->getReplace());

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

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

    /**
     * 获取实体替换信息.
     *
     * @return array
     */
    protected function getReplace(): array
    {
        $columns = $this->getColumns();

        return [
            'file_name'        => ucfirst(camelize($this->argument('name'))),
            'table_name'       => $this->getTableName(),
            'primary_key'      => $this->getPrimaryKey($columns),
            'primary_key_type' => $this->getPrimaryKeyType($columns),
            'auto_increment'   => $this->getAutoIncrement($columns),
            'struct'           => $this->getStruct($columns),
            'props'            => $this->getProps($columns),
        ];
    }

    /**
     * 获取主键信息.
     *
     * @param array $columns
     *
     * @return string
     */
    protected function getPrimaryKey(array $columns): string
    {
        if (!$columns['primary_key']) {
            return 'null';
        }

        if (count($columns['primary_key']) > 1) {
            return '['.implode(', ', array_map(function ($item) {
                return "'{$item}'";
            }, $columns['primary_key'])).']';
        }

        return  "'{$columns['primary_key'][0]}'";
    }

    /**
     * 获取主键类型信息.
     *
     * @param array $columns
     *
     * @return string
     */
    protected function getPrimaryKeyType(array $columns): string
    {
        if (!$columns['primary_key']) {
            return 'string';
        }

        if (count($columns['primary_key']) > 1) {
            return 'array';
        }

        return 'string';
    }

    /**
     * 获取自增信息.
     *
     * @param array $columns
     *
     * @return string
     */
    protected function getAutoIncrement(array $columns): string
    {
        return $columns['auto_increment'] ? "'{$columns['auto_increment']}'" : 'null';
    }

    /**
     * 获取结构信息.
     *
     * @param array $columns
     *
     * @return string
     */
    protected function getStruct(array $columns): string
    {
        $struct = ['['];

        foreach ($columns['list'] as $val) {
            if ($val['primary_key']) {
                $struct[] = "        '{$val['name']}' => [
            'readonly' => true,
        ],";
            } else {
                $struct[] = "        '{$val['name']}' => [],";
            }
        }

        $struct[] = '    ]';

        return implode(PHP_EOL, $struct);
    }

    /**
     * 获取属性信息.
     *
     * @param array $columns
     *
     * @return string
     */
    protected function getProps(array $columns): string
    {
        $props = [];

        foreach ($columns['list'] as $val) {
            $comment = $val['comment'] ?: $val['name'];

            $propName = camelize($val['name']);

            $type = in_array($val['type'], ['tinyint', 'smallint', 'mediumint', 'int', 'integer', 'bigint'], true) ?
                'int' : 'string';

            $tmpProp = "
    /**
     * {$comment}.
     *
     * @var {$type}
     */
    private \${$propName};
";

            $props[] = $tmpProp;
        }

        return implode('', $props);
    }

    /**
     * 获取数据库表名字.
     *
     * @return string
     */
    protected function getTableName(): string
    {
        if ($this->option('table')) {
            return $this->option('table');
        }

        return un_camelize($this->argument('name'));
    }

    /**
     * 获取数据库表字段信息.
     *
     * @return array
     */
    protected function getColumns(): array
    {
        return $this->database->tableColumns($this->getTableName(), true);
    }

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

    /**
     * 命令配置.
     *
     * @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',
            ],
            [
                'table',
                null,
                Option::VALUE_OPTIONAL,
                'The database table of entity',
            ],
        ];
    }
}

// import fn.
class_exists(un_camelize::class);
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\Di;

use ArrayAccess;
use BadMethodCallException;
use Closure;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionParameter;

/**
 * IOC 容器.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.04.13
 *
 * @version 1.0
 */
class Container implements IContainer, ArrayAccess
{
    /**
     * 注册服务.
     *
     * @var array
     */
    protected $services = [];

    /**
     * 注册的实例.
     *
     * @var array
     */
    protected $instances = [];

    /**
     * 单一实例.
     *
     * @var array
     */
    protected $singletons = [];

    /**
     * 别名支持.
     *
     * @var array
     */
    protected $alias = [];

    /**
     * 协程.
     *
     * @var \Leevel\Di\ICoroutine
     */
    protected $coroutine;

    /**
     * 协程上下文注册的实例.
     *
     * @var array
     */
    protected $coroutineInstances = [];

    /**
     * 捕捉支持属性参数.
     *
     * @param string $key
     *
     * @return mixed
     */
    public function __get(string $key)
    {
        return $this[$key];
    }

    /**
     * 设置支持属性参数.
     *
     * @param string $key
     * @param mixed  $service
     */
    public function __set(string $key, $service): IContainer
    {
        $this[$key] = $service;

        return $this;
    }

    /**
     * call.
     *
     * @param string $method
     * @param array  $args
     *
     * @return mixed
     */
    public function __call(string $method, array $args)
    {
        $e = sprintf('Method `%s` is not exits.', $method);

        throw new BadMethodCallException($e);
    }

    /**
     * 注册到容器.
     *
     * @param mixed $name
     * @param mixed $service
     * @param bool  $share
     *
     * @return $this
     */
    public function bind($name, $service = null, bool $share = false): IContainer
    {
        if (is_array($name)) {
            list($name, $alias) = $this->parseAlias($name);
            $this->alias($name, $alias);
        }

        if (null === $service) {
            $service = $name;
        }

        $this->services[$name] = $service;

        if ($share) {
            $this->singletons[] = $name;
        }

        return $this;
    }

    /**
     * 注册为实例.
     *
     * @param mixed $name
     * @param mixed $service
     *
     * @return $this
     */
    public function instance($name, $service = null): IContainer
    {
        if (is_array($name)) {
            list($name, $alias) = $this->parseAlias($name);
            $this->alias($name, $alias);
        }

        if (null === $service) {
            $service = $name;
        }

        if ($this->coroutineContext($service)) {
            $this->coroutineInstances[$this->coroutineUid()][$name] = $service;
        } else {
            $this->instances[$name] = $service;
        }

        return $this;
    }

    /**
     * 注册单一实例.
     *
     * @param array|scalar $name
     * @param mixed        $service
     *
     * @return $this
     */
    public function singleton($name, $service = null): IContainer
    {
        return $this->bind($name, $service, true);
    }

    /**
     * 设置别名.
     *
     * @param array|string      $alias
     * @param null|array|string $value
     *
     * @return $this
     */
    public function alias($alias, $value = null): IContainer
    {
        if (is_array($alias)) {
            foreach ($alias as $key => $item) {
                if (is_int($key)) {
                    continue;
                }

                $this->alias($key, $item);
            }
        } else {
            $value = (array) $value;

            foreach ($value as $item) {
                $this->alias[$item] = $alias;
            }
        }

        return $this;
    }

    /**
     * 服务容器返回对象.
     *
     * @param string $name
     * @param array  $args
     *
     * @return mixed
     */
    public function make(string $name, array $args = [])
    {
        // 别名
        $name = $this->getAlias($name);

        // 存在直接返回
        if (isset($this->instances[$name])) {
            return $this->instances[$name];
        }

        if ($this->existsCoroutine($name)) {
            return $this->coroutineInstances[$this->coroutineUid()][$name];
        }

        // 生成实例
        if (!isset($this->services[$name])) {
            $instance = $this->getInjectionObject($name, $args);
        } else {
            if (!is_string($this->services[$name]) && is_callable($this->services[$name])) {
                array_unshift($args, $this);
                $instance = call_user_func_array($this->services[$name], $args);
            } else {
                if (is_string($this->services[$name])) {
                    $instance = $this->getInjectionObject($this->services[$name], $args);
                } else {
                    $instance = $this->services[$name];
                }
            }
        }

        // 单一实例
        if (in_array($name, $this->singletons, true)) {
            if ($this->coroutineContext($instance)) {
                return $this->coroutineInstances[$this->coroutineUid()][$name] = $instance;
            }

            return $this->instances[$name] = $instance;
        }

        // 多个实例

        return $instance;
    }

    /**
     * 实例回调自动注入.
     *
     * @param array|callable|string $callback
     * @param array                 $args
     *
     * @return mixed
     */
    public function call($callback, array $args = [])
    {
        $isStatic = false;

        if (is_string($callback)) {
            if (false !== strpos($callback, '@')) {
                $callback = explode('@', $callback);
            } elseif (false !== strpos($callback, '::')) {
                $callback = explode('::', $callback);
                $isStatic = true;
            }
        }

        if (false === $isStatic && is_array($callback)) {
            if (!is_object($callback[0])) {
                if (!is_string($callback[0])) {
                    throw new InvalidArgumentException('The classname must be string.');
                }

                $callback[0] = $this->getInjectionObject($callback[0]);
            }

            if (empty($callback[1])) {
                $callback[1] = 'handle';
            }
        }

        $args = $this->normalizeInjectionArgs($callback, $args);

        return call_user_func_array($callback, $args);
    }

    /**
     * 删除服务和实例.
     *
     * @param string $name
     */
    public function remove(string $name): void
    {
        $name = $this->normalize($name);

        $prop = [
            'services',
            'instances',
            'singletons',
        ];

        foreach ($prop as $item) {
            if (isset($this->{$item}[$name])) {
                unset($this->{$item}[$name]);
            }
        }

        if ($this->existsCoroutine($name)) {
            unset($this->coroutineInstances[$this->coroutineUid()][$name]);
        }
    }

    /**
     * 删除协程上下文服务和实例.
     *
     * @param string $name
     */
    public function removeCoroutine(?string $name = null): void
    {
        if (!$this->coroutine) {
            return;
        }

        if (null === $name) {
            if (isset($this->coroutineInstances[$this->coroutineUid()])) {
                unset($this->coroutineInstances[$this->coroutineUid()]);
            }
        } else {
            $name = $this->normalize($name);

            if ($this->existsCoroutine($name)) {
                unset($this->coroutineInstances[$this->coroutineUid()][$name]);
            }
        }
    }

    /**
     * 服务或者实例是否存在.
     *
     * @param string $name
     *
     * @return bool
     */
    public function exists(string $name): bool
    {
        $name = $this->normalize($name);

        $name = $this->getAlias($name);

        return isset($this->services[$name]) ||
            isset($this->instances[$name]) ||
            $this->existsCoroutine($name);
    }

    /**
     * 清理容器.
     */
    public function clear(): void
    {
        $prop = [
            'services',
            'instances',
            'singletons',
            'coroutineInstances',
        ];

        foreach ($prop as $item) {
            $this->{$item} = [];
        }

        $this->alias = [];
    }

    /**
     * 设置协程.
     *
     * @param \Leevel\Di\ICoroutine $coroutine
     */
    public function setCoroutine(ICoroutine $coroutine): void
    {
        $this->coroutine = $coroutine;
    }

    /**
     * 返回协程.
     *
     * @return \Leevel\Di\ICoroutine
     */
    public function getCoroutine(): ?ICoroutine
    {
        return $this->coroutine;
    }

    /**
     * 协程服务或者实例是否存在.
     *
     * @param string $name
     *
     * @return bool
     */
    public function existsCoroutine(string $name): bool
    {
        return false !== $this->coroutineUid() &&
            isset($this->coroutineInstances[$this->coroutineUid()], $this->coroutineInstances[$this->coroutineUid()][$name]);
    }

    /**
     * 实现 ArrayAccess::offsetExits.
     *
     * @param string $index
     *
     * @return bool
     */
    public function offsetExists($index): bool
    {
        return $this->exists($index);
    }

    /**
     * 实现 ArrayAccess::offsetGet.
     *
     * @param string $index
     *
     * @return mixed
     */
    public function offsetGet($index)
    {
        return $this->make($index);
    }

    /**
     * 实现 ArrayAccess::offsetSet.
     *
     * @param mixed $index
     * @param mixed $newval
     */
    public function offsetSet($index, $newval): void
    {
        $this->bind($index, $newval);
    }

    /**
     * 实现 ArrayAccess::offsetUnset.
     *
     * @param string $index
     */
    public function offsetUnset($index): void
    {
        $this->remove($index);
    }

    /**
     * 统一去掉前面的斜杠.
     *
     * @param string $name
     *
     * @return string
     */
    protected function normalize(string $name): string
    {
        return ltrim($name, '\\');
    }

    /**
     * 返回对象别名.
     *
     * @param string $name
     *
     * @return string
     */
    protected function getAlias(string $name): string
    {
        return $this->alias[$name] ?? $name;
    }

    /**
     * 是否处于协程上下文.
     *
     * @param mixed $instance
     *
     * @return bool
     */
    protected function coroutineContext($instance): bool
    {
        if (!$this->coroutine) {
            return false;
        }

        if (is_object($instance)) {
            $instance = get_class($instance);
        }

        return $this->coroutine->context($instance);
    }

    /**
     * 当前协程 ID.
     *
     * @return false|int
     */
    protected function coroutineUid()
    {
        if (!$this->coroutine) {
            return false;
        }

        return $this->coroutine->uid();
    }

    /**
     * 根据 class 名字创建实例.
     *
     * @param string $classname
     * @param array  $args
     *
     * @return object|string
     */
    protected function getInjectionObject(string $classname, array $args = [])
    {
        if (interface_exists($classname)) {
            $e = sprintf('Interface %s cannot be normalize because not binded.', $classname);

            throw new ContainerInvalidArgumentException($e);
        }

        if (!class_exists($classname)) {
            return $classname;
        }

        $args = $this->normalizeInjectionArgs($classname, $args);

        return $this->newInstanceArgs($classname, $args);
    }

    /**
     * 格式化依赖参数.
     *
     * @param mixed $value
     * @param array $args
     *
     * @return array
     */
    protected function normalizeInjectionArgs($value, array $args): array
    {
        list($args, $required, $validArgs) = $this->parseInjection($value, $args);

        if ($validArgs < $required) {
            $e = sprintf('There are %d required args,but %d gived.', $required, $validArgs);

            throw new ContainerInvalidArgumentException($e);
        }

        return $args;
    }

    /**
     * 分析自动依赖注入.
     *
     * @param mixed $injection
     * @param array $args
     *
     * @return array
     */
    protected function parseInjection($injection, array $args = []): array
    {
        $result = [];
        $required = 0;

        $param = $this->parseReflection($injection);
        $validArgs = count($param);

        foreach ($param as $key => $item) {
            try {
                switch (true) {
                    case $argsclass = $this->parseParameterClass($item):
                        if (isset($args[0]) && is_object($args[0]) && $args[0] instanceof $argsclass) {
                            $data = array_shift($args);
                        } elseif (array_key_exists($argsclass, $args)) {
                            $data = $args[$argsclass];
                        } elseif ($item->isDefaultValueAvailable()) {
                            $data = $item->getDefaultValue();
                        } else {
                            $data = $this->parseClassFromContainer($argsclass);
                        }

                        $required++;
                        $validArgs++;

                        break;
                    case $item->isDefaultValueAvailable():
                        $data = array_key_exists($item->name, $args) ? $args[$item->name] : $item->getDefaultValue();

                        break;
                    default:
                        $required++;

                        if (array_key_exists($item->name, $args)) {
                            $data = $args[$item->name];
                            $validArgs++;
                        } else {
                            if (isset($args[0])) {
                                $data = array_shift($args);
                            } else {
                                $validArgs--;
                                $data = null;
                            }
                        }

                        break;
                }

                $result[$item->name] = $data;
            } catch (ReflectionException $e) {
                throw new InvalidArgumentException($e->getMessage());
            }
        }

        if ($args) {
            $result = array_values($result);

            // 定义函数 function(IFoo $foo)，调用方法 call([1, 2])，则进行基本的 count($result) 逻辑
            // 定义函数 function($foo)，调用方法 call([1, 2])，则进行 -($required-$validArgs) 填充 null
            $min = count($result) - ($required - $validArgs);

            foreach ($args as $k => $value) {
                if (is_int($k)) {
                    $result[$k + $min] = $value;
                    $validArgs++;
                }
            }
        }

        return [
            $result,
            $required,
            $validArgs,
        ];
    }

    /**
     * 分析反射参数的类.
     *
     * @param \ReflectionParameter $param
     *
     * @return bool|string
     */
    protected function parseParameterClass(ReflectionParameter $param)
    {
        $classObject = $param->getClass();

        if (!$classObject || !($classObject instanceof ReflectionClass)) {
            return false;
        }

        return $classObject->getName();
    }

    /**
     * 从服务容器中获取解析反射参数类实例.
     *
     * @param string $argsclass
     *
     * @return bool|object
     */
    protected function parseClassFromContainer(string $argsclass)
    {
        $itemMake = $this->make($argsclass);

        // 实例对象
        if (is_object($itemMake)) {
            return $itemMake;
        }

        // 接口绑定实现
        $result = $this->make($itemMake);

        if (!is_object($result)) {
            $e = sprintf('Class or interface %s is register in container is not object.', $argsclass);

            throw new InvalidArgumentException($e);
        }

        return $result;
    }

    /**
     * 不同的类型不同的反射.
     *
     * @param mixed $injection
     *
     * @return array
     */
    protected function parseReflection($injection): array
    {
        switch (true) {
            case $injection instanceof Closure:
                return $this->parseClosureReflection($injection);
            case !is_string($injection) && is_callable($injection):
                return $this->parseMethodReflection($injection);
            case is_string($injection):
                return $this->parseClassReflection($injection);
            default:
                throw new InvalidArgumentException('Unsupported callback types.');
        }
    }

    /**
     * 解析闭包反射参数.
     *
     * @param Closure $injection
     *
     * @return array
     */
    protected function parseClosureReflection(Closure $injection): array
    {
        $reflection = new ReflectionFunction($injection);

        if (!($param = $reflection->getParameters())) {
            $param = [];
        }

        return $param;
    }

    /**
     * 解析数组回调反射参数.
     *
     * @param array&callback $injection
     *
     * @return array
     */
    protected function parseMethodReflection(callable $injection): array
    {
        $reflection = new ReflectionMethod($injection[0], $injection[1]);

        if (!($param = $reflection->getParameters())) {
            $param = [];
        }

        return $param;
    }

    /**
     * 解析类反射参数.
     *
     * @param string $injection
     *
     * @return array
     */
    protected function parseClassReflection(string $injection): array
    {
        $reflection = new ReflectionClass($injection);

        if (!$reflection->isInstantiable()) {
            $e = sprintf('Class %s is not instantiable.', $injection);

            throw new InvalidArgumentException($e);
        }

        $param = [];

        if (($constructor = $reflection->getConstructor())) {
            $param = $constructor->getParameters();
        }

        return $param;
    }

    /**
     * 动态创建实例对象.
     * zephir 版本会执行到 newInstanceWithoutConstructor.
     * 例子：Class Tests\\Event\\ListenerNotExtends does not
     * have a constructor, so you cannot pass any constructor arguments.
     *
     * @param string $classname
     * @param array  $args
     *
     * @return mixed
     */
    protected function newInstanceArgs(string $classname, array $args)
    {
        try { // @codeCoverageIgnore
            return (new ReflectionClass($classname))->newInstanceArgs($args);
        } catch (ReflectionException $e) { // @codeCoverageIgnore
            return (new ReflectionClass($classname))->newInstanceWithoutConstructor(); // @codeCoverageIgnore
        } // @codeCoverageIgnore
    }

    /**
     * 解析注册容器对象别名.
     *
     * @param array $name
     *
     * @return array
     */
    protected function parseAlias(array $name): array
    {
        return [key($name), current($name)];
    }
}