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

/**
 * 国际化组件.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.18
 *
 * @version 1.0
 */
class I18n implements II18n
{
    /**
     * 当前语言上下文.
     *
     * @var string
     */
    protected $i18n;

    /**
     * 语言数据.
     *
     * @var array
     */
    protected $text = [];

    /**
     * 构造函数.
     *
     * @param string $i18n
     */
    public function __construct(string $i18n)
    {
        $this->i18n = $i18n;

        $this->text[$i18n] = [];
    }

    /**
     * 获取语言 text.
     *
     * @param string $text
     * @param array  $arr
     *
     * @return string
     */
    public function __(string $text, ...$arr): string
    {
        return $this->gettext($text, ...$arr);
    }

    /**
     * 获取语言 text.
     *
     * @param string $text
     * @param array  $arr
     *
     * @return string
     */
    public function gettext(string $text, ...$arr): string
    {
        $value = $this->text[$this->i18n][$text] ?? $text;

        if ($arr) {
            return sprintf($value, ...$arr);
        }

        return $value;
    }

    /**
     * 添加语言包语句.
     *
     * @param string $i18nName
     * @param array  $data
     */
    public function addtext(string $i18n, array $data = []): void
    {
        if (array_key_exists($i18n, $this->text)) {
            $this->text[$i18n] = array_merge($this->text[$i18n], $data);
        } else {
            $this->text[$i18n] = $data;
        }
    }

    /**
     * 设置当前语言包.
     *
     * @param string $i18n
     */
    public function setI18n(string $i18n): void
    {
        $this->i18n = $i18n;
    }

    /**
     * 获取当前语言包.
     *
     * @return string
     */
    public function getI18n(): string
    {
        return $this->i18n;
    }

    /**
     * 返回所有语言包.
     *
     * @return array
     */
    public function all(): array
    {
        return $this->text;
    }
}


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

/**
 * 模板处理抽象类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.18
 *
 * @version 1.0
 */
abstract class Connect
{
    /**
     * 变量值.
     *
     * @var array
     */
    protected $vars = [];

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [];

    /**
     * 构造函数.
     *
     * @param array $option
     */
    public function __construct(array $option = [])
    {
        $this->option = array_merge($this->option, $option);
    }

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

        return $this;
    }

    /**
     * 设置模板变量.
     *
     * @param array|string $name
     * @param mixed        $value
     */
    public function setVar($name, $value = null): void
    {
        if (is_array($name)) {
            $this->vars = array_merge($this->vars, $name);
        } else {
            $this->vars[$name] = $value;
        }
    }

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

        return $this->vars[$name] ?? null;
    }

    /**
     * 删除变量值.
     *
     * @param array $name
     */
    public function deleteVar(array $name): void
    {
        foreach ($name as $item) {
            if (isset($this->vars[$item])) {
                unset($this->vars[$item]);
            }
        }
    }

    /**
     * 清空变量值.
     *
     * @param null|string $name
     */
    public function clearVar(): void
    {
        $this->vars = [];
    }

    /**
     * 分析展示的视图文件.
     *
     * @param string $file
     * @param string $ext
     *
     * @return string
     */
    protected function parseDisplayFile(?string $file = null, ?string $ext = ''): string
    {
        if (!is_file($file)) {
            $file = $this->parseFile($file, $ext);
        }

        if (!is_file($file)) {
            throw new RuntimeException(
                sprintf('Template file %s does not exist.', $file)
            );
        }

        return $file;
    }

    /**
     * 分析模板真实路径.
     *
     * @param string $tpl
     * @param string $ext
     *
     * @return string
     */
    protected function parseFile(?string $tpl = null, ?string $ext = ''): string
    {
        $tpl = trim(str_replace('->', '.', $tpl));

        // 完整路径或者变量以及表达式路径
        if (pathinfo($tpl, PATHINFO_EXTENSION) ||
            0 === strpos($tpl, '$') ||
            false !== strpos($tpl, '(')) {
            return $this->formatFile($tpl);
        }

        if (!$this->option['theme_path']) {
            throw new RuntimeException('Theme path must be set.');
        }

        return $this->option['theme_path'].'/'.$tpl.
            ($ext ?: $this->option['suffix']);
    }

    /**
     * 格式化文件名.
     *
     * @param string $content
     *
     * @return string
     */
    protected function formatFile(string $content): string
    {
        return str_replace([
            ':',
            '+',
        ], [
            '->',
            '::',
        ], $content);
    }
}


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

/**
 * response header bag.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.02.27
 *
 * @version 1.0
 */
class ResponseHeaderBag extends HeaderBag
{
    /**
     * 下载附件.
     *
     * @var string
     */
    const DISPOSITION_ATTACHMENT = 'attachment';

    /**
     * 文件直接读取.
     *
     * @var string
     */
    const DISPOSITION_INLINE = 'inline';
    /**
     * Cookie.
     *
     * @var \Leevel\Http\ICookie
     */
    protected $cookie;

    /**
     * 构造函数.
     *
     * @param array $elements
     */
    public function __construct(array $elements = [])
    {
        parent::__construct($elements);

        $this->cookie = new Cookie();
    }

