<?php

declare(strict_types=1);

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

namespace Leevel\Database\Ddd;

use Closure;
use InvalidArgumentException;
use Leevel\Collection\Collection;

/**
 * 仓储基础
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.10.14
 * @since 2018.10 重新实现规约查询
 *
 * @version 1.0
 */
class Repository implements IRepository
{
    /**
     * 实体.
     *
     * @var \Leevel\Database\Ddd\IEntity
     */
    protected $entity;

    /**
     * 构造函数.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     */
    public function __construct(IEntity $entity)
    {
        $this->entity = $entity;
    }

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

    /**
     * 取得一条数据.
     *
     * @param int   $id
     * @param array $column
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    public function find(int $id, array $column = ['*']): IEntity
    {
        return $this->entity->find($id, $column);
    }

    /**
     * 取得一条数据，未找到记录抛出异常.
     *
     * @param int   $id
     * @param array $column
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    public function findOrFail(int $id, array $column = ['*']): IEntity
    {
        return $this->entity->findOrFail($id, $column);
    }

    /**
     * 取得所有记录.
     *
     * @param null|array|\Closure|\Leevel\Database\Ddd\ISpecification|string $condition
     *
     * @return \Leevel\Collection\Collection
     */
    public function findAll($condition = null): Collection
    {
        $select = $this->entity->selfDatabaseSelect();

        if ($condition) {
            $this->normalizeCondition($condition, $select);
        }

        return $select->findAll();
    }

    /**
     * 返回一列数据.
     *
     * @param null|array|\Closure|\Leevel\Database\Ddd\ISpecification|string $condition
     * @param mixed                                                          $fieldValue
     * @param string                                                         $fieldKey
     *
     * @return array
     */
    public function findList($condition, $fieldValue, ?string $fieldKey = null): array
    {
        $select = $this->entity->selfDatabaseSelect();

        if ($condition) {
            $this->normalizeCondition($condition, $select);
        }

        return $select->list($fieldValue, $fieldKey);
    }

    /**
     * 取得记录数量.
     *
     * @param null|array|\Closure|\Leevel\Database\Ddd\ISpecification|string $condition
     * @param string                                                         $field
     *
     * @return int
     */
    public function findCount($condition = null, string $field = '*'): int
    {
        $select = $this->entity->selfDatabaseSelect();

        if ($condition) {
            $this->normalizeCondition($condition, $select);
        }

        return $select->findCount($field);
    }

    /**
     * 分页查询记录.
     *
     * @param int                                                            $currentPage
     * @param int                                                            $perPage
     * @param null|array|\Closure|\Leevel\Database\Ddd\ISpecification|string $condition
     * @param bool                                                           $flag
     * @param bool                                                           $withTotal
     * @param string                                                         $column
     *
     * @return array
     */
    public function findPage(int $currentPage, int $perPage = 10, $condition = null, bool $flag = false, bool $withTotal = true, string $column = '*'): array
    {
        $select = $this->entity->selfDatabaseSelect();

        if ($condition) {
            $this->normalizeCondition($condition, $select);
        }

        return $select->page($currentPage, $perPage, $flag, $withTotal, $column);
    }

    /**
     * 分页查询.
     * 可以渲染 HTML.
     *
     * @param int                                                            $currentPage
     * @param int                                                            $perPage
     * @param null|array|\Closure|\Leevel\Database\Ddd\ISpecification|string $condition
     * @param bool                                                           $flag
     * @param string                                                         $column
     * @param array                                                          $option
     *
     * @return array
     */
    public function findPageHtml(int $currentPage, int $perPage = 10, $condition = null, bool $flag = false, string $column = '*', array $option = []): array
    {
        $select = $this->entity->selfDatabaseSelect();

        if ($condition) {
            $this->normalizeCondition($condition, $select);
        }

        return $select->pageHtml($currentPage, $perPage, $flag, $column, $option);
    }

    /**
     * 创建一个无限数据的分页查询.
     *
     * @param int                                                            $currentPage
     * @param int                                                            $perPage
     * @param null|array|\Closure|\Leevel\Database\Ddd\ISpecification|string $condition
     * @param bool                                                           $flag
     * @param array                                                          $option
     *
     * @return array
     */
    public function findPageMacro(int $currentPage, int $perPage = 10, $condition = null, bool $flag = false, array $option = []): array
    {
        $select = $this->entity->selfDatabaseSelect();

        if ($condition) {
            $this->normalizeCondition($condition, $select);
        }

        return $select->pageMacro($currentPage, $perPage, $flag, $option);
    }

    /**
     * 创建一个只有上下页的分页查询.
     *
     * @param int                                                            $currentPage
     * @param int                                                            $perPage
     * @param null|array|\Closure|\Leevel\Database\Ddd\ISpecification|string $condition
     * @param bool                                                           $flag
     * @param array                                                          $option
     *
     * @return array
     */
    public function findPagePrevNext(int $currentPage, int $perPage = 10, $condition = null, bool $flag = false, array $option = []): array
    {
        $select = $this->entity->selfDatabaseSelect();

        if ($condition) {
            $this->normalizeCondition($condition, $select);
        }

        return $select->pagePrevNext($currentPage, $perPage, $flag, $option);
    }

    /**
     * 条件查询器.
     *
     * @param array|\Closure|\Leevel\Database\Ddd\ISpecification|string $condition
     *
     * @return \Leevel\Database\Ddd\Select
     */
    public function condition($condition): Select
    {
        $select = $this->entity->selfDatabaseSelect();

        $this->normalizeCondition($condition, $select);

        return $select;
    }

    /**
     * 响应新建.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return mixed
     */
    public function create(IEntity $entity)
    {
        if ($entity->flushed()) {
            return false;
        }

        return $entity->create()->flush();
    }

    /**
     * 响应修改.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return mixed
     */
    public function update(IEntity $entity)
    {
        if ($entity->flushed()) {
            return false;
        }

        return $entity->update()->flush();
    }

    /**
     * 响应不存在则新增否则更新.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return mixed
     */
    public function replace(IEntity $entity)
    {
        if ($entity->flushed()) {
            return false;
        }

        return $entity->replace()->flush();
    }

    /**
     * 响应删除.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return mixed
     */
    public function delete(IEntity $entity)
    {
        if ($entity->flushed()) {
            return false;
        }

        return $entity->destroy()->flush();
    }

    /**
     * 重新载入.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     */
    public function refresh(IEntity $entity): void
    {
        $entity->refresh();
    }

    /**
     * 返回实体.
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    public function entity(): IEntity
    {
        return $this->entity;
    }

    /**
     * 处理查询条件.
     *
     * @param array|\Closure|\Leevel\Database\Ddd\ISpecification|string $condition
     * @param \Leevel\Database\Ddd\Select                               $select
     */
    protected function normalizeCondition($condition, Select $select): void
    {
        if (is_string($condition)) {
            $select->scope($condition);
        } elseif (is_object($condition) && $condition instanceof ISpecification) {
            $this->normalizeSpec($select, $condition);
        } elseif (is_object($condition) && $condition instanceof Closure) {
            $condition($select, $this->entity);
        } elseif (is_array($condition) && count($condition) >= 2) {
            $tmpCondition = array_shift($condition);
            $scope = $condition;

            $this->normalizeCondition($tmpCondition, $select);
            $select->scope($scope);
        } else {
            throw new InvalidArgumentException('Invalid condition type.');
        }
    }

    /**
     * 处理规约查询.
     *
     * @param \Leevel\Database\Ddd\Select         $select
     * @param \Leevel\Database\Ddd\ISpecification $spec
     */
    protected function normalizeSpec(Select $select, ISpecification $spec): void
    {
        if ($spec instanceof ISpecification && $spec->isSatisfiedBy($this->entity)) {
            $spec->handle($select, $this->entity);
        }
    }
}


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

use Gettext\Translations;

/**
 * 解析 po 文件.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.09.18
 *
 * @version 1.0
 */
class Po implements IGettext
{
    /**
     * 读取文件数据.
     *
     * @param array $filenames
     *
     * @return array
     */
    public function read(array $filenames): array
    {
        $translations = new Translations();

        foreach ($filenames as $val) {
            $translations->addFromPoFile($val);
        }

        $result = json_decode($translations->toJsonString(), true);
        $result = $result['messages'][''] ?? [];

        $result = array_map(function ($item) {
            return $item[0];
        }, $result);

        return $result;
    }
}


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