    /**
     * call.
     *
     * @param string $method
     * @param array  $args
     *
     * @return mixed
     */
    public function __call(string $method, array $args)
    {
        return $this->cookie->{$method}(...$args);
    }

    /**
     * 设置 COOKIE 别名.
     *
     * @param string $name
     * @param string $value
     * @param array  $option
     */
    public function cookie(string $name, string $value = '', array $option = [])
    {
        $this->setCookie($name, $value, $option);
    }

    /**
     * 设置 COOKIE.
     *
     * @param string $name
     * @param string $value
     * @param array  $option
     */
    public function setCookie(string $name, string $value = '', array $option = []): void
    {
        $this->cookie->set($name, $value, $option);
    }

    /**
     * 批量设置 COOKIE.
     *
     * @param array $cookies
     * @param array $option
     */
    public function withCookies(array $cookies, array $option = []): void
    {
        foreach ($cookies as $key => $value) {
            $this->setCookie($key, $value, $option);
        }
    }

    /**
     * 获取 COOKIE.
     *
     * @return array
     */
    public function getCookies(): array
    {
        return $this->cookie->all();
    }
}


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

use Closure;
use Leevel\Support\IArray;
use Leevel\Support\IJson;
use RuntimeException;

/**
 * 树数据处理.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.09.11
 *
 * @version 1.0
 */
class Tree implements IJson, IArray
{
    /**
     * 子父关系映射.
     *
     * @var array
     */
    protected $map = [];

    /**
     * 节点数据.
     *
     * @var array
     */
    protected $data = [];

    /**
     * 构造函数.
     *
     * @param array $nodes
     */
    public function __construct(array $nodes = [])
    {
        foreach ($nodes as $node) {
            if (!is_array($node) || 3 !== count($node)) {
                throw new RuntimeException(
                    'The node must be an array of three elements.'
                );
            }

            $this->setNode($node[0], $node[1], $node[2]);
        }
    }

    /**
     * 设置节点数据.
     *
     * @param int|string $id
     * @param int|string $parent
     * @param mixed      $value
     * @param bool       $priority
     */
    public function setNode($id, $parent, $value, bool $priority = false): void
    {
        $this->data[$id] = $value;

        if ($priority) {
            $tmp = [
                $id => $parent,
            ];

            foreach ($this->map as $key => $value) {
                $tmp[$key] = $value;
            }

            $this->map = $tmp;

            unset($tmp);
        } else {
            $this->map[$id] = $parent;
        }
    }

    /**
     * 取得给定 ID 子树.
     *
     * @param int|string $id
     *
     * @return array
     */
    public function getChildrenTree($id = 0): array
    {
        return $this->normalize(null, [], $id);
    }

    /**
     * 取得给定 ID 一级子树 ID.
     *
     * @param int|string $id
     *
     * @return array
     */
    public function getChild($id): array
    {
        $data = [];

        foreach ($this->map as $key => $parent) {
            if ((string) $parent === (string) $id) {
                $data[$key] = $key;
            }
        }

        return $data;
    }

    /**
     * 取得给定 ID 所有子树 ID.
     *
     * @param int|string $id
     *
     * @return array
     */
    public function getChildren($id = 0): array
    {
        $data = [];

        foreach ($this->getChild($id) as $key) {
            $data[] = $key;
            $data = array_merge($data, $this->getChildren($key));
        }

        return $data;
    }

    /**
     * 取得给定 ID 是否包含子树.
     *
     * @param int|string $id
     *
     * @return bool
     */
    public function hasChild($id): bool
    {
        return count($this->getChild($id)) > 0;
    }

    /**
     * 验证是否存在子菜单.
     *
     * @param int|string $id
     * @param array      $validateChildren
     * @param bool       $strict
     *
     * @return bool
     */
    public function hasChildren($id, array $validateChildren, bool $strict = true): bool
    {
        if (empty($validateChildren)) {
            return false;
        }

        $children = $this->getChildren($id);

        if (true === $strict && array_diff($validateChildren, $children)) {
            return false;
        }

        if (false === $strict && array_intersect($validateChildren, $children)) {
            return true;
        }

        return false;
    }

    /**
     * 取得给定 ID 上级父级 ID.
     *
     * @param int|string $id
     * @param bool       $withItSelf
     *
     * @return array
     */
    public function getParent($id, bool $withItSelf = false): array
    {
        if (!array_key_exists($id, $this->map)) {
            return [];
        }

        $data = [];

        if (array_key_exists($this->map[$id], $this->map)) {
            $data[] = $this->map[$id];
        }

        if (true === $withItSelf) {
            $data[] = $id;
        }

        return $data;
    }

    /**
     * 取得给定 ID 所有父级 ID.
     *
     * @param int|string $id
     * @param bool       $withItSelf
     *
     * @return array
     */
    public function getParents($id, bool $withItSelf = false): array
    {
        $data = $this->getParentsReal($id);
        sort($data);

        if (true === $withItSelf) {
            $data[] = $id;
        }

        return $data;
    }

    /**
     * 判断级别.
     *
     * @param int|string $id
     *
     * @return int
     */
    public function getLevel($id): int
    {
        return count($this->getParentsReal($id));
    }