/**
 * phpui 模板处理类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.11.21
 *
 * @version 1.0
 */
class Phpui extends Connect implements IConnect
{
    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'theme_path'            => '',
        'suffix'                => '.php',
    ];

    /**
     * 加载视图文件.
     *
     * @param string $file    视图文件地址
     * @param array  $vars
     * @param string $ext     后缀
     * @param bool   $display 是否显示
     *
     * @return string|void
     */
    public function display(string $file, array $vars = [], ?string $ext = null, bool $display = true)
    {
        // 加载视图文件
        $file = $this->parseDisplayFile($file, $ext);

        // 变量赋值
        if ($vars) {
            $this->setVar($vars);
        }

        if (is_array($this->vars) && !empty($this->vars)) {
            extract($this->vars, EXTR_PREFIX_SAME, 'q_');
        }

        // 返回类型
        if (false === $display) {
            ob_start();
            include $file;
            $result = ob_get_contents();
            ob_end_clean();

            return $result;
        }

        include $file;
    }
}


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

use Leevel\Debug\Debug;
use Leevel\Di\IContainer;
use Leevel\Di\Provider;

/**
 * debug 服务提供者.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.09.20
 *
 * @version 1.0
 */
class Register extends Provider
{
    /**
     * 注册服务.
     */
    public function register(): void
    {
        $this->debug();
        $this->middleware();
    }

    /**
     * 可用服务提供者.
     *
     * @return array
     */
    public static function providers(): array
    {
        return [
            'debug' => [
                'Leevel\\Debug\\Debug',
            ],
            'Leevel\\Debug\\Middleware\\Debug',
        ];
    }

    /**
     * {@inheritdoc}
     */
    public static function isDeferred(): bool
    {
        return true;
    }

    /**
     * 注册 debug 服务.
     */
    protected function debug(): void
    {
        $this->container->singleton('debug', function (IContainer $container) {
            return new Debug($container, $container['option']->get('debug\\'));
        });
    }

    /**
     * 注册 middleware 服务.
     */
    protected function middleware(): void
    {
        $this->container->singleton('Leevel\\Debug\\Middleware\\Debug');
    }
}


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

/**
 * header bag.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.02.25
 *
 * @version 1.0
 */
class HeaderBag extends Bag
{
    /**
     * 构造函数.
     *
     * @param array $elements
     */
    public function __construct(array $elements = [])
    {
        $this->add($elements);
    }

    /**
     * 格式化 header 字符串.
     *
     * @return string
     */
    public function __toString(): string
    {
        if (!$headers = $this->all()) {
            return '';
        }

        ksort($headers);

        $content = '';

        foreach ($headers as $name => $value) {
            $name = ucwords($name, '-');
            $content .= $name.': '.$value."\r\n";
        }

        return $content;
    }

    /**
     * {@inheritdoc}
     */
    public function replace(array $elements = []): void
    {
        $this->elements = [];

        $this->add($elements);
    }