    /**
     * 取得节点的值
     *
     * @param int|string $id
     * @param null|mixed $defaults
     *
     * @return mixed
     */
    public function getData($id, $defaults = null)
    {
        return $this->data[$id] ?? $defaults;
    }

    /**
     * 设置节点的值
     *
     * @param int|string $id
     * @param mixed      $value
     */
    public function setData($id, $value): void
    {
        if (isset($this->data[$id])) {
            $this->data[$id] = $value;
        }
    }

    /**
     * 树转化为数组.
     *
     * @param \Closure   $callables
     * @param array      $key
     * @param int|string $id
     *
     * @return array
     */
    public function normalize(Closure $callables = null, array $key = [], $id = 0): array
    {
        $data = [];

        foreach ($this->getChild($id) as $value) {
            $item = [
                $key['value'] ?? 'value' => $value,
                $key['data'] ?? 'data'   => $this->data[$value],
            ];

            if ($callables) {
                $result = $callables($item, $this);

                if (null !== $result) {
                    $item = $result;
                }
            }

            if ($children = $this->normalize($callables, $key, $value)) {
                $item[$key['children'] ?? 'children'] = $children;
            }

            $data[] = $item;
        }

        return $data;
    }

    /**
     * 对象转 JSON.
     *
     * @param int $option
     *
     * @return string
     */
    public function toJson($option = null): string
    {
        if (null === $option) {
            $option = JSON_UNESCAPED_UNICODE;
        }

        $args = func_get_args();
        array_shift($args);

        return json_encode($this->toArray(...$args), $option);
    }

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

    /**
     * 取得给定 ID 所有父级 ID.
     *
     * @param int|string $id
     *
     * @return array
     */
    protected function getParentsReal($id): array
    {
        if (!array_key_exists($id, $this->map)) {
            return [];
        }

        $data = [];

        if (array_key_exists($this->map[$id], $this->map)) {
            $data[] = $this->map[$id];
            $data = array_merge($data, $this->getParentsReal($this->map[$id]));
        }

        return $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\Cache;

/**
 * 缓存抽象类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.02.15
 *
 * @version 1.0
 */
abstract class Connect
{
    /**
     * 缓存服务句柄.
     *
     * @var handle
     */
    protected $handle;

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [];

    /**
     * 构造函数.
     *
     * @param array $option
     */
    public function __construct(array $option = [])
    {
        $this->option = array_merge($this->option, $option);
    }

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

        return $this;
    }

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

    /**
     * 获取缓存名字.
     *
     * @param string $name
     *
     * @return string
     */
    protected function getCacheName(string $name): string
    {
        return $name;
    }

    /**
     * 读取缓存时间配置.
     *
     * @param string $id
     * @param int    $defaultTime
     *
     * @return int
     */
    protected function cacheTime(string $id, int $defaultTime = 0): int
    {
        if (!$this->option['time_preset']) {
            return $defaultTime;
        }

        if (isset($this->option['time_preset'][$id])) {
            return $this->option['time_preset'][$id];
        }

        foreach ($this->option['time_preset'] as $key => $value) {
            if (preg_match($this->prepareRegexForWildcard($key), $id, $res)) {
                return $this->option['time_preset'][$key];
            }
        }

        return $defaultTime;
    }

    /**
     * 通配符正则.
     *
     * @param string $regex
     *
     * @return string
     */
    protected function prepareRegexForWildcard(string $regex): string
    {
        $regex = preg_quote($regex, '/');
        $regex = '/^'.str_replace('\*', '(\S*)', $regex).'$/';

        return $regex;
    }

    /**
     * 整理配置.
     *
     * @param array $option
     *
     * @return array
     */
    protected function normalizeOptions(array $option = []): array
    {
        return $option ? array_merge($this->option, $option) : $this->option;
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Kernel\Console;

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

/**
 * public 资源目录创建软连接到 www.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.09.01
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
class LinkPublic extends Command
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'link:public';

    /**
     * 命令行描述.
     *
     * @var string
     */
    protected $description = 'Create a symbolic link from `public` to `www/public`.';

    /**
     * 响应命令.
     *
     * @param \Leevel\Kernel\IApp $app
     */
    public function handle(IApp $app): void
    {
        if (file_exists($link = $app->path('www/public'))) {
            $this->error(
                sprintf('The `%s` directory already exists.', $link)
            );

            return;
        }

        link(
            $path = $app->publicPath(), $link
        );

        $this->info(sprintf('Linked `%s` directory to `%s` successed.', $path, $link));
    }

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

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

// import fn.
class_exists(link::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\Manager;

use Exception;
use Leevel\Di\IContainer;

/**
 * manager 入口.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.09.07
 *
 * @version 1.0
 */
abstract class Manager
{
    /**
     * IOC Container.
     *
     * @var \Leevel\Di\IContainer
     */
    protected $container;

    /**
     * 连接对象
     *
     * @var object[]
     */
    protected $connects = [];

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

    /**
     * call.
     *
     * @param string $method
     * @param array  $args
     *
     * @return mixed
     */
    public function __call(string $method, array $args)
    {
        return $this->connect()->{$method}(...$args);
    }

    /**
     * 返回 IOC 容器.
     *
     * @return \Leevel\Di\IContainer
     */
    public function container(): IContainer
    {
        return $this->container;
    }

    /**
     * 连接 connect 并返回连接对象
     *
     * @param null|array|string $options
     *
     * @return object
     */
    public function connect($options = null): object
    {
        list($options, $unique) = $this->parseOptionAndUnique($options);

        if (isset($this->connects[$unique])) {
            return $this->connects[$unique];
        }

        $driver = $options['driver'] ?? $this->getDefaultDriver();

        return $this->connects[$unique] = $this->makeConnect(
            $driver, $options
        );
    }

    /**
     * 重新连接.
     *
     * @param array|string $options
     *
     * @return object
     */
    public function reconnect($options = []): object
    {
        $this->disconnect($options);

        return $this->connect($options);
    }

    /**
     * 删除连接.
     *
     * @param array|string $options
     */
    public function disconnect($options = []): void
    {
        list($options, $unique) = $this->parseOptionAndUnique($options);

        if (isset($this->connects[$unique])) {
            unset($this->connects[$unique]);
        }
    }

    /**
     * 取回所有连接.
     *
     * @return object[]
     */
    public function getConnects(): array
    {
        return $this->connects;
    }

    /**
     * 返回默认驱动.
     *
     * @return string
     */
    public function getDefaultDriver(): string
    {
        return $this->getContainerOption('default');
    }

    /**
     * 设置默认驱动.
     *
     * @param string $name
     */
    public function setDefaultDriver(string $name): void
    {
        $this->setContainerOption('default', $name);
    }

    /**
     * 获取容器配置值.
     *
     * @param string $name
     *
     * @return mixed
     */
    public function getContainerOption(?string $name = null)
    {
        $name = $this->normalizeOptionName($name);

        return $this->container['option'][$name];
    }

    /**
     * 设置容器配置值.
     *
     * @param string $name
     * @param mixed  $value
     */
    public function setContainerOption(string $name, $value): void
    {
        $name = $this->normalizeOptionName($name);

        $this->container['option'][$name] = $value;
    }

    /**
     * 取得配置命名空间.
     *
     * @return string
     */
    abstract protected function normalizeOptionNamespace(): string;

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

    /**
     * 取得连接名字.
     *
     * @param string $name
     *
     * @return string
     */
    protected function normalizeOptionName(?string $name = null): string
    {
        return $this->normalizeOptionNamespace().'\\'.$name;
    }

    /**
     * 创建连接.
     *
     * @param string $connect
     * @param array  $options
     *
     * @return object
     */
    protected function makeConnect(string $connect, array $options = []): object
    {
        if (null === $this->getContainerOption('connect.'.$connect)) {
            $e = sprintf('Connect driver %s not exits.', $connect);

            throw new Exception($e);
        }

        return $this->createConnect(
            $this->createConnectCommon($connect, $options)
        );
    }

    /**
     * 创建连接对象公共入口.
     *
     * @param string $connect
     * @param array  $options
     *
     * @return object
     */
    protected function createConnectCommon(string $connect, array $options = []): object
    {
        return $this->{'makeConnect'.ucwords($connect)}($options);
    }

    /**
     * 分析连接参数以及其唯一值
     *
     * @param array|string $options
     *
     * @return array
     */
    protected function parseOptionAndUnique($options = []): array
    {
        return [
            $options = $this->parseOptionParameter($options),
            $this->normalizeUnique($options),
        ];
    }

    /**
     * 分析连接参数.
     *
     * @param array|string $options
     *
     * @return array
     */
    protected function parseOptionParameter($options = []): array
    {
        if (null === $options) {
            return [];
        }

        if (is_string($options)) {
            $options = $this->getContainerOption('connect.'.$options);

            if (!is_array($options)) {
                return [];
            }
        }

        return $options;
    }

    /**
     * 取得唯一值
     *
     * @param array $options
     *
     * @return string
     */
    protected function normalizeUnique(array $options): string
    {
        return md5(serialize($options));
    }

    /**
     * 整理连接配置.
     *
     * @param string $connect
     * @param array  $extendOption
     *
     * @return array
     */
    protected function normalizeConnectOption(string $connect, array $extendOption = []): array
    {
        return array_merge(
            $this->getConnectOption($connect),
            $this->getCommonOption(),
            $extendOption
        );
    }

    /**
     * 读取连接全局配置.
     *
     * @return array
     */
    protected function getCommonOption(): array
    {
        return $this->filterCommonOption(
            $this->getContainerOption()
        );
    }

    /**
     * 过滤全局配置.
     *
     * @param array $options
     *
     * @return array
     */
    protected function filterCommonOption(array $options): array
    {
        foreach ($this->defaultCommonOption() as $item) {
            if (isset($options[$item])) {
                unset($options[$item]);
            }
        }

        return $options;
    }

    /**
     * 过滤全局配置项.
     *
     * @return array
     */
    protected function defaultCommonOption(): array
    {
        return [
            'default',
            'connect',
        ];
    }

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

    /**
     * 清除配置 null.
     *
     * @param array $options
     *
     * @return array
     */
    protected function filterNullOfOption(array $options): array
    {
        return array_filter($options, function ($value) {
            return null !== $value;
        });
    }
}


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

use Dotenv\Dotenv;
use Dotenv\Exception\InvalidFileException;
use Dotenv\Exception\InvalidPathException;
use Leevel\Kernel\IApp;
use RuntimeException;

/**
 * 配置工具类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.05.18
 *
 * @version 1.0
 */
class Load
{
    /**
     * 配置路径.
     *
     * @var string
     */
    protected $dir = [];

    /**
     * 已经载入数据.
     *
     * @var array
     */
    protected $loaded = [];

    /**
     * 构造函数.
     *
     * @param string $dir
     */
    public function __construct(string $dir)
    {
        if (!is_dir($dir)) {
            $e = sprintf('Option load dir %s is not exits.', $dir);

            throw new RuntimeException($e);
        }

        $this->dir = $dir;
    }

    /**
     * 载入配置数据.
     *
     * @param \Leevel\Kernel\IApp $app
     *
     * @return array
     */
    public function loadData(IApp $app): array
    {
        if ($this->loaded) {
            return $this->loaded;
        }

        $env = $this->loadEnvData($app);

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

        $data = $this->loadOptionData();

        // 读取额外的配置
        if ($composer['options']) {
            $data = $this->mergeComposerOption($data, $app, $composer['options']);
        }

        // 环境变量
        $data['app']['_env'] = $env;

        // 延迟服务提供者
        $composer['providers'] = array_diff($composer['providers'], $composer['ignores']);
        $providers = $this->loadDeferredProviderData($composer['providers']);
        $data['app']['_deferred_providers'] = $providers;

        // composer 配置
        $data['app']['_composer'] = $composer;

        return $this->loaded = $data;
    }

    /**
     * 载入环境变量数据.
     *
     * @param \Leevel\Kernel\IApp $app
     *
     * @return array
     */
    protected function loadEnvData(IApp $app): array
    {
        $oldEnv = $_ENV;
        $_ENV = [];

        try {
            (new Dotenv($app->envPath(), $app->envFile()))->overload();
        } catch (InvalidPathException $e) {
            throw new RuntimeException($e->getMessage());
        } catch (InvalidFileException $e) {
            throw new RuntimeException($e->getMessage());
        }

        $result = $_ENV;
        $_ENV = array_merge($oldEnv, $_ENV);

        return $result;
    }

    /**
     * 分析延迟加载服务提供者.
     *
     * @param array $providers
     *
     * @return array
     */
    protected function loadDeferredProviderData(array &$providers): array
    {
        $deferredProviders = $deferredAlias = [];

        foreach ($providers as $k => $provider) {
            if (!class_exists($provider)) {
                unset($providers[$k]);

                continue;
            }

            if ($provider::isDeferred()) {
                $providerAlias = $provider::providers();

                foreach ($providerAlias as $key => $alias) {
                    if (is_int($key)) {
                        $key = $alias;
                    }

                    $deferredProviders[$key] = $provider;
                }

                $deferredAlias[$provider] = $providerAlias;

                unset($providers[$k]);
            }
        }

        $providers = array_values($providers);

        return [
            $deferredProviders,
            $deferredAlias,
        ];
    }

    /**
     * 载入 Composer 配置数据.
     *
     * @param string $path
     *
     * @return array
     */
    protected function loadComposerOption(string $path): array
    {
        return (new ComposerOption($path))->loadData();
    }

    /**
     * 载入配置数据.
     *
     * @return array
     */
    protected function loadOptionData(): array
    {
        $data = [];

        $files = glob($this->dir.'/*.php');

        $findApp = false;

        foreach ($files as $file) {
            $type = substr(basename($file), 0, -4);

            if ('app' === $type) {
                $findApp = true;
            }

            $data[$type] = (array) include $file;
        }

        if (false === $findApp) {
            throw new RuntimeException('Unable to load the app option file.');
        }

        return $data;
    }

    /**
     * 合并 composer 配置数据.
     *
     * @param array               $options
     * @param \Leevel\Kernel\IApp $app
     * @param array               $optionFiles
     *
     * @return array
     */
    protected function mergeComposerOption(array $options, IApp $app, array $optionFiles): array
    {
        $data = [];

        $path = $app->path();

        foreach ($optionFiles as $key => $files) {
            if (!is_array($files)) {
                $files = [$files];
            }

            $optionData = [];

            foreach ($files as $item) {
                if (!is_file($item)) {
                    $item = $path.'/'.$item;
                }

                if (!is_file($item)) {
                    $e = sprintf('Option file %s is not exist.', $item);

                    throw new RuntimeException($e);
                }

                $optionData = array_merge($optionData, include $item);
            }

            if (array_key_exists($key, $options)) {
                $options[$key] = array_merge($options[$key], $optionData);
            } else {
                $options[$key] = $optionData;
            }
        }

        return $options;
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Kernel\Console;

use Leevel\Console\Command;
use Leevel\Http\Leevel2Psr;
use Leevel\Http\Psr2Leevel;
use Leevel\Kernel\IApp;
use Leevel\Kernel\IKernel;
use Spiral\Goridge\StreamRelay;
use Spiral\RoadRunner\PSR7Client;
use Spiral\RoadRunner\Worker;
use Throwable;

/**
 * RoadRunner 的服务器端.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2019.03.11
 *
 * @version 1.0
 *
 * @see https://github.com/spiral/roadrunner
 * @codeCoverageIgnore
 */
class RoadRunnerServer extends Command
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'rrserver';

    /**
     * 命令行描述.
     *
     * @var string
     */
    protected $description = 'Start road runner server.';

    /**
     * 响应命令.
     *
     * @param \Leevel\Kernel\IApp $app
     */
    public function handle(IApp $app): void
    {
        ini_set('display_errors', 'stderr');

        $kernel = $app->make(IKernel::class);
        list($psr7, $psr2Leevel, $leevel2Psr) = $this->getPsrBridge();

        while ($req = $psr7->acceptRequest()) {
            try {
                $request = $psr2Leevel->createRequest($req);
                $response = $kernel->handle($request);
                $psr7->respond($leevel2Psr->createResponse($response));
                $kernel->terminate($request, $response);
            } catch (Throwable $e) {
                $psr7->getWorker()->error((string) $e);
            }
        }
    }

    /**
     * 取得 Psr 桥接.
     *
     * @return \Spiral\RoadRunner\PSR7Client
     */
    protected function getPsr7(): PSR7Client
    {
        $relay = new StreamRelay(STDIN, STDOUT);

        return new PSR7Client(new Worker($relay));
    }

    /**
     * 取得 Psr 桥接.
     *
     * @return array
     */
    protected function getPsrBridge(): array
    {
        return [
            $this->getPsr7(),
            new Psr2Leevel(),
            new Leevel2Psr(),
        ];
    }

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

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


<?php

declare(strict_types=1);

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

namespace Leevel\Kernel\Console;

use Leevel\Console\Command;
use Leevel\Kernel\IApp;

/**
 * 自动加载缓存清理.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.09.04
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
class AutoloadClear extends Command
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'autoload:clear';

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

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

        $cachePath = $app->runtimePath('autoload.php');

        $this->clearCache($cachePath);

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

    /**
     * 删除缓存.
     *
     * @param string $cachePath
     */
    protected function clearCache(string $cachePath): void
    {
        if (!is_file($cachePath)) {
            $this->warn('Autoload cache files have been cleaned up.');

            return;
        }

        unlink($cachePath);
    }

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

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


<?php

declare(strict_types=1);

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

namespace Leevel\Kernel\Utils;

/**
 * 从文件获取类名字.
 * 直接从文章拷贝过来.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2019.02.28
 *
 * @version 1.0
 *
 * @see http://jarretbyrne.com/2015/06/197/
 *
 * @codeCoverageIgnore
 */
class ClassParser
{
    /**
     * 从文件读取类名.
     *
     * @param string $pathToFile
     *
     * @return string
     */
    public function handle(string $pathToFile): string
    {
        // Grab the contents of the file
        $contents = file_get_contents($pathToFile);

        // Start with a blank namespace and class
        $namespace = $className = '';

        // Set helper values to know that we have found the namespace/class token and need to collect the string values after them
        $gettingNamespace = $gettingClass = false;

        // Go through each token and evaluate it as necessary
        foreach (token_get_all($contents) as $token) {
            // If this token is the namespace declaring, then flag that the next tokens will be the namespace name
            if (is_array($token) && T_NAMESPACE === $token[0]) {
                $gettingNamespace = true;
            }

            // If this token is the class declaring, then flag that the next tokens will be the class name
            if (is_array($token) && T_CLASS === $token[0]) {
                $gettingClass = true;
            }

            // While we're grabbing the namespace name...
            if (true === $gettingNamespace) {
                // If the token is a string or the namespace separator...
                if (is_array($token) && in_array($token[0], [T_STRING, T_NS_SEPARATOR], true)) {
                    // Append the token's value to the name of the namespace
                    $namespace .= $token[1];
                } elseif (';' === $token) {
                    // If the token is the semicolon, then we're done with the namespace declaration
                    $gettingNamespace = false;
                }
            }

            // While we're grabbing the class name...
            if (true === $gettingClass) {
                // If the token is a string, it's the name of the class
                if (is_array($token) && T_STRING === $token[0]) {
                    // Store the token's value as the class name
                    $className = $token[1];

                    // Got what we need, stope here
                    break;
                }
            }
        }

        // Build the fully-qualified class name and return it
        return $namespace ? $namespace.'\\'.$className : $className;
    }
}


<?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 Leevel\Di\IContainer;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * 命令抽象类
 * This class borrows heavily from the Lavavel Framework and is part of the lavavel package.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.04.28
 *
 * @version 1.0
 *
 * @see Illuminate/Console (https://github.com/laravel/framework)
 */
abstract class Command extends SymfonyCommand
{
    /**
     * 默认输出映射.
     *
     * @var int
     */
    const DEFAULT_VERBOSITY = OutputInterface::VERBOSITY_NORMAL;

    /**
     * 应用容器.
     *
     * @var \Leevel\Di\IContainer
     */
    protected $container;

    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name;

    /**
     * 命令行描述.
     *
     * @var string
     */
    protected $description;

    /**
     * 命令帮助.
     *
     * @var string
     */
    protected $help;

    /**
     * 输出映射.
     *
     * @var array
     */
    protected static $verbosityMap = [
        'v'      => OutputInterface::VERBOSITY_VERBOSE,
        'vv'     => OutputInterface::VERBOSITY_VERY_VERBOSE,
        'vvv'    => OutputInterface::VERBOSITY_DEBUG,
        'quiet'  => OutputInterface::VERBOSITY_QUIET,
        'normal' => OutputInterface::VERBOSITY_NORMAL,
    ];

    /**
     * 输入接口.
     *
     * @var object
     */
    protected $input;

    /**
     * 输入接口.
     *
     * @var object
     */
    protected $output;

    /**
     * 构造函数.
     */
    public function __construct()
    {
        parent::__construct($this->name);

        $this->setDescription($this->description);
        $this->setHelp($this->help);
        $this->specifyParameters();
    }

    /**
     * 运行命令.
     *
     * @param \Symfony\Component\Console\Input\InputInterface   $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     *
     * @return int
     */
    public function run(InputInterface $input, OutputInterface $output)
    {
        $this->input = $input;
        $this->output = new SymfonyStyle($input, $output);

        return parent::run($input, $output);
    }

    /**
     * 调用其他命令.
     *
     * @param string $command
     * @param array  $arguments
     *
     * @return int
     */
    public function call($command, array $arguments = []): int
    {
        $arguments['command'] = $command;

        return $this->getApplication()->find($command)->run(
            new ArrayInput($arguments), $this->output
        );
    }

    /**
     * 获取输入参数.
     *
     * @param string $key
     *
     * @return array|string
     */
    public function argument(?string $key = null)
    {
        if (null === $key) {
            return $this->input->getArguments();
        }

        return $this->input->getArgument($key);
    }

    /**
     * 获取配置信息.
     *
     * @param string $key
     *
     * @return array|bool|string
     */
    public function option(?string $key = null)
    {
        if (null === $key) {
            return $this->input->getOptions();
        }

        return $this->input->getOption($key);
    }

    /**
     * 确认用户的问题.
     * 等待与用户进行交互，无法被测试.
     *
     * @param string $question
     * @param bool   $defaults
     *
     * @return bool
     * @codeCoverageIgnore
     */
    public function confirm($question, $defaults = false): bool
    {
        return $this->output->confirm($question, $defaults);
    }

    /**
     * 提示用户输入.
     * 等待与用户进行交互，无法被测试.
     *
     * @param string $question
     * @param string $defaults
     *
     * @return string
     * @codeCoverageIgnore
     */
    public function ask($question, $defaults = null): string
    {
        return $this->output->ask($question, $defaults);
    }

    /**
     * 输出一个表格文本.
     *
     * @param array  $headers
     * @param array  $rows
     * @param string $style
     */
    public function table(array $headers, array $rows, $style = 'default'): void
    {
        (new Table($this->output))->

        setHeaders($headers)->

        setRows($rows)->

        setStyle($style)->

        render();
    }

    /**
     * 输出一个一般信息.
     *
     * @param string          $message
     * @param null|int|string $verbosity
     */
    public function info($message, $verbosity = null): void
    {
        $this->line($message, 'info', $verbosity);
    }

    /**
     * 返回一个带有时间的消息.
     *
     * @param string $message
     * @param string $format
     *
     * @return string
     */
    public function time($message, $format = 'H:i:s'): string
    {
        return ($format ? sprintf('[%s]', date($format)) : '').$message;
    }

    /**
     * 输出一个注释信息.
     *
     * @param string          $message
     * @param null|int|string $verbosity
     */
    public function comment($message, $verbosity = null): void
    {
        $this->line($message, 'comment', $verbosity);
    }

    /**
     * 输出一个问题信息.
     *
     * @param string          $message
     * @param null|int|string $verbosity
     */
    public function question($message, $verbosity = null): void
    {
        $this->line($message, 'question', $verbosity);
    }

    /**
     * 提示用户输入根据返回结果自动完成一些功能.
     * 等待与用户进行交互，无法被测试.
     *
     * @param string $question
     * @param array  $choices
     * @param string $defaults
     *
     * @return string
     * @codeCoverageIgnore
     */
    public function askWithCompletion($question, array $choices, $defaults = null): string
    {
        $question = new Question($question, $defaults);
        $question->setAutocompleterValues($choices);

        return $this->output->askQuestion($question);
    }

    /**
     * 提示用户输入但是控制台隐藏答案.
     * 等待与用户进行交互，无法被测试.
     *
     * @param string $question
     * @param bool   $fallback
     *
     * @return string
     * @codeCoverageIgnore
     */
    public function secret($question, bool $fallback = true): string
    {
        $question = new Question($question);
        $question->setHidden(true)->setHiddenFallback($fallback);

        return $this->output->askQuestion($question);
    }

    /**
     * 给用户一个问题组选择.
     * 等待与用户进行交互，无法被测试.
     *
     * @param string $question
     * @param array  $choices
     * @param string $defaults
     * @param mixed  $attempts
     * @param bool   $multiple
     *
     * @return string
     * @codeCoverageIgnore
     */
    public function choice($question, array $choices, $defaults = null, $attempts = null, $multiple = null): string
    {
        $question = new ChoiceQuestion($question, $choices, $defaults);
        $question->setMaxAttempts($attempts)->setMultiselect($multiple);

        return $this->output->askQuestion($question);
    }

    /**
     * 输出一个错误信息.
     *
     * @param string          $message
     * @param null|int|string $verbosity
     */
    public function error($message, $verbosity = null): void
    {
        $this->line($message, 'error', $verbosity);
    }

    /**
     * 输出一个警告信息.
     *
     * @param string          $message
     * @param null|int|string $verbosity
     */
    public function warn($message, $verbosity = null): void
    {
        if (!$this->output->getFormatter()->hasStyle('warning')) {
            $this->output->getFormatter()->setStyle('warning', new OutputFormatterStyle('yellow'));
        }

        $this->line($message, 'warning', $verbosity);
    }

    /**
     * 输出一条独立的信息.
     *
     * @param string          $message
     * @param string          $style
     * @param null|int|string $verbosity
     */
    public function line($message, $style = null, $verbosity = null): void
    {
        $message = $style ? "<{$style}>{$message}</{$style}>" : $message;

        $this->output->writeln($message, $this->parseVerbosity($verbosity));
    }

    /**
     * 设置服务容器.
     *
     * @param \Leevel\Di\IContainer $container
     */
    public function setContainer(IContainer $container): void
    {
        $this->container = $container;
    }

    /**
     * 返回服务容器.
     *
     * @return \Leevel\Di\IContainer
     */
    public function getContainer(): IContainer
    {
        return $this->container;
    }

    /**
     * 响应命令.
     *
     * @param object $input
     * @param object $output
     *
     * @return mixed
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        return $this->container->call([
            $this,
            'handle',
        ]);
    }

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

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

    /**
     * 定义参数和配置.
     */
    protected function specifyParameters(): void
    {
        foreach ($this->getArguments() as $argument) {
            $this->addArgument(...$argument);
        }

        foreach ($this->getOptions() as $option) {
            $this->addOption(...$option);
        }
    }

    /**
     * 获取输入信息级别.
     *
     * @param int|string $level
     *
     * @return int
     */
    protected function parseVerbosity($level = null): int
    {
        return static::$verbosityMap[$level] ??
            (!is_int($level) ? static::DEFAULT_VERBOSITY : $level);
    }
}


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

/**
 * 路由中间件分析.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.04.21
 *
 * @version 1.0
 */
class MiddlewareParser
{
    /**
     * 路由.
     *
     * @var \Leevel\Router\Router
     */
    protected $router;

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

    /**
     * 解析中间件
     * 最多 2 个层级支持
     *
     * @param array $middlewares
     *
     * @return array
     */
    public function handle(array $middlewares): array
    {
        $middlewareGroups = $this->router->getMiddlewareGroups();
        $middlewareAlias = $this->router->getMiddlewareAlias();

        $result = [];

        foreach ($middlewares as $m) {
            if (!is_string($m)) {
                throw new InvalidArgumentException('Middleware only allowed string.');
            }

            list($m, $params) = $this->parseMiddleware($m);

            if (isset($middlewareGroups[$m])) {
                $temp = is_array($middlewareGroups[$m]) ?
                    $middlewareGroups[$m] : [$middlewareGroups[$m]];

                foreach ($temp as $item) {
                    list($item, $params) = $this->parseMiddleware($item);

                    $result[] = $this->middlewareName($middlewareAlias[$item] ?? $item, $params);
                }
            } else {
                $result[] = $this->middlewareName($middlewareAlias[$m] ?? $m, $params);
            }
        }

        $result = [
            'handle'    => $this->normalizeMiddleware($result, 'handle'),
            'terminate' => $this->normalizeMiddleware($result, 'terminate'),
        ];

        if (empty($result['handle']) && empty($result['terminate'])) {
            return [];
        }

        return $result;
    }

    /**
     * 格式化中间件.
     *
     * @param array  $middlewares
     * @param string $method
     *
     * @return array
     */
    protected function normalizeMiddleware(array $middlewares, string $method): array
    {
        $middlewares = array_map(function ($item) use ($method) {
            if (false === strpos($item, ':')) {
                $realClass = $item;
            } else {
                list($realClass) = explode(':', $item);
            }

            // ignore group like `web` or `api`
            if (false !== strpos($realClass, '\\') && !class_exists($realClass)) {
                throw new InvalidArgumentException(
                    sprintf('Middleware %s was not found.', $realClass)
                );
            }

            if (!method_exists($realClass, $method)) {
                return false;
            }

            if (false === strpos($item, ':')) {
                return $item.'@'.$method;
            }

            return str_replace(':', '@'.$method.':', $item);
        }, $middlewares);

        $middlewares = array_values(array_unique(array_filter($middlewares)));

        return $middlewares;
    }

    /**
     * 分析中间件.
     *
     * @param string $middleware
     *
     * @return array
     */
    protected function parseMiddleware(string $middleware): array
    {
        $params = '';

        if (false !== strpos($middleware, ':')) {
            list($middleware, $params) = explode(':', $middleware);
        }

        return [
            $middleware,
            $params,
        ];
    }

    /**
     * 中间件名字.
     *
     * @param string $middleware
     * @param string $params
     *
     * @return string
     */
    protected function middlewareName(string $middleware, string $params): string
    {
        return $middleware.($params ? ':'.$params : '');
    }
}