    /**
     * {@inheritdoc}
     */
    public function add(array $elements = []): void
    {
        foreach ($elements as $key => $value) {
            $this->set($key, $value);
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function normalize(string $key): string
    {
        $key = str_replace('_', '-', strtolower($key));

        return $key;
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Database\Ddd\Relation;

use Leevel\Collection\Collection;
use Leevel\Database\Ddd\IEntity;

/**
 * 关联模型实体 ManyMany.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.09.28
 *
 * @version 1.0
 */
class ManyMany extends Relation
{
    /**
     * 中间表模型实体.
     *
     * @var \Leevel\Database\Ddd\IEntity
     */
    protected $middleEntity;

    /**
     * 目标中间表关联字段.
     *
     * @var string
     */
    protected $middleTargetKey;

    /**
     * 源中间表关联字段.
     *
     * @var string
     */
    protected $middleSourceKey;

    /**
     * 构造函数.
     *
     * @param \Leevel\Database\Ddd\IEntity $targetEntity
     * @param \Leevel\Database\Ddd\IEntity $sourceEntity
     * @param \Leevel\Database\Ddd\IEntity $middleEntity
     * @param string                       $targetKey
     * @param string                       $sourceKey
     * @param string                       $middleTargetKey
     * @param string                       $middleSourceKey
     */
    public function __construct(IEntity $targetEntity, IEntity $sourceEntity, IEntity $middleEntity, $targetKey, $sourceKey, $middleTargetKey, $middleSourceKey)
    {
        $this->middleEntity = $middleEntity;
        $this->middleTargetKey = $middleTargetKey;
        $this->middleSourceKey = $middleSourceKey;

        parent::__construct($targetEntity, $sourceEntity, $targetKey, $sourceKey);
    }

    /**
     * 关联基础查询条件.
     */
    public function addRelationCondition(): void
    {
        if (static::$relationCondition) {
            $this->select
                ->join($this->middleEntity->table(), [
                    'middle_'.$this->middleTargetKey => $this->middleTargetKey,
                    'middle_'.$this->middleSourceKey => $this->middleSourceKey, ], [
                        $this->middleTargetKey       => '{['.$this->targetEntity->table().'.'.$this->targetKey.']}',
                    ])
                ->where(
                    $this->middleEntity->table().'.'.$this->middleSourceKey,
                    $this->getSourceValue()
                )
                ->asDefault()
                ->asCollection(false);
        }
    }

    /**
     * 设置预载入关联查询条件.
     *
     * @param \Leevel\Database\Ddd\IEntity[] $entitys
     */
    public function preLoadCondition(array $entitys): void
    {
        $this->select
            ->join($this->middleEntity->table(), [
                'middle_'.$this->middleTargetKey => $this->middleTargetKey,
                'middle_'.$this->middleSourceKey => $this->middleSourceKey, ], [
                    $this->middleTargetKey       => '{['.$this->targetEntity->table().'.'.$this->targetKey.']}',
                ])
            ->whereIn(
                $this->middleEntity->table().'.'.$this->middleSourceKey,
                $this->getPreLoadSourceValue($entitys)
            )
            ->asDefault()
            ->asCollection(false);
    }

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

        foreach ($entitys as $entity) {
            $key = $entity->__get($this->sourceKey);

            if (isset($maps[$key])) {
                $entity->withRelationProp(
                    $relation,
                    $this->targetEntity->collection($maps[$key])
                );
            }
        }

        return $entitys;
    }

    /**
     * 取回源模型实体对应数据.
     *
     * @return mixed
     */
    public function getSourceValue()
    {
        return $this->sourceEntity->__get($this->sourceKey);
    }

    /**
     * 查询关联对象.
     *
     * @return mixed
     */
    public function sourceQuery()
    {
        $tmps = $this->select->findAll();

        if (!$tmps) {
            return new Collection();
        }

        $result = [];

        $middelClass = get_class($this->middleEntity);
        $targetClass = get_class($this->targetEntity);

        foreach ($tmps as $value) {
            $value = (array) $value;

            $middleEnity = new $middelClass([
                $this->middleSourceKey => $value['middle_'.$this->middleSourceKey],
                $this->middleTargetKey => $value['middle_'.$this->middleTargetKey],
            ]);

            unset(
                $value['middle_'.$this->middleSourceKey],
                $value['middle_'.$this->middleTargetKey]
            );

            $targetEntity = new $targetClass($value);

            $targetEntity->withMiddle($middleEnity);

            $result[] = $targetEntity;
        }

        return new Collection($result, [$targetClass]);
    }

    /**
     * 取得预载入关联模型实体.
     *
     * @return mixed
     */
    public function getPreLoad()
    {
        return $this->targetEntity
            ->select()
            ->preLoadResult(
                $this->sourceQuery()
            );
    }

    /**
     * 取得中间表模型实体.
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    public function getMiddleEntity(): IEntity
    {
        return $this->middleEntity;
    }

    /**
     * 取得目标中间表关联字段.
     *
     * @return string
     */
    public function getMiddleTargetKey(): string
    {
        return $this->middleTargetKey;
    }

    /**
     * 取得源中间表关联字段.
     *
     * @return string
     */
    public function getMiddleSourceKey(): string
    {
        return $this->middleSourceKey;
    }

    /**
     * 取回源模型实体对应数据.
     *
     * @return array
     */
    protected function getPreLoadSourceValue(array $entitys): array
    {
        $arr = [];

        foreach ($entitys as $sourceEntity) {
            $arr[] = $sourceEntity->__get($this->sourceKey);
        }

        return $arr;
    }

    /**
     * 模型实体隐射数据.
     *
     * @param \Leevel\Collection\Collection $result
     *
     * @return array
     */
    protected function buildMap(Collection $result): array
    {
        $maps = [];

        foreach ($result as $entity) {
            $maps[$entity->middle()->__get($this->middleSourceKey)][] = $entity;
        }

        return $maps;
    }
}


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

use PDO;

/**
 * mysql 数据库连接.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.03.09
 *
 * @version 1.0
 */
class Mysql extends Connect implements IConnect
{
    /**
     * dsn 解析.
     *
     * @param array $option
     *
     * @return string
     */
    public function parseDsn(array $option): string
    {
        $dsn = [];

        foreach (['Base', 'Port', 'Socket', 'Charset'] as $method) {
            $dsn[] = $this->{'parse'.$method}($option);
        }

        return implode('', $dsn);
    }

    /**
     * 取得数据库表名列表.
     *
     * @param string   $dbName
     * @param bool|int $master
     *
     * @return array
     */
    public function tableNames(string $dbName, $master = false): array
    {
        $sql = 'SHOW TABLES FROM '.$this->normalizeTableOrColumn($dbName);

        $result = [];

        if (($tables = $this->query($sql, [], $master, PDO::FETCH_ASSOC))) {
            foreach ($tables as $v) {
                $result[] = reset($v);
            }
        }

        unset($tables, $sql);

        return $result;
    }

    /**
     * 取得数据库表字段信息.
     *
     * @param string   $tableName
     * @param bool|int $master
     *
     * @return array
     */
    public function tableColumns(string $tableName, $master = false): array
    {
        $sql = 'SHOW FULL COLUMNS FROM '.
            $this->normalizeTableOrColumn($tableName);

        $result = [
            'list'           => [],
            'primary_key'    => null,
            'auto_increment' => null,
        ];

        if (($columns = $this->query($sql, [], $master, PDO::FETCH_ASSOC))) {
            foreach ($columns as $column) {
                $tmp = [];
                $tmp['name'] = $column['Field'];

                if (preg_match('/(.+)\((.+)\)/', $column['Type'], $matche)) {
                    $tmp['type'] = $matche[1];
                    $tmp['length'] = $matche[1];
                } else {
                    $tmp['type'] = $column['Type'];
                    $tmp['length'] = null;
                }

                $tmp['primary_key'] = 'pri' === strtolower($column['Key']);
                $tmp['auto_increment'] = false !== strpos($column['Extra'], 'auto_increment');

                if (null !== $column['Default'] &&
                    'null' !== strtolower($column['Default'])) {
                    $tmp['default'] = $column['Default'];
                } else {
                    $tmp['default'] = null;
                }

                $tmp['comment'] = $column['Comment'];

                $result['list'][$tmp['name']] = $tmp;

                if ($tmp['auto_increment']) {
                    $result['auto_increment'] = $tmp['name'];
                }

                if ($tmp['primary_key']) {
                    if (!is_array($result['primary_key'])) {
                        $result['primary_key'] = [];
                    }

                    $result['primary_key'][] = $tmp['name'];
                }
            }
        }

        unset($columns, $sql);

        return $result;
    }

    /**
     * sql 字段格式化.
     *
     * @param mixed $name
     *
     * @return string
     */
    public function identifierColumn($name): string
    {
        return '*' !== $name ? "`{$name}`" : '*';
    }

    /**
     * 分析 limit.
     *
     * @param null|int $limitCount
     * @param null|int $limitOffset
     *
     * @return string
     */
    public function limitCount(?int $limitCount = null, ?int $limitOffset = null): string
    {
        if (null !== $limitOffset) {
            $sql = 'LIMIT '.$limitOffset;

            if (null !== $limitCount) {
                $sql .= ','.$limitCount;
            } else {
                $sql .= ',999999999999';
            }

            return $sql;
        }

        if (null !== $limitCount) {
            return 'LIMIT '.$limitCount;
        }

        return '';
    }

    /**
     * 获取是否启用部分事务
     *
     * @return bool
     */
    public function hasSavepoints(): bool
    {
        return $this->transactionWithSavepoints;
    }

    /**
     * 基本.
     *
     * @param array $option
     *
     * @return string
     */
    protected function parseBase(array $option): string
    {
        return 'mysql:dbname='.$option['name'].';host='.$option['host'];
    }

    /**
     * 端口.
     *
     * @param array $option
     *
     * @return string
     */
    protected function parsePort(array $option): string
    {
        if (empty($option['port'])) {
            return '';
        }

        return ';port='.$option['port'];
    }

    /**
     * 用 unix socket 加速 php-fpm、mysql、redis 连接.
     *
     * @param array $option
     *
     * @return string
     */
    protected function parseSocket(array $option): string
    {
        if (empty($option['socket'])) {
            return '';
        }

        return ';unix_socket='.$option['socket'];
    }

    /**
     * 编码
     *
     * @param array $option
     *
     * @return string
     */
    protected function parseCharset(array $option): string
    {
        if (empty($option['charset'])) {
            return '';
        }

        return ';charset='.$option['charset'];
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Throttler;

use Leevel\Cache\ICache;
use Leevel\Http\IRequest;
use RuntimeException;

/**
 * throttler 入口.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.08.07
 *
 * @version 1.0
 */
class Throttler implements IThrottler
{
    /**
     * 节流器实例.
     *
     * @var \Leevel\Throttler\RateLimiter[]
     */
    protected $rateLimiter = [];

    /**
     * cache.
     *
     * @var \Leevel\Cache\ICache
     */
    protected $cache;

    /**
     * http request.
     *
     * @var \Leevel\Http\IRequest
     */
    protected $request;

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

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

    /**
     * 创建一个节流器.
     *
     * @param null|string $key
     * @param int         $limit
     * @param int         $time
     *
     * @return \Leevel\Throttler\IRateLimiter
     */
    public function create(?string $key = null, int $limit = 60, int $time = 60): IRateLimiter
    {
        $key = $this->getRequestKey($key);

        if (isset($this->rateLimiter[$key])) {
            return $this->rateLimiter[$key]->
            limit($limit)->

            time($time);
        }

        return $this->rateLimiter[$key] = new RateLimiter(
            $this->cache, $key, $limit, $time
        );
    }

    /**
     * 设置 http request.
     *
     * @param \Leevel\Http\IRequest $request
     *
     * @return $this
     */
    public function setRequest(IRequest $request): IThrottler
    {
        $this->request = $request;

        return $this;
    }

    /**
     * 获取请求 key.
     *
     * @param null|string $key
     *
     * @return string
     */
    public function getRequestKey(?string $key = null): string
    {
        if (!$key && !$this->request) {
            throw new RuntimeException('Request is not set.');
        }

        return $key ?: sha1(
            $this->request->getClientIp().
            '@'.
            $this->request->getRoot()
        );
    }
}


<?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\Console\Option;
use Leevel\Kernel\IApp;
use Symfony\Component\Process\PhpExecutableFinder;

/**
 * PHP 自带的服务器端.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.09.01
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
class Server extends Command
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'server';

    /**
     * 命令行描述.
     *
     * @var string
     */
    protected $description = 'Start php build-in server.';

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

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

        $this->line("<info>The QueryPHP server started:</info> <http://{$this->host()}:{$this->port()}>");

        $this->table(['key', 'value'], [
            ['php', $this->php()],
            ['server', $this->server()],
        ]);

        passthru($this->normalizeCommand(), $status);
    }

    /**
     * 取得命令.
     *
     * @return string
     */
    protected function normalizeCommand(): string
    {
        return sprintf(
            '%s -S %s:%d -t %s',
            escapeshellarg($this->php()),
            $this->host(),
            $this->port(),
            escapeshellarg($this->server())
        );
    }

    /**
     * 取得主机.
     *
     * @return string
     */
    protected function host(): string
    {
        return $this->option('host');
    }

    /**
     * 取得端口.
     *
     * @return string
     */
    protected function port(): string
    {
        return $this->option('port');
    }

    /**
     * 取得服务入口.
     *
     * @return string
     */
    protected function server(): string
    {
        return $this->option('server') ?: $this->app->path('www');
    }

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

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

    /**
     * 命令配置.
     *
     * @return array
     */
    protected function getOptions(): array
    {
        return [
            [
                'host',
                null,
                Option::VALUE_OPTIONAL,
                'The host address to be listening on.',
                '127.0.0.1',
            ],
            [
                'port',
                null,
                Option::VALUE_OPTIONAL,
                'The port to be listening on.',
                '9527',
            ],
            [
                'server',
                null,
                Option::VALUE_OPTIONAL,
                'The server enter.',
                null,
            ],
            [
                'php',
                null,
                Option::VALUE_OPTIONAL,
                'Where is php.',
                (new PhpExecutableFinder())->find(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\Protocol\Client;

use Leevel\Http\IResponse;
use Leevel\Http\JsonResponse;
use Leevel\Http\RedirectResponse;
use Leevel\Http\Response as HttpResponse;
use Leevel\Protocol\Thrift\Service\Request;
use Leevel\Protocol\Thrift\Service\Response;
use Leevel\Protocol\Thrift\Service\ThriftClient;
use Thrift\Protocol\TBinaryProtocol;
use Thrift\Transport\TFramedTransport;
use Thrift\Transport\TSocket;

/**
 * Rpc 客户端.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.04.03
 *
 * @version 1.0
 */
class Rpc
{
    /**
     * 正确响应为 200
     * 对应 HTTP 状态码
     *
     * @var int
     */
    const OK = 200;

    /**
     * 服务端和客户端的享元数据.
     *
     * @var array
     */
    protected $metas = [];

    /**
     * 构造函数.
     *
     * @param string $id
     */
    public function __construct()
    {
    }

    /**
     * 是否处于协程上下文.
     *
     * @return bool
     */
    public static function coroutineContext(): bool
    {
        return true;
    }

    /**
     * Rpc 调用.
     *
     * @param string $call
     * @param array  $params
     * @param array  $metas
     *
     * @return \Leevel\Http\IResponse
     */
    public function call(string $call, array $params = [], array $metas = []): IResponse
    {
        $transport = $this->makeTransport();
        $protocol = new TBinaryProtocol($transport);
        $transport->open();

        $response = $this->getResponseWithProtocol($protocol, [
            'call'   => $call,
            'params' => $params,
            'metas'  => $metas,
        ]);

        $transport->close();

        $response = $this->normalizeResponse($response);

        return $response;
    }

    /**
     * 设置享元数据.
     *
     * @param array $metas;
     */
    public function setMetas(array $metas): array
    {
        $this->metas = $metas;
    }

    /**
     * 返回享元数据.
     *
     * @return array
     */
    public function getMetas(): array
    {
        return $this->metas;
    }

    /**
     * 添加享元数据.
     *
     * @param array|string $key
     * @param mixed        $value
     */
    public function addMetas($key, $value = null)
    {
        $key = is_array($key) ? $key : [
            $key => $value,
        ];

        foreach ($key as $k => $v) {
            $this->metas[$k] = $v;
        }
    }

    /**
     * 格式化 Thrift Rpc 响应到 QueryPHP 响应.
     *
     * @param \Leevel\Protocol\Thrift\Service\Response $response
     *
     * @return \Leevel\Http\IResponse
     */
    protected function normalizeResponse(Response $response): IResponse
    {
        if ($this->isJson($response->data)) {
            $data = json_decode($response->data, true);

            if (isset($data['target_url'])) {
                return new RedirectResponse($data['target_url'], $response->status);
            }

            return JsonResponse::fromJsonString($response->data, $response->status);
        }

        return new HttpResponse($response->data, $response->status);
    }

    /**
     * 创建传输层
     *
     * @return \Thrift\Transport\TFramedTransport
     */
    protected function makeTransport(): TFramedTransport
    {
        $socket = new TSocket('127.0.0.1', 1355);

        return new TFramedTransport($socket);
    }

    /**
     * 根据协议返回响应.
     *
     * @param \Thrift\Protocol\TBinaryProtocol $protocol
     * @param array                            $data
     *
     * @return \Leevel\Protocol\Thrift\Service\Response
     */
    protected function getResponseWithProtocol(TBinaryProtocol $protocol, array $data): Response
    {
        $client = new ThriftClient($protocol);

        $message = new Request($data);

        return $client->call($message);
    }

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

        json_decode((string) ($data));

        return JSON_ERROR_NONE === json_last_error();
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Database\Ddd;

use Closure;
use InvalidArgumentException;
use Leevel\Database\Manager as DatabaseManager;
use Leevel\Database\Select as DatabaseSelect;

/**
 * 数据库元对象
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.04.27
 *
 * @version 1.0
 */
class Meta implements IMeta
{
    /**
     * Database 管理.
     *
     * @var \Leevel\Database\Manager
     */
    protected static $resolveDatabase;

    /**
     * Database 管理.
     *
     * @var \Closure
     */
    protected static $databaseResolver;

    /**
     * meta 对象实例.
     *
     * @var \Leevel\Database\Ddd\IMeta[]
     */
    protected static $instances = [];

    /**
     * 元对象表.
     *
     * @var string
     */
    protected $table;

    /**
     * 表连接.
     *
     * @var \Leevel\Database\IConnect
     */
    protected $connect;

    /**
     * 构造函数
     * 禁止直接访问构造函数，只能通过 instance 生成对象
     *
     * @param string $table
     */
    protected function __construct(string $table)
    {
        $this->table = $table;
    }

    /**
     * 返回数据库元对象
     *
     * @param string $table
     *
     * @return $this
     */
    public static function instance(string $table): IMeta
    {
        if (!isset(static::$instances[$table])) {
            return static::$instances[$table] = new static($table);
        }

        return static::$instances[$table];
    }

    /**
     * 返回数据库管理对象.
     *
     * @return \Leevel\Database\Manager
     */
    public static function resolveDatabase(): DatabaseManager
    {
        if (static::$resolveDatabase) {
            return static::$resolveDatabase;
        }

        if (!static::$databaseResolver &&
            static::lazyloadPlaceholder() && !static::$databaseResolver) {
            throw new InvalidArgumentException('Database resolver was not set.');
        }

        return static::$resolveDatabase = call_user_func(static::$databaseResolver);
    }

    /**
     * 设置数据库管理对象.
     *
     * @param null|\Closure $databaseResolver
     */
    public static function setDatabaseResolver(?Closure $databaseResolver = null): void
    {
        static::$databaseResolver = $databaseResolver;

        if (null === $databaseResolver) {
            static::$resolveDatabase = null;
        }
    }

    /**
     * 返回数据库元对象连接.
     *
     * @param mixed $connect
     *
     * @return $this
     */
    public function setConnect($connect = null): IMeta
    {
        $this->connect = self::resolveDatabase()->connect($connect);

        return $this;
    }

    /**
     * 新增数据并返回上一次插入 ID.
     *
     * @param array $saveData
     *
     * @return mixed
     */
    public function insert(array $saveData)
    {
        return $this->select()->insert($saveData);
    }

    /**
     * 更新数据并返回影响行数.
     *
     * @param array $condition
     * @param array $saveData
     *
     * @return int
     */
    public function update(array $condition, array $saveData): int
    {
        return $this->select()
            ->where($condition)
            ->update($saveData);
    }

    /**
     * 删除数据并返回影响行数.
     *
     * @param array $condition
     *
     * @return int
     */
    public function delete(array $condition): int
    {
        return $this->select()
            ->where($condition)
            ->delete();
    }

    /**
     * 返回查询.
     *
     * @var \Leevel\Database\Select
     */
    public function select(): DatabaseSelect
    {
        return $this->connect->table($this->table);
    }

    /**
     * 延迟载入占位符.
     *
     * @return bool
     */
    protected static function lazyloadPlaceholder(): bool
    {
        return Lazyload::placeholder();
    }
}


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

/**
 * connect 驱动抽象类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.09.07
 *
 * @version 1.0
 */
abstract class Connect
{
    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'token' => null,
    ];

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

    /**
     * 用户是否已经登录.
     *
     * @return bool
     */
    public function isLogin(): bool
    {
        return $this->getLogin() ? true : false;
    }

    /**
     * 获取登录信息.
     *
     * @return array
     */
    public function getLogin(): array
    {
        return $this->tokenData();
    }

    /**
     * 登录写入数据.
     *
     * @param array $data
     * @param int   $loginTime
     */
    public function login(array $data, int $loginTime = 0): void
    {
        $this->tokenPersistence($data, $loginTime);
    }

    /**
     * 登出.
     */
    public function logout(): void
    {
        $this->tokenDelete();
    }

    /**
     * 设置认证名字.
     *
     * @param string $tokenName
     */
    public function setTokenName(string $tokenName): void
    {
        $this->option['token'] = $tokenName;
    }

    /**
     * 取得认证名字.
     *
     * @return string
     */
    public function getTokenName(): string
    {
        if (!$this->option['token']) {
            throw new AuthException('Token name was not set.');
        }

        return $this->option['token'];
    }

    /**
     * 认证信息持久化.
     *
     * @param array $data
     * @param int   $loginTime
     */
    protected function tokenPersistence(array $data, int $loginTime = 0): void
    {
        $this->setPersistence($this->getTokenName(), json_encode($data), $loginTime);
    }

    /**
     * 认证信息获取.
     *
     * @return array
     */
    protected function tokenData(): array
    {
        $data = $this->getPersistence($this->getTokenName());

        return $data ? json_decode($data, true) : [];
    }

    /**
     * 认证信息删除.
     */
    protected function tokenDelete(): void
    {
        $this->deletePersistence($this->getTokenName());
    }
}


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

use Leevel\Console\Command;
use Leevel\Filesystem\Fso\create_file;
use function Leevel\Filesystem\Fso\create_file;
use function Leevel\Filesystem\Fso\list_directory;
use Leevel\Filesystem\Fso\list_directory;
use Leevel\I18n\Load;
use Leevel\Kernel\Bootstrap\LoadI18n;
use Leevel\Kernel\IApp;

/**
 * 语言包缓存.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.05.06
 *
 * @version 1.0
 */
class Cache extends Command
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'i18n:cache';

    /**
     * 命令行描述.
     *
     * @var string
     */
    protected $description = 'Cache i18n to a file.';

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

    /**
     * 扩展语言包目录.
     *
     * @var array
     */
    protected $extends;

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

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

        list_directory($app->i18nPath(), false, function ($item) use ($app) {
            if ($item->isDir()) {
                $i18n = $item->getFilename();

                $data = $this->data($i18n);

                $cachePath = $app->i18nCachedPath($i18n);

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

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

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

    /**
     * 获取语言包扩展.
     *
     * @return array
     */
    protected function extends(): array
    {
        return (new LoadI18n())->getExtend($this->app);
    }

    /**
     * 获取语言数据.
     *
     * @param string $i18n
     *
     * @return array
     */
    protected function data(string $i18n): array
    {
        $load = (new Load([$this->app->i18nPath()]))
            ->setI18n($i18n)
            ->addDir($this->extends);

        return $load->loadData();
    }

    /**
     * 写入缓存.
     *
     * @param string $cachePath
     * @param array  $data
     */
    protected function writeCache(string $cachePath, array $data): void
    {
        $content = '<?'.'php /* '.date('Y-m-d H:i:s').' */ ?'.'>'.
            PHP_EOL.'<?'.'php return '.var_export($data, true).'; ?'.'>';

        create_file($cachePath, $content);
    }

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

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

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

use Leevel\I18n\Helper\gettext;
use function Leevel\I18n\Helper\gettext as __;

/**
 * 默认分页渲染.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.07.14
 *
 * @version 1.0
 */
class Defaults implements IRender
{
    /**
     * 分页.
     *
     * @var \Leevel\Page\IPage
     */
    protected $page;

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'small'          => false,
        'template'       => '{header} {total} {prev} {ul} {first} {main} {last} {endul} {next} {jump} {footer}',
        'small_template' => false,
    ];

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

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

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

        return $this;
    }

    /**
     * 简单渲染.
     *
     * @return $this
     */
    public function setSimpleTemplate(): IRender
    {
        return $this->setOption(
            'template',
            '{header} {prev} {ul} {first} {main} {last} {endul} {next} {footer}'
        );
    }

    /**
     * 渲染.
     *
     * @param array $option
     *
     * @return string
     */
    public function render(array $option = []): string
    {
        if ($option) {
            $this->option = array_merge($this->option, $option);
            $this->intOption();
        }

        return preg_replace_callback(
            '/{(.+?)}/',
            function ($matches) {
                return $this->{'get'.ucwords($matches[1]).'Render'}();
            },
            $this->option['template']
        );
    }

    /**
     * 替换分页变量.
     *
     * @param int|string $page
     *
     * @return string
     */
    public function replace($page): string
    {
        return $this->page->pageReplace($page);
    }

    /**
     * 初始化配置.
     */
    protected function intOption(): void
    {
        if ($this->option['small_template']) {
            $this->setSimpleTemplate();
        }
    }

    /**
     * 返回渲染 header.
     *
     * @return string
     */
    protected function getHeaderRender(): string
    {
        return sprintf(
            '
',
            $this->option['small'] ? ' pagination-small' : ''
        );
    }

    /**
     * 返回渲染 pager.ul.
     *
     * @return string
     */
    protected function getUlRender(): string
    {
        return '
';
    }

    /**
     * 返回渲染 total.
     *
     * @return string
     */
    protected function getTotalRender(): string
    {
        if (!$this->page->canTotalRender()) {
            return '';
        }

        return sprintf(
            '%s',
            __('共 %d 条', $this->page->getTotalRecord() ?: 0)
        );
    }

    /**
     * 返回渲染 first.
     *
     * @return string
     */
    protected function getFirstRender(): string
    {
        if (!$this->page->canFirstRender()) {
            return '';
        }

        return sprintf(
            '
1
'.
                '
...
',
            $this->replace(1),
            $this->replace(
                $this->page->parseFirstRenderPrev()
            )
        );
    }

    /**
     * 返回渲染 prev.
     *
     * @return string
     */
    protected function getPrevRender(): string
    {
        if ($this->page->canPrevRender()) {
            return sprintf(
                '<button class="btn-prev" '.
                    'onclick="window.location.href=\'%s\';">‹</button>',
                $this->replace(
                    $this->page->parsePrevRenderPrev()
                )
            );
        }

        return '<button class="btn-prev disabled">'.
            '‹</button>';
    }

    /**
     * 返回渲染 main.
     *
     * @return string
     */
    protected function getMainRender(): string
    {
        if (!$this->page->canMainRender()) {
            return '';
        }

        $result = '';

        for ($i = $this->page->getPageStart();
            $i <= $this->page->getPageEnd(); $i++) {
            $active = $this->page->getCurrentPage() === $i;

            $result .= sprintf(
                '
%d
',
                $active ? ' active' : '',
                $active ? '' : sprintf(' href="%s"', $this->replace($i)),
                $i
            );
        }

        return $result;
    }

    /**
     * 返回渲染 next.
     *
     * @return string
     */
    protected function getNextRender(): string
    {
        if ($this->page->canNextRender()) {
            return sprintf(
                '<button class="btn-next" '.
                    'onclick="window.location.href=\'%s\';">›</button>',
                $this->replace($this->page->getCurrentPage() + 1)
            );
        }

        return '<button class="btn-next disabled">'.
            '›</button>';
    }

    /**
     * 返回渲染 last.
     *
     * @return string
     */
    protected function getLastRender(): string
    {
        if ($this->page->isTotalMacro()) {
            return sprintf(
                '
...
',
                $this->replace(
                    $this->page->parseLastRenderNext()
                )
            );
        }

        if ($this->page->canLastRender()) {
            return ($this->page->canLastRenderNext() ?
                    sprintf('
...
',
                        $this->replace($this->page->parseLastRenderNext())) : '').
                sprintf(
                    '
%d
',
                    $this->replace($this->page->getTotalPage()),
                    $this->page->getTotalPage()
                );
        }

        return '';
    }

    /**
     * 返回渲染 pager.endul.
     *
     * @return string
     */
    protected function getEndulRender(): string
    {
        return '
';
    }

    /**
     * 返回渲染 jump.
     *
     * @return string
     */
    protected function getJumpRender(): string
    {
        return sprintf(
            '%s'.
                '<input type="number" link="%s" '.
                'onkeydown="var event = event || window.event; '.
                'if (event.keyCode == 13) { window.location.href = '.
                'this.getAttribute(\'link\').replace( \'{jump}\', this.value); }" '.
                'onfocus="this.select();" min="1" value="1" number="true" '.
                'class="pagination-editor">%s',
            __('前往'),
            $this->replace('{jump}'),
            __('页')
        );
    }

    /**
     * 返回渲染 footer.
     *
     * @return string
     */
    protected function getFooterRender(): string
    {
        return '
';
    }
}

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

use function Leevel\Support\Str\rand_alpha_num;
use Leevel\Support\Str\rand_alpha_num;
use RuntimeException;
use SessionHandlerInterface;

/**
 * session 仓储.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.04.17
 *
 * @version 1.0
 */
class Session implements ISession
{
    /**
     * session ID.
     * 相当于 session_id.
     *
     * @var string
     */
    protected $id;

    /**
     * session 名字.
     * 相当于 session_name.
     *
     * @var string
     */
    protected $name;

    /**
     * session handler.
     *
     * @var \SessionHandlerInterface
     */
    protected $connect;

    /**
     * session 是否开启.
     *
     * @var bool
     */
    protected $started = false;

    /**
     * session 数据.
     *
     * @var array
     */
    protected $datas = [];

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'id'             => null,
        'name'           => null,
    ];

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

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

        $this->setName($this->option['name']);
    }

    /**
     * 启动 session.
     *
     * @param string $sessionId
     */
    public function start(?string $sessionId = null): void
    {
        if ($this->isStart()) {
            return;
        }

        $this->setId($sessionId ?: $this->option['id']);

        $this->loadData();

        $this->started = true;
    }

    /**
     * 程序执行保存 session.
     */
    public function save(): void
    {
        if (!$this->isStart()) {
            throw new RuntimeException(
                'Session is not start yet.'
            );
        }

        $this->unregisterFlash();

        $this->connect->write($this->getId(), serialize($this->datas));

        $this->started = false;
    }

    /**
     * 取回所有 session 数据.
     *
     * @return array
     */
    public function all(): array
    {
        return $this->datas;
    }

    /**
     * 设置 session.
     *
     * @param string $name
     * @param mixed  $value
     */
    public function set(string $name, $value): void
    {
        $name = $this->getNormalizeName($name);

        $this->datas[$name] = $value;
    }

    /**
     * 批量插入.
     *
     * @param array|string $keys
     * @param mixed        $value
     */
    public function put($keys, $value = null): void
    {
        if (!is_array($keys)) {
            $keys = [
                $keys => $value,
            ];
        }

        foreach ($keys as $item => $value) {
            $this->set($item, $value);
        }
    }

    /**
     * 数组插入数据.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function push(string $key, $value): void
    {
        $arr = $this->get($key, []);
        $arr[] = $value;

        $this->set($key, $arr);
    }

    /**
     * 合并元素.
     *
     * @param string $key
     * @param array  $value
     */
    public function merge(string $key, array $value): void
    {
        $this->set($key, array_merge($this->get($key, []), $value));
    }

    /**
     * 弹出元素.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function pop(string $key, array $value): void
    {
        $this->set($key, array_diff($this->get($key, []), $value));
    }

    /**
     * 数组插入键值对数据.
     *
     * @param string $key
     * @param mixed  $keys
     * @param mixed  $value
     */
    public function arr(string $key, $keys, $value = null): void
    {
        $arr = $this->get($key, []);

        if (is_string($keys)) {
            $arr[$keys] = $value;
        } elseif (is_array($keys)) {
            $arr = array_merge($arr, $keys);
        }

        $this->set($key, $arr);
    }

    /**
     * 数组键值删除数据.
     *
     * @param string $key
     * @param mixed  $keys
     */
    public function arrDelete(string $key, $keys): void
    {
        $arr = $this->get($key, []);

        if (!is_array($keys)) {
            $keys = [
                $keys,
            ];
        }

        foreach ($keys as $item) {
            if (isset($arr[$item])) {
                unset($arr[$item]);
            }
        }

        $this->set($key, $arr);
    }

    /**
     * 取回 session.
     *
     * @param string $name
     * @param mixed  $value
     *
     * @return mixed
     */
    public function get(string $name, $value = null)
    {
        $name = $this->getNormalizeName($name);

        return $this->datas[$name] ?? $value;
    }

    /**
     * 返回数组部分数据.
     *
     * @param string $name
     * @param mixed  $value
     *
     * @return mixed
     */
    public function getPart(string $name, $value = null)
    {
        return $this->getPartData($name, $value);
    }

    /**
     * 删除 session.
     *
     * @param string $name
     */
    public function delete(string $name): void
    {
        $name = $this->getNormalizeName($name);

        if (isset($this->datas[$name])) {
            unset($this->datas[$name]);
        }
    }

    /**
     * 是否存在 session.
     *
     * @param string $name
     *
     * @return bool
     */
    public function has(string $name): bool
    {
        $name = $this->getNormalizeName($name);

        return isset($this->datas[$name]);
    }

    /**
     * 删除 session.
     */
    public function clear(): void
    {
        $this->datas = [];
    }

    /**
     * 闪存一个数据，当前请求和下一个请求可用.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function flash(string $key, $value): void
    {
        $this->set($this->flashDataKey($key), $value);

        $this->mergeNewFlash([
            $key,
        ]);

        $this->popOldFlash([
            $key,
        ]);
    }

    /**
     * 批量闪存数据，当前请求和下一个请求可用.
     *
     * @param array $flash
     */
    public function flashs(array $flash): void
    {
        foreach ($flash as $key => $value) {
            $this->flash($key, $value);
        }
    }

    /**
     * 闪存一个 flash 用于当前请求使用,下一个请求将无法获取.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function nowFlash(string $key, $value): void
    {
        $this->set($this->flashDataKey($key), $value);

        $this->mergeOldFlash([
            $key,
        ]);
    }

    /**
     * 批量闪存数据,用于当前请求使用，下一个请求将无法获取.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function nowFlashs(array $flash): void
    {
        foreach ($flash as $key => $value) {
            $this->nowFlash($key, $value);
        }
    }

    /**
     * 保持所有闪存数据.
     */
    public function rebuildFlash(): void
    {
        $this->mergeNewFlash(
            $this->get($this->flashOldKey(), [])
        );

        $this->set($this->flashOldKey(), []);
    }

    /**
     * 保持闪存数据.
     *
     * @param array $keys
     */
    public function keepFlash(array $keys): void
    {
        $this->mergeNewFlash($keys);

        $this->popOldFlash($keys);
    }

    /**
     * 返回闪存数据.
     *
     * @param string $key
     * @param mixed  $defaults
     *
     * @return mixed
     */
    public function getFlash(string $key, $defaults = null)
    {
        if (false !== strpos($key, '\\')) {
            return $this->getPartData($key, $defaults, 'flash');
        }

        return $this->get(
            $this->flashDataKey($key),
            $defaults
        );
    }

    /**
     * 删除闪存数据.
     *
     * @param array $keys
     */
    public function deleteFlash(array $keys): void
    {
        foreach ($keys as $item) {
            $this->delete($this->flashDataKey($item));
        }

        $this->mergeOldFlash($keys);

        $this->popNewFlash($keys);
    }

    /**
     * 清理所有闪存数据.
     */
    public function clearFlash(): void
    {
        $this->deleteFlash($this->get($this->flashNewKey(), []));
    }

    /**
     * 程序执行结束清理 flash.
     */
    public function unregisterFlash(): void
    {
        $arr = $this->get($this->flashNewKey(), []);
        $old = $this->get($this->flashOldKey(), []);

        foreach ($old as $item) {
            $this->delete($this->flashDataKey($item));
        }

        $this->delete($this->flashNewKey());
        $this->set($this->flashOldKey(), $arr);

        unset($arr, $old);
    }

    /**
     * 获取前一个请求地址.
     *
     * @return null|string
     */
    public function prevUrl(): ?string
    {
        return $this->get($this->prevUrlKey());
    }

    /**
     * 设置前一个请求地址
     *
     * @param string $url
     */
    public function setPrevUrl(string $url): void
    {
        $this->set($this->prevUrlKey(), $url);
    }

    /**
     * 终止会话.
     */
    public function destroy(): void
    {
        $this->clear();
        $this->connect->destroy($this->getId());

        $this->id = null;
        $this->started = false;
    }

    /**
     * session 是否已经启动.
     *
     * @return bool
     */
    public function isStart(): bool
    {
        return $this->started;
    }

    /**
     * 设置 SESSION 名字.
     *
     * @param string $name
     */
    public function setName(?string $name = null): void
    {
        $this->name = $name ?: static::SESSION_NAME;
    }

    /**
     * 取得 SESSION 名字.
     *
     * @return null|string
     */
    public function getName(): ?string
    {
        return $this->name;
    }

    /**
     * 设置 SESSION ID.
     *
     * @param string $id
     */
    public function setId(?string $id = null): void
    {
        $this->id = $id ?: $this->generateSessionId();
    }

    /**
     * 取得 SESSION ID.
     *
     * @return null|string
     */
    public function getId(): ?string
    {
        return $this->id;
    }

    /**
     * 重新生成 SESSION ID.
     *
     * @return string
     */
    public function regenerateId(): string
    {
        return $this->id = $this->generateSessionId();
    }

    /**
     * 返回连接.
     *
     * @return \SessionHandlerInterface
     */
    public function getConnect(): SessionHandlerInterface
    {
        return $this->connect;
    }

    /**
     * 生成 SESSION ID.
     *
     * @return string
     */
    protected function generateSessionId(): string
    {
        return sha1($this->parseMicrotime().'.'.time().'.'.rand_alpha_num(32));
    }

    /**
     * 生成微秒数.
     *
     * @return string
     */
    protected function parseMicrotime(): string
    {
        list($usec, $sec) = explode(' ', microtime());

        return (string) ((float) $usec + (float) $sec);
    }

    /**
     * 返回 session 名字.
     *
     * @param string $name
     *
     * @return string
     */
    protected function getNormalizeName(string $name): string
    {
        return $name;
    }

    /**
     * 载入 session 数据.
     */
    protected function loadData(): void
    {
        $this->datas = array_merge($this->datas, $this->loadDataFromConnect());
    }

    /**
     * 从驱动载入 session 数据.
     *
     * @return array
     */
    protected function loadDataFromConnect(): array
    {
        return unserialize($this->connect->read($this->getId()));
    }

    /**
     * 弹出旧闪存 KEY.
     *
     * @param array $keys
     */
    protected function popOldFlash(array $keys): void
    {
        $this->pop($this->flashOldKey(), $keys);
    }

    /**
     * 合并旧闪存 KEY.
     *
     * @param array $keys
     */
    protected function mergeOldFlash(array $keys): void
    {
        $this->merge($this->flashOldKey(), $keys);
    }

    /**
     * 弹出新闪存 KEY.
     *
     * @param array $keys
     */
    protected function popNewFlash(array $keys): void
    {
        $this->pop($this->flashNewKey(), $keys);
    }

    /**
     * 合并新闪存 KEY.
     *
     * @param array $keys
     */
    protected function mergeNewFlash(array $keys): void
    {
        $this->merge($this->flashNewKey(), $keys);
    }

    /**
     * 返回部分闪存数据.
     *
     * @param string $key
     * @param mixed  $defaults
     * @param string $type
     *
     * @return mixed
     */
    protected function getPartData(string $key, $defaults = null, ?string $type = null)
    {
        list($key, $name) = explode('\\', $key);

        if ('flash' === $type) {
            $key = $this->flashDataKey($key);
        }

        $value = $this->get($key);

        if (is_array($value)) {
            if (!strpos($name, '.')) {
                return array_key_exists($name, $value) ? $value[$name] : $defaults;
            }

            $parts = explode('.', $name);

            foreach ($parts as $part) {
                if (!isset($value[$part])) {
                    return $defaults;
                }

                $value = $value[$part];
            }

            return $value;
        }

        return $defaults;
    }

    /**
     * 闪存值 KEY.
     *
     * @param string $key
     *
     * @return string
     */
    protected function flashDataKey(string $key): string
    {
        return 'flash.data.'.$key;
    }

    /**
     * 新值闪存 KEY.
     *
     * @return string
     */
    protected function flashNewKey(): string
    {
        return 'flash.new.key';
    }

    /**
     * 旧值闪存 KEY.
     *
     * @return string
     */
    protected function flashOldKey(): string
    {
        return 'flash.old.key';
    }

    /**
     * 前一个页面 KEY.
     *
     * @return string
     */
    protected function prevUrlKey(): string
    {
        return 'prev.url.key';
    }
}

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

/**
 * Console 渲染.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.09.20
 *
 * @version 1.0
 */
class ConsoleRenderer
{
    /**
     * debug 管理.
     *
     * @var \Leevel\Debug\Debug
     */
    protected $debugBar;

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

    /**
     * 渲染数据.
     *
     * @return string
     */
    public function render(): string
    {
        return $this->console($this->debugBar->getData());
    }

    /**
     * 返回输出到浏览器.
     *
     * @param array $data
     *
     * @return string
     */
    protected function console(array $data): string
    {
        $content = [];

        $content[] = '<script type="text/javascript">
console.log( \'%cThe PHP Framework For Code Poem As Free As Wind %c(http://www.queryphp.com)\', \'font-weight: bold;color: #06359a;\', \'color: #02d629;\' );';

        foreach ($data as $key => $item) {
            if (is_string($key)) {
                $content[] = 'console.log(\'\');';

                $content[] = 'console.log(\'%c '.$key.'\', \'color: blue; background: #045efc; color: #fff; padding: 8px 15px; -moz-border-radius: 15px; -webkit-border-radius: 15px; border-radius: 15px;\');';

                $content[] = 'console.log(\'\');';
            }

            if ($item) {
                $content[] = 'console.log('.json_encode($item, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT).');';
            }
        }

        $content[] = '</script>';

        return implode('', $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\Router;

use Leevel\Di\Provider;

/**
 * 路由服务提供者.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.04.17
 *
 * @version 1.0
 */
abstract class RouterProvider extends Provider
{
    /**
     * 控制器相对目录.
     *
     * @var string
     */
    protected $controllerDir;

    /**
     * 中间件分组
     * 分组可以很方便地批量调用组件.
     *
     * @var array
     */
    protected $middlewareGroups;

    /**
     * 中间件别名
     * HTTP 中间件提供一个方便的机制来过滤进入应用程序的 HTTP 请求
     * 例外在应用执行结束后响应环节也会调用 HTTP 中间件.
     *
     * @var array
     */
    protected $middlewareAlias;

    /**
     * bootstrap.
     */
    public function bootstrap(): void
    {
        $this->setControllerDir();

        $this->setMiddleware();

        if ($this->isRouterCached()) {
            $this->importCachedRouters();
        } else {
            $this->loadRouters();
        }
    }

    /**
     * 注册一个提供者.
     */
    public function register(): void
    {
        $this->container->singleton(self::class, $this);
    }

    /**
     * 可用服务提供者.
     *
     * @return array
     */
    public static function providers(): array
    {
        return [
            'Leevel\\Router\\RouterProvider',
        ];
    }

    /**
     * 返回路由.
     *
     * @return array
     */
    public function getRouters(): array
    {
        return (new ScanRouter($this->makeMiddlewareParser()))->handle();
    }

    /**
     * 导入路由缓存.
     */
    protected function importCachedRouters(): void
    {
        $routers = include $this->getRouterCachePath();

        $this->setRoutersData($routers);
    }

    /**
     * 注册路由.
     */
    protected function loadRouters(): void
    {
        $this->setRoutersData($this->getRouters());
    }

    /**
     * 生成中间件分析器.
     *
     * @return \Leevel\Router\MiddlewareParser
     */
    protected function makeMiddlewareParser(): MiddlewareParser
    {
        return new MiddlewareParser($this->container['router']);
    }

    /**
     * 设置路由数据.
     *
     * @param array $routers
     */
    protected function setRoutersData(array $routers): void
    {
        $this->container['router']->setBasePaths($routers['base_paths']);
        $this->container['router']->setGroupPaths($routers['group_paths']);
        $this->container['router']->setGroups($routers['groups']);
        $this->container['router']->setRouters($routers['routers']);
    }

    /**
     * 路由是否已经缓存.
     *
     * @return bool
     */
    protected function isRouterCached(): bool
    {
        return file_exists($this->getRouterCachePath());
    }

    /**
     * 获取路由缓存地址
     *
     * @return string
     */
    protected function getRouterCachePath(): string
    {
        return $this->container->routerCachedPath();
    }

    /**
     * 设置应用控制器目录.
     */
    protected function setControllerDir(): void
    {
        if (null !== $this->controllerDir) {
            $this->container['router']->setControllerDir($this->controllerDir);
        }
    }

    /**
     * 设置中间件.
     */
    protected function setMiddleware(): void
    {
        if (null !== $this->middlewareGroups) {
            $this->container['router']->setMiddlewareGroups($this->middlewareGroups);
        }

        if (null !== $this->middlewareAlias) {
            $this->container['router']->setMiddlewareAlias($this->middlewareAlias);
        }
    }
}


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

use ErrorException;
use Exception;
use Leevel\Kernel\IApp;
use Leevel\Kernel\IRuntime;
use Symfony\Component\Console\Output\ConsoleOutput;
use Throwable;

/**
 * 注册运行时异常.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.04.25
 *
 * @version 1.0
 */
class RegisterRuntime
{
    /**
     * 容器.
     *
     * @var \Leevel\Di\IApp
     */
    protected $app;

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

        $test = 2 === func_num_args();

        if (!$test) {
            // @codeCoverageIgnoreStart
            error_reporting(E_ALL);

            set_error_handler([$this, 'setErrorHandle']);

            set_exception_handler([$this, 'setExceptionHandler']);

            register_shutdown_function([$this, 'registerShutdownFunction']);

            if ('production' === $app->environment()) {
                ini_set('display_errors', 'Off');
            }
            // @codeCoverageIgnoreEnd
        }
    }

    /**
     * 设置错误句柄.
     *
     * @param int    $code
     * @param string $description
     * @param string $file
     * @param int    $line
     * @param mixed  $context
     */
    public function setErrorHandle(int $code, string $description, ?string $file = null, ?int $line = null, $context = null): void
    {
        // 根据 error_reporing 等级来确定是否抛出错误
        if (!(error_reporting() & $code)) {
            return;
        }

        throw new ErrorException($description, 0, $code, (string) ($file), (int) ($line));
    }

    /**
     * 设置退出句柄.
     *
     * @codeCoverageIgnore
     */
    public function registerShutdownFunction(): void
    {
        if (($error = error_get_last()) && !empty($error['type'])) {
            $this->setExceptionHandler($this->formatErrorException($error));
        }
    }

    /**
     * 响应异常.
     *
     * @param \Throwable $e
     */
    public function setExceptionHandler(Throwable $e): void
    {
        if (!$e instanceof Exception) {
            $e = new ErrorException(
                $e->getMessage(),
                $e->getCode(),
                E_ERROR,
                $e->getFile(),
                $e->getLine(),
                $e->getPrevious()
            );
        }

        try {
            $this->getRuntime()->report($e);
            // @codeCoverageIgnoreStart
        } catch (Exception $e) {
        }
        // @codeCoverageIgnoreEnd

        if ($this->app->console()) {
            $this->renderConsoleResponse($e);
        } else {
            $this->renderHttpResponse($e);
        }
    }

    /**
     * 渲染命令行异常并输出.
     *
     * @param \Exception $e
     */
    protected function renderConsoleResponse(Exception $e): void
    {
        $this->getRuntime()->renderForConsole(new ConsoleOutput(), $e);
    }

    /**
     * 渲染 HTTP 异常并输出.
     *
     * @param \Exception $e
     */
    protected function renderHttpResponse(Exception $e): void
    {
        $this->getRuntime()->render($this->app['request'], $e)->send();
    }

    /**
     * 格式化致命错误信息.
     *
     * @param array $error
     *
     * @return \ErrorException
     */
    protected function formatErrorException(array $error): ErrorException
    {
        return new ErrorException(
            (string) ($error['message']), (int) ($error['type']), 0, (string) ($error['file']), (int) ($error['line'])
        );
    }

    /**
     * 返回运行处理器.
     *
     * @return \Leevel\Kernel\IRuntime
     */
    protected function getRuntime(): IRuntime
    {
        return $this->app->make(IRuntime::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\Auth;

use Leevel\Cache\ICache;
use Leevel\Http\IRequest;

/**
 * auth.token.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.09.07
 *
 * @version 1.0
 */
class Token extends Connect implements IConnect
{
    /**
     * 验证
     *
     * @var \Leevel\Cache\ICache
     */
    protected $cache;

    /**
     * HTTP 请求
     *
     * @var \Leevel\Http\IRequest
     */
    protected $request;

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'token'       => null,
        'input_token' => 'token',
    ];

    /**
     * 构造函数.
     *
     * @param \Leevel\Cache\ICache  $cache
     * @param \Leevel\Http\IRequest $request
     * @param array                 $option
     */
    public function __construct(ICache $cache, IRequest $request, array $option = [])
    {
        $this->cache = $cache;
        $this->request = $request;

        parent::__construct($option);

        $this->option['token'] = $this->getTokenNameFromRequest();
    }

    /**
     * 数据持久化.
     *
     * @param string $key
     * @param string $value
     * @param int    $expire
     */
    protected function setPersistence(string $key, string $value, int $expire = 0): void
    {
        $this->cache->set($key, $value, ['expire' => $expire]);
    }

    /**
     * 获取持久化数据.
     *
     * @param string $key
     *
     * @return mixed
     */
    protected function getPersistence(string $key)
    {
        return $this->cache->get($key);
    }

    /**
     * 删除持久化数据.
     *
     * @param string $key
     */
    protected function deletePersistence(string $key): void
    {
        $this->cache->delete($key);
    }

    /**
     * 从请求中获取 token.
     *
     * @return string
     */
    protected function getTokenNameFromRequest(): string
    {
        $token = $this->request->query($this->option['input_token'], '');

        if (!$token) {
            $token = $this->request->input($this->option['input_token'], '');
        }

        return $token;
    }
}


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

use Leevel\Di\IContainer;
use Leevel\Di\Provider;
use Leevel\Protocol\Client\Rpc;
use Leevel\Protocol\HttpServer;
use Leevel\Protocol\Pool;
use Leevel\Protocol\RpcServer;
use Leevel\Protocol\WebsocketServer;

/**
 * swoole 服务提供者.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.12.21
 *
 * @version 1.0
 */
class Register extends Provider
{
    /**
     * 注册服务.
     */
    public function register(): void
    {
        $this->httpServer();
        $this->websocketServer();
        $this->rpcServer();
        $this->pool();
        $this->rpc();
    }

    /**
     * 可用服务提供者.
     *
     * @return array
     */
    public static function providers(): array
    {
        return [
            'http.server' => [
                'Leevel\\Protocol\\Http\\Server',
            ],
            'websocket.server' => [
                'Leevel\\Protocol\\Websocket\\Server',
            ],
            'rpc.server' => [
                'Leevel\\Protocol\\RpcServer',
            ],
            'pool' => [
                'Leevel\\Protocol\\Pool',
                'Leevel\\Protocol\\IPool',
            ],
            'rpc' => [
                'Leevel\\Protocol\\Client\\Rpc',
            ],
        ];
    }

    /**
     * 是否延迟载入.
     *
     * @return bool
     */
    public static function isDeferred(): bool
    {
        return true;
    }

    /**
     * 注册  http.server 服务.
     */
    protected function httpServer(): void
    {
        $this->container->singleton('http.server', function (IContainer $container) {
            return new HttpServer(
                $container,
                array_merge(
                    $container['option']['protocol\\server'],
                    $container['option']['protocol\\http']
                )
            );
        });
    }

    /**
     * 注册 websocket.server 服务.
     */
    protected function websocketServer(): void
    {
        $this->container->singleton('websocket.server', function (IContainer $container) {
            return new WebsocketServer(
                $container,
                array_merge(
                    $container['option']['protocol\\server'],
                    $container['option']['protocol\\websocket']
                )
            );
        });
    }

    /**
     * 注册 rpc.server 服务.
     */
    protected function rpcServer(): void
    {
        $this->container->singleton('rpc.server', function (IContainer $container) {
            return new RpcServer(
                $container,
                array_merge(
                    $container['option']['protocol\\server'],
                    $container['option']['protocol\\rpc']
                )
            );
        });
    }

    /**
     * 注册 pool 服务.
     */
    protected function pool(): void
    {
        $this->container->singleton('pool', function (IContainer $container) {
            return new Pool($container);
        });
    }

    /**
     * 注册 rpc 服务.
     */
    protected function rpc(): void
    {
        $this->container->singleton('rpc', function (IContainer $container) {
            return new Rpc();
        });
    }
}