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

use InvalidArgumentException;
use Leevel\Console\Command;
use Leevel\Protocol\IServer;
use Swoole\Process;

/**
 * swoole 服务停止.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.12.26
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
abstract class Stop extends Command
{
    /**
     * 响应命令.
     */
    public function handle(): void
    {
        $this->warn($this->getVersion());

        $server = $this->createServer();

        $this->close($server->getOption());
    }

    /**
     * 创建 server.
     *
     * @return \Leevel\Protocol\IServer
     */
    abstract protected function createServer(): IServer;

    /**
     * 返回 Version.
     *
     * @return string
     */
    abstract protected function getVersion(): string;

    /**
     * 停止 Swoole 服务.
     *
     * @param array $option
     */
    protected function close(array $option): void
    {
        $pidFile = $option['pid_path'];
        $processName = $option['process_name'];

        if (!file_exists($pidFile)) {
            throw new InvalidArgumentException(
                sprintf('Pid path `%s` was not found.', $pidFile)
            );
        }

        $pids = explode(PHP_EOL, file_get_contents($pidFile));
        $pid = (int) $pids[0];

        if (!Process::kill($pid, 0)) {
            throw new InvalidArgumentException(
                sprintf('Pid `%s` was not found.', $pid)
            );
        }

        Process::kill($pid, SIGKILL);

        if (is_file($pidFile)) {
            unlink($pidFile);
        }

        $this->info(sprintf('Process %s:%d has stoped.', $processName, $pid), true);
    }

    /**
     * 命令参数.
     *
     * @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\Flow;

/**
 * 流程控制复用.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.04.13
 *
 * @version 1.0
 */
trait FlowControl
{
    /**
     * 逻辑代码是否处于条件表达式中.
     *
     * @var bool
     */
    protected $inFlowControl = false;

    /**
     * 条件表达式是否为真
     *
     * @var bool
     */
    protected $isFlowControlTrue = false;

    /**
     * 条件语句 ifs.
     *
     * @param mixed $value
     *
     * @return $this
     */
    public function ifs($value = false): self
    {
        return $this->setFlowControl(true, $value ? true : false);
    }

    /**
     * 条件语句 elseIfs.
     *
     * @param mixed $value
     *
     * @return $this
     */
    public function elseIfs($value = false): self
    {
        return $this->setFlowControl(true, $value ? true : false);
    }

    /**
     * 条件语句 elses.
     *
     * @return $this
     */
    public function elses(): self
    {
        return $this->setFlowControl(true, !$this->isFlowControlTrue);
    }

    /**
     * 条件语句 endIfs.
     *
     * @return $this
     */
    public function endIfs(): self
    {
        return $this->setFlowControl(false, false);
    }

    /**
     * 设置当前条件表达式状态
     *
     * @param bool $inFlowControl
     * @param bool $isFlowControlTrue
     *
     * @return $this
     */
    public function setFlowControl(bool $inFlowControl, bool $isFlowControlTrue): self
    {
        $this->inFlowControl = $inFlowControl;
        $this->isFlowControlTrue = $isFlowControlTrue;

        return $this;
    }

    /**
     * 验证一下条件表达式是否通过.
     *
     * @return bool
     */
    public function checkFlowControl(): bool
    {
        return $this->inFlowControl && !$this->isFlowControlTrue;
    }

    /**
     * 占位符.
     *
     * @param string $method
     *
     * @return bool
     */
    public function placeholderFlowControl(string $method): bool
    {
        return in_array($method, ['placeholder', 'foobar', '_'], true);
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Mail;

use Closure;
use Leevel\Event\IDispatch;
use Leevel\Router\IView;
use Swift_Attachment;
use Swift_Image;
use Swift_Message;

/**
 * mail 存储.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.08.26
 *
 * @version 1.0
 */
class Mail implements IMail
{
    /**
     * 连接驱动.
     *
     * @var \Leevel\Mail\IConnect
     */
    protected $connect;

    /**
     * 视图.
     *
     * @var \leevel\Mvc\IView
     */
    protected $view;

    /**
     * 事件处理器.
     *
     * @var null|\Leevel\Event\IDispatch
     */
    protected $dispatch;

    /**
     * 邮件错误消息.
     *
     * @var array
     */
    protected $failedRecipients = [];

    /**
     * 消息.
     *
     * @var \Leevel\Mail\Message
     */
    protected $message;

    /**
     * 消息配置.
     *
     * @var array
     */
    protected $messageData = [
        'html'  => [],
        'plain' => [],
    ];

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

    /**
     * 构造函数.
     *
     * @param \Leevel\Mail\IConnect        $connect
     * @param \leevel\Mvc\IView            $view
     * @param null|\Leevel\Event\IDispatch $dispatch
     * @param array                        $option
     */
    public function __construct(IConnect $connect, IView $view, IDispatch $dispatch = null, array $option = [])
    {
        $this->connect = $connect;
        $this->view = $view;
        $this->dispatch = $dispatch;

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

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

        return $this;
    }

    /**
     * 设置邮件发送来源.
     *
     * @param string      $address
     * @param null|string $name
     *
     * @return $this
     */
    public function globalFrom(string $address, ?string $name = null): IMail
    {
        $this->setOption('global_from', compact('address', 'name'));

        return $this;
    }

    /**
     * 设置邮件发送地址
     *
     * @param string      $address
     * @param null|string $name
     *
     * @return $this
     */
    public function globalTo(string $address, ?string $name = null): IMail
    {
        $this->setOption('global_to', compact('address', 'name'));

        return $this;
    }

    /**
     * 视图 html 邮件内容.
     *
     * @param string $file
     * @param array  $data
     *
     * @return $this
     */
    public function view(string $file, array $data = []): IMail
    {
        $this->messageData['html'][] = [
            'file' => $file,
            'data' => $data,
        ];

        return $this;
    }

    /**
     * html 邮件内容.
     *
     * @param string $content
     *
     * @return $this
     */
    public function html(string $content): IMail
    {
        $this->messageData['html'][] = $content;

        return $this;
    }

    /**
     * 纯文本邮件内容.
     *
     * @param string $content
     *
     * @return $this
     */
    public function plain(string $content): IMail
    {
        $this->messageData['plain'][] = $content;

        return $this;
    }

    /**
     * 视图纯文本邮件内容.
     *
     * @param string $file
     * @param array  $data
     *
     * @return $this
     */
    public function viewPlain(string $file, array $data = []): IMail
    {
        $this->messageData['plain'][] = [
            'file' => $file,
            'data' => $data,
        ];

        return $this;
    }

    /**
     * 消息回调处理.
     *
     * @param \Closure $callbacks
     *
     * @return $this
     */
    public function message(Closure $callbacks): IMail
    {
        $this->callbackMessage($callbacks, $this->makeMessage());

        return $this;
    }

    /**
     * 添加附件.
     *
     * @param string        $file
     * @param null|\Closure $callbacks
     *
     * @return $this
     */
    public function attach(string $file, Closure $callbacks = null): IMail
    {
        $this->makeMessage();

        return $this->callbackAttachment(
            $this->createPathAttachment($file),
            $callbacks
        );
    }

    /**
     * 添加内存内容附件
     * file_get_content(path).
     *
     * @param string        $data
     * @param string        $name
     * @param null|\Closure $callbacks
     *
     * @return $this
     */
    public function attachData(string $data, string $name, Closure $callbacks = null): IMail
    {
        $this->makeMessage();

        return $this->callbackAttachment(
            $this->createDataAttachment($data, $name),
            $callbacks
        );
    }

    /**
     * 图片嵌入邮件.
     *
     * @param string $file
     *
     * @return string
     */
    public function attachView(string $file): string
    {
        $this->makeMessage();

        return $this->message->embed(Swift_Image::fromPath($file));
    }

    /**
     * 内存内容图片嵌入邮件.
     *
     * @param string      $data
     * @param string      $name
     * @param null|string $contentType
     *
     * @return string
     */
    public function attachDataView(string $data, string $name, ?string $contentType = null): string
    {
        $this->makeMessage();

        return $this->message->embed(
            new Swift_Image($data, $name, $contentType)
        );
    }

    /**
     * 格式化中文附件名字.
     *
     * @param string $file
     *
     * @return string
     */
    public function attachChinese(string $file): string
    {
        $ext = pathinfo($file, PATHINFO_EXTENSION);

        if ($ext) {
            $file = substr($file, 0, strrpos($file, '.'.$ext));
        }

        return '=?UTF-8?B?'.base64_encode($file).'?='.($ext ? '.'.$ext : '');
    }

    /**
     * 发送邮件.
     *
     * @param \Closure $callbacks
     * @param bool     $htmlPriority
     *
     * @return int
     */
    public function send(Closure $callbacks = null, bool $htmlPriority = true): int
    {
        $this->makeMessage();

        $this->parseMailContent($htmlPriority);

        if ($callbacks) {
            $this->message($callbacks);
        }

        if (!empty($this->option['global_to']['address'])) {
            $this->message->addTo(
                $this->option['global_to']['address'],
                $this->option['global_to']['name']
            );
        }

        $this->handleDispatch($this->message);

        return $this->sendMessage($this->message);
    }

    /**
     * 错误消息.
     *
     * @return array
     */
    public function failedRecipients(): array
    {
        return $this->failedRecipients;
    }

    /**
     * 事件派发.
     *
     * @param \Swift_Message $message
     */
    protected function handleDispatch(Swift_Message $message): void
    {
        if ($this->dispatch) {
            $this->dispatch->handle(self::MAIL_EVENT, $message);
        }
    }

    /**
     * 试图渲染数据.
     *
     * @param string $file
     * @param array  $data
     *
     * @return string
     */
    protected function getViewData(string $file, array $data): string
    {
        return $this->view->
        clearAssign()->

        assign('mail', $this)->

        assign($data)->

        display($file, [], null);
    }

    /**
     * 解析邮件内容.
     *
     * @param bool $htmlPriority
     */
    protected function parseMailContent(bool $htmlPriority = true): void
    {
        $findBody = false;

        $messageData = $this->messageData;

        if (!empty($messageData['html']) && !empty($messageData['plain'])) {
            unset($messageData[true === $htmlPriority ? 'plain' : 'html']);
        }

        if (!empty($messageData['html'])) {
            foreach ($messageData['html'] as $view) {
                if (false === $findBody) {
                    $method = 'setBody';
                    $findBody = true;
                } else {
                    $method = 'addPart';
                }

                $this->message->{$method}(
                    is_array($view) ?
                        $this->getViewData($view['file'], $view['data']) :
                        $view,
                    'text/html'
                );
            }
        }

        if (!empty($messageData['plain'])) {
            foreach ($messageData['plain'] as $view) {
                if (false === $findBody) {
                    $method = 'setBody';
                    $findBody = true;
                } else {
                    $method = 'addPart';
                }

                $this->message->{$method}(
                    is_array($view) ?
                        $this->getViewData($view['file'], $view['data']) :
                        $view,
                    'text/plain'
                );
            }
        }
    }

    /**
     * 发送消息对象
     *
     * @param \Swift_Message $message
     *
     * @return int
     */
    protected function sendMessage(Swift_Message $message): int
    {
        return $this->connect->send($message, $this->failedRecipients);
    }

    /**
     * 创建消息对象
     *
     * @return \Swift_Message
     */
    protected function makeMessage(): Swift_Message
    {
        if (null !== $this->message) {
            return $this->message;
        }

        $message = new Swift_Message();

        if (!empty($this->option['global_from']['address'])) {
            $message->setFrom(
                $this->option['global_from']['address'],
                $this->option['global_from']['name']
            );
        }

        return $this->message = $message;
    }

    /**
     * 邮件消息回调处理.
     *
     * @param \Closure       $callbacks
     * @param \Swift_Message $message
     *
     * @return mixed
     */
    protected function callbackMessage(Closure $callbacks, Swift_Message $message)
    {
        return $callbacks($message, $this);
    }

    /**
     * 路径创建 Swift_Attachment.
     *
     * @param string $file
     *
     * @return \Swift_Attachment
     */
    protected function createPathAttachment(string $file): Swift_Attachment
    {
        return Swift_Attachment::fromPath($file);
    }

    /**
     * 内存内容创建 Swift_Attachment.
     *
     * @param string $data
     * @param string $name
     *
     * @return \Swift_Attachment
     */
    protected function createDataAttachment(string $data, string $name): Swift_Attachment
    {
        return new Swift_Attachment($data, $name);
    }

    /**
     * 邮件附件消息回调处理.
     *
     * @param \Swift_Attachment $attachment
     * @param null|\Closure     $callbacks
     *
     * @return $this
     */
    protected function callbackAttachment(Swift_Attachment $attachment, Closure $callbacks = null): IMail
    {
        if ($callbacks) {
            $callbacks($attachment, $this);
        }

        $this->message->attach($attachment);

        return $this;
    }
}


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

/**
 * html 模板处理类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.18
 *
 * @version 1.0
 */
class Html extends Connect implements IConnect
{
    /**
     * 视图分析器.
     *
     * @var \Leevel\View\iparser
     */
    protected $parser;

    /**
     * 解析 parse.
     *
     * @var \Closure
     */
    protected $parseResolver;

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'theme_path'            => '',
        'suffix'                => '.html',
        'cache_path'            => '',
    ];

    /**
     * 加载视图文件.
     *
     * @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_');
        }

        $cachepath = $this->getCachePath($file); // 编译文件路径

        if ($this->isCacheExpired($file, $cachepath)) { // 重新编译
            $this->parser()->doCompile($file, $cachepath);
        }

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

            return $result;
        }

        include $cachepath;
    }

    /**
     * 设置 parser 解析回调.
     *
     * @param \Closure $parseResolver
     */
    public function setParseResolver(Closure $parseResolver): void
    {
        $this->parseResolver = $parseResolver;
    }

    /**
     * 获取编译路径.
     *
     * @param string $file
     *
     * @return string
     */
    public function getCachePath(string $file): string
    {
        if (!$this->option['cache_path']) {
            throw new RuntimeException('Theme cache path must be set.');
        }

        $file = str_replace('//', '/', str_replace('\\', '/', $file));

        $file = basename($file, '.'.pathinfo($file, PATHINFO_EXTENSION)).'.'.md5($file).'.php';

        return $this->option['cache_path'].'/'.$file;
    }

    /**
     * 解析 parser.
     *
     * @return \Leevel\View\IParser
     */
    protected function resolverParser(): IParser
    {
        if (!$this->parseResolver) {
            throw new RuntimeException('Html theme not set parse resolver.');
        }

        return call_user_func($this->parseResolver);
    }

    /**
     * 获取分析器.
     *
     * @return \Leevel\View\IParser
     */
    protected function parser(): IParser
    {
        if (null !== $this->parser) {
            return $this->parser;
        }

        return $this->parser = $this->resolverParser();
    }

    /**
     * 判断缓存是否过期
     *
     * @param string $file
     * @param string $cachepath
     *
     * @return bool
     */
    protected function isCacheExpired(string $file, string $cachepath): bool
    {
        if (!is_file($cachepath)) {
            return true;
        }

        if (filemtime($file) >= filemtime($cachepath)) {
            return true;
        }

        return 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\Thrift\Service;

use InvalidArgumentException;
use Leevel\Http\IRequest;
use Leevel\Http\IResponse;
use Leevel\Http\RedirectResponse;
use Leevel\Http\Request as HttpRequest;
use Leevel\Kernel\Facade\App;
use Leevel\Kernel\IKernel;
use Leevel\Router\IRouter;

/**
 * thrift 默认服务调用响应.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.04.01
 *
 * @version 1.0
 */
class ThriftHandler implements ThriftIf
{
    /**
     * 共享数据存储键.
     *
     * @var string
     */
    const METAS = 'metas';

    /**
     * 定义一个响应包结构.
     *
     * @param \Leevel\Protocol\Thrift\Service\Request $request
     *
     * @return \Leevel\Protocol\Thrift\Service\Response
     */
    public function call(Request $request): Response
    {
        $httpRequest = $this->normalizeRequest($request);

        $response = App::make(IKernel::class)->handle($httpRequest);

        return $this->normalizeResponse($response);
    }

    /**
     * 格式化 QueryPHP 响应到 Thrift RPC 响应.
     *
     * @param \Leevel\Http\Request $response
     *
     * @return \Leevel\Protocol\Thrift\Service\Response
     */
    protected function normalizeResponse(IResponse $response): Response
    {
        if ($response instanceof RedirectResponse) {
            $content = json_encode(['target_url' => $response->getTargetUrl()]);
        } else {
            $content = $response->getContent();
        }

        return new Response([
            'status' => $response->getStatusCode(),
            'data'   => $content,
        ]);
    }

    /**
     * 格式化 Thrift RPC 请求到 QueryPHP 请求
     *
     * @param \Leevel\Protocol\Thrift\Service\Request $request
     *
     * @return \Leevel\Http\Request
     */
    protected function normalizeRequest(Request $request): HttpRequest
    {
        if (!$request->call) {
            throw new InvalidArgumentException('Rpc call is not set.');
        }

        $httpRequest = new HttpRequest();

        // pathInfo
        if (false !== strpos($request->call, '@')) {
            list($call, $method) = explode('@', $request->call);
            $httpRequest->setMethod($method);
        } else {
            $call = $request->call;
        }

        $httpRequest->setPathInfo($call);

        // var
        $data = $request->params ?: [];
        $data[self::METAS] = $request->metas ?: [];
        $this->setPreRequestMatched($httpRequest, $data);

        return $httpRequest;
    }

    /**
     * 设置路由匹配数据.
     *
     * @param \Leevel\Http\IRequest $request
     * @param array                 $data
     */
    protected function setPreRequestMatched(IRequest $request, array $data): void
    {
        App::make(IRouter::class)
            ->setPreRequestMatched($request, [IRouter::VARS => $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\Router;

use InvalidArgumentException;
use function Leevel\Support\Arr\normalize;
use Leevel\Support\Arr\normalize;
use OpenApi\Annotations\OpenApi;
use OpenApi\Annotations\PathItem;
use OpenApi\Context;
use function OpenApi\scan;

/**
 * OpenApi 注解路由
 * 1:忽略已删除的路由 deprecated 和带有 leevelIgnore 的路由
 * 2:如果没有绑定路由参数 leevelBind,系统会尝试自动解析注解所在控制器方法.
 * 3:只支持最新的 zircote/swagger-php 3，支持最新的 OpenApi 3.0 规范.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.04.10
 *
 * @version 1.0
 */
class OpenApiRouter
{
    /**
     * 路由中间件分析器.
     *
     * @var \Leevel\Router\MiddlewareParser
     */
    protected $middlewareParser;

    /**
     * 顶级域名.
     *
     * @var string
     */
    protected $domain;

    /**
     * 扫描目录.
     *
     * @var array
     */
    protected $scandirs = [];

    /**
     * 支持的方法.
     *
     * @var array
     */
    protected $methods = [
        'get',
        'delete',
        'post',
        'delete',
        'options',
        'head',
        'patch',
    ];

    /**
     * 支持的路由字段.
     *
     * @var array
     */
    protected $routerField = [
        'scheme',
        'domain',
        'params',
        'bind',
        'middlewares',
    ];

    /**
     * 匹配基础路径.
     *
     * @var array
     */
    protected $basePaths = [];

    /**
     * 匹配分组路径.
     *
     * @var array
     */
    protected $groupPaths = [];

    /**
     * 匹配分组.
     *
     * @var array
     */
    protected $groups = [];

    /**
     * 构造函数.
     *
     * @param \Leevel\Router\MiddlewareParser $middlewareParser
     * @param string                          $domain
     */
    public function __construct(MiddlewareParser $middlewareParser, ?string $domain = null)
    {
        $this->middlewareParser = $middlewareParser;

        if ($domain) {
            $this->domain = $domain;
        }

        // 忽略 OpenApi 扩展字段警告,改变 set_error_handler 抛出时机
        // 补充基于标准 OpenApi 路由，并可以扩展注解路由的功能
        error_reporting(E_ERROR | E_PARSE | E_STRICT);
    }

    /**
     * 添加一个扫描目录.
     *
     * @param string $dir
     */
    public function addScandir(string $dir): void
    {
        if (!is_dir($dir)) {
            throw new InvalidArgumentException(
                sprintf('OpenApi scandir %s is exits.', $dir)
            );
        }

        $this->scandirs[] = $dir;
    }

    /**
     * 处理 OpenApi 注解路由.
     *
     * @return array
     */
    public function handle(): array
    {
        $openApi = $this->makeOpenApi();

        $this->parseMainPath($openApi);

        $routers = $this->normalizeFastRoute($this->parseMainRouters($openApi));

        return $this->packageRouters($routers);
    }

    /**
     * 打包路由解析数据.
     *
     * @param array $routers
     *
     * @return array
     */
    protected function packageRouters(array $routers): array
    {
        return [
            'base_paths'      => $this->basePaths,
            'group_paths'     => $this->groupPaths,
            'groups'          => $this->groups,
            'routers'         => $routers,
        ];
    }

    /**
     * 解析主路径.
     *
     * @param \OpenApi\Annotations\OpenApi $openApi
     */
    protected function parseMainPath(OpenApi $openApi): void
    {
        list($this->basePaths, $this->groupPaths) = $this->parsePaths($openApi);

        $this->groups = $this->parseGroups($openApi);
    }

    /**
     * 解析主路由.
     *
     * @param \OpenApi\Annotations\OpenApi $openApi
     *
     * @return array
     */
    protected function parseMainRouters(OpenApi $openApi): array
    {
        $routers = [];

        if ($openApi->paths) {
            foreach ($openApi->paths as $path) {
                $routers = $this->parseOpenApiPath($path, $routers);
            }
        }

        return $routers;
    }

    /**
     * 解析 openApi 每一项路径.
     *
     * @param \OpenApi\Annotations\PathItem $path
     * @param array                         $routers
     *
     * @return array
     */
    protected function parseOpenApiPath(PathItem $path, array $routers): array
    {
        foreach ($this->methods as $m) {
            $method = $path->{$m};

            // 忽略已删除和带有忽略标记的路由
            if ($this->isRouterIgnore($method, $path->path)) {
                continue;
            }

            $router = [];

            // 支持的自定义路由字段
            $router = $this->parseRouterField($method);

            // 根据源代码生成绑定
            $router = $this->parseRouterBind($method, $router);

            // 解析中间件
            $router = $this->parseRouterMiddlewares($router);

            // 解析域名
            $router = $this->parseRouterDomain($router);

            // 解析基础路径
            list($prefix, $groupPrefix, $routerPath) = $this->parseRouterPath($path->path, $this->groupPaths, $this->groups);

            // 解析路由正则
            if ($this->isStaticRouter($routerPath)) {
                $routers[$m]['static'][$routerPath] = $router;
            } else {
                $routers[$m][$prefix][$groupPrefix][$routerPath] =
                    $this->parseRouterRegex($routerPath, $router);
            }
        }

        return $routers;
    }

    /**
     * 判断是否为忽略路由.
     *
     * @param object|string $method
     * @param string        $path
     *
     * @return bool
     */
    protected function isRouterIgnore($method, string $path): bool
    {
        if (!is_object($method) || true === $method->deprecated ||
            (property_exists($method, 'leevelIgnore') && $method->leevelIgnore)) {
            return true;
        }

        // 首页 `/` 默认提供 Home::index 需要过滤
        if ('//' === $this->normalizePath($path)) {
            return true;
        }

        return false;
    }

    /**
     * 解析自定义路由字段.
     *
     * @param object $method
     *
     * @return array
     */
    protected function parseRouterField($method): array
    {
        $result = [];

        foreach ($this->routerField as $f) {
            $field = 'leevel'.ucfirst($f);

            if (property_exists($method, $field)) {
                $result[$f] = $method->{$field};
            }
        }

        return $result;
    }

    /**
     * 解析路由绑定.
     *
     * @param object $method
     * @param array  $router
     *
     * @return array
     */
    protected function parseRouterBind($method, array $router): array
    {
        if (empty($router['bind'])) {
            $router['bind'] = $this->parseBindBySource($method->_context);
        }

        if ($router['bind']) {
            $router['bind'] = '\\'.trim($router['bind'], '\\');
        }

        return $router;
    }

    /**
     * 解析基础路径和分组.
     * 基础路径如 /api/v1、/web/v2 等等.
     * 分组例如 goods、orders.
     *
     * @param string $path
     * @param array  $groupPaths
     * @param array  $groups
     *
     * @return array
     */
    protected function parseRouterPath(string $path, array $groupPaths, array $groups): array
    {
        $routerPath = $this->normalizePath($path);
        $pathPrefix = '';

        if ($groupPaths) {
            foreach ($groupPaths as $key => $item) {
                if (0 === strpos($routerPath, $key)) {
                    $pathPrefix = $key;
                    $routerPath = substr($routerPath, strlen($key));

                    break;
                }
            }
        }

        $prefix = $routerPath[1];
        $groupPrefix = '_';

        foreach ($groups as $g) {
            if (0 === strpos($routerPath, $g)) {
                $groupPrefix = $g;

                break;
            }
        }

        $routerPath = $pathPrefix.$routerPath;

        return [$prefix, $groupPrefix, $routerPath];
    }

    /**
     * 解析中间件.
     *
     * @param array $router
     *
     * @return array
     */
    protected function parseRouterMiddlewares(array $router): array
    {
        if (!empty($router['middlewares'])) {
            $router['middlewares'] = $this->middlewareParser->handle(
                normalize($router['middlewares'])
            );
        }

        return $router;
    }

    /**
     * 解析域名.
     *
     * @param array $router
     *
     * @return array
     */
    protected function parseRouterDomain(array $router): array
    {
        $router['domain'] = $this->normalizeDomain($router['domain'] ?? '', $this->domain ?: '');

        if ($router['domain'] && false !== strpos($router['domain'], '{')) {
            list($router['domain_regex'], $router['domain_var']) =
                $this->ruleRegex($router['domain'], $router, true);
        }

        if (!$router['domain']) {
            unset($router['domain']);
        }

        return $router;
    }

    /**
     * 是否为静态路由.
     *
     * @param string $router
     *
     * @return bool
     */
    protected function isStaticRouter(string $router): bool
    {
        return false === strpos($router, '{');
    }

    /**
     * 解析路由正则.
     *
     * @param string $path
     * @param array  $router
     *
     * @return array
     */
    protected function parseRouterRegex(string $path, array $router): array
    {
        list($router['regex'], $router['var']) = $this->ruleRegex($path, $router);

        return $router;
    }

    /**
     * 格式化路径.
     *
     * @param string $path
     *
     * @return string
     */
    protected function normalizePath(string $path): string
    {
        return '/'.trim($path, '/').'/';
    }

    /**
     * 路由正则分组合并.
     *
     * @param array $routers
     *
     * @return array
     */
    protected function normalizeFastRoute(array $routers): array
    {
        // 我和同事毛飞我们讨论了这个，基于 FastRoute 背后技术原理构建 @ 2018.05
        // 合并路由匹配规则提高匹配效率,10 个一分组
        // http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html
        foreach ($routers as &$first) {
            foreach ($first as $firstKey => &$second) {
                if ('static' === $firstKey) {
                    continue;
                }

                foreach ($second as $secondKey => &$three) {
                    $groups = $this->parseToGroups($three);

                    foreach ($groups as $groupKey => $groupThree) {
                        list($three['regex'][$groupKey], $three['map'][$groupKey]) =
                            $this->parseGroupRegex($groupThree);
                    }
                }
            }
        }

        return $routers;
    }

    /**
     * 将路由进行分组.
     *
     * @param array $routers
     *
     * @return array
     */
    protected function parseToGroups(array &$routers): array
    {
        $groups = [];
        $groupIndex = 0;

        foreach ($routers as $key => &$item) {
            $groups[(int) ($groupIndex / 10)][$key] = $item;

            unset($item['regex']);

            $groupIndex++;
        }

        return $groups;
    }

    /**
     * 解析分组路由正则.
     *
     * @param array $routers
     *
     * @return array
     */
    protected function parseGroupRegex(array $routers): array
    {
        $minCount = $this->computeMinCountVar($routers);

        $regex = [];
        $ruleMap = [];
        $ruleKey = 0;
        $regex[] = '~^(?';

        foreach ($routers as $key => $router) {
            $countVar = $minCount + $ruleKey;
            $emptyMatche = $countVar - count($router['var']);

            $ruleMap[$countVar + 1] = $key;

            $regex[] = '|'.$router['regex'].($emptyMatche ? str_repeat('()', $emptyMatche) : '');

            $ruleKey++;
        }

        $regex[] = ')$~x';

        return [
            implode('', $regex),
            $ruleMap,
        ];
    }

    /**
     * 计算初始最低的增长变量数量.
     *
     * @param array $routers
     *
     * @return int
     */
    protected function computeMinCountVar(array $routers): int
    {
        $minCount = 1;

        foreach ($routers as $item) {
            if (($curCount = count($item['var'])) > $minCount) {
                $minCount = $curCount;
            }
        }

        return $minCount;
    }

    /**
     * 根据源代码生成绑定.
     *
     * @param \OpenApi\Context $context
     *
     * @return null|string
     */
    protected function parseBindBySource(Context $context): ?string
    {
        if (!$context->class || !$context->method) {
            return null;
        }

        return $context->fullyQualifiedName($context->class).'@'.$context->method;
    }

    /**
     * 分析分组标签.
     *
     * @param \OpenApi\Annotations\OpenApi $openApi
     *
     * @return array
     */
    protected function parseGroups(OpenApi $openApi): array
    {
        $groups = [];

        if ($openApi->tags) {
            foreach ($openApi->tags as $tag) {
                if (property_exists($tag, 'leevelGroup')) {
                    $groups[] = '/'.$tag->leevelGroup;
                }
            }
        }

        return $groups;
    }

    /**
     * 格式化正则.
     *
     * @param string $rule
     * @param array  $routers
     * @param bool   $forSingleRegex
     *
     * @return array
     */
    protected function ruleRegex(string $rule, array $routers, bool $forSingleRegex = false): array
    {
        $routerVar = [];

        $mapRegex = [
            'find'    => [],
            'replace' => [],
        ];

        $rule = preg_replace_callback('/{(.+?)}/', function ($matches) use ($routers, &$routerVar, &$mapRegex) {
            if (false !== strpos($matches[1], ':')) {
                list($routerVar[], $regex) = explode(':', $matches[1]);
            } else {
                $routerVar[] = $matches[1];
                $regex = IRouter::DEFAULT_REGEX;
            }

            $regex = '('.$regex.')';
            $regexEncode = '`'.md5($regex).'`';

            $mapRegex['find'][] = $regexEncode;
            $mapRegex['replace'][] = $regex;

            return $regexEncode;
        }, $rule);

        if (false === $forSingleRegex) {
            $rule = preg_quote($rule);
        } else {
            $rule = preg_quote($rule, '/');
        }

        if ($mapRegex['find']) {
            $rule = str_replace($mapRegex['find'], $mapRegex['replace'], $rule);
        }

        if (true === $forSingleRegex) {
            $rule = '/^'.$rule.'$/';
        }

        return [$rule, $routerVar];
    }

    /**
     * 格式化域名
     * 如果没有设置域名，则加上顶级域名.
     *
     * @param string $domain
     * @param string $topDomain
     *
     * @return string
     */
    protected function normalizeDomain(string $domain, string $topDomain): string
    {
        if (!$domain || !$this->domain) {
            return $domain;
        }

        if ($topDomain !== substr($domain, -strlen($topDomain))) {
            $domain .= '.'.$topDomain;
        }

        return $domain;
    }

    /**
     * 分析路径.
     *
     * @param \OpenApi\Annotations\OpenApi $openApi
     *
     * @return array
     */
    protected function parsePaths(OpenApi $openApi): array
    {
        if (\OpenApi\UNDEFINED === $openApi->externalDocs) {
            return [[], []];
        }

        $externalDocs = $openApi->externalDocs;

        if (!property_exists($externalDocs, 'leevels')) {
            return [[], []];
        }

        $leevels = $externalDocs->leevels;
        $tmps = is_array($leevels) ? $leevels : [$leevels];
        $basePaths = $groupPaths = [];

        foreach ($tmps as $key => $value) {
            // * 表示所有路径，group 为 true 的表示分组路径，其余为基础正则匹配路径
            // 分组路径将会在路由匹配成功后移除自身进行接下来的匹配
            $newKey = '*' !== $key ? '/'.trim($key, '/') : $key;

            if (!empty($value['middlewares'])) {
                $value['middlewares'] = $this->middlewareParser->handle(
                    normalize($value['middlewares'])
                );
            }

            if (isset($value['group']) && true === $value['group']) {
                unset($value['group']);
                $groupPaths[$newKey] = $value;
            } else {
                $newKey = '*' === $newKey ? '*' : $this->prepareRegexForWildcard($newKey.'/');
                $basePaths[$newKey] = $value;
            }
        }

        return [$basePaths, $groupPaths];
    }

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

        return $regex;
    }

    /**
     * 生成 OpenApi.
     *
     * @return \OpenApi\Annotations\OpenApi
     */
    protected function makeOpenApi(): OpenApi
    {
        return scan($this->scandirs);
    }
}

// import fn.
class_exists(normalize::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\Cache\Provider;

use Leevel\Cache\Load;
use Leevel\Cache\Manager;
use Leevel\Di\IContainer;
use Leevel\Di\Provider;

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

    /**
     * 可用服务提供者.
     *
     * @return array
     */
    public static function providers(): array
    {
        return [
            'caches' => [
                'Leevel\\Cache\\Manager',
            ],
            'cache' => [
                'Leevel\\Cache\\Cache',
                'Leevel\\Cache\\ICache',
            ],
            'cache.load' => [
                'Leevel\\Cache\\Load',
            ],
        ];
    }

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

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

    /**
     * 注册 cache 服务
     */
    protected function cache(): void
    {
        $this->container->singleton('cache', function (IContainer $container) {
            return $container['caches']->connect();
        });
    }

    /**
     * 注册 cache.load 服务
     */
    protected function cacheLoad(): void
    {
        $this->container->singleton('cache.load', function (IContainer $container) {
            return new Load($container, $container['cache']);
        });
    }
}


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

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

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

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

    /**
     * 基础路径.
     *
     * @var string
     */
    protected $basePath;

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

        $load = new Load($app->optionPath());
        $data = $load->loadData($app);

        $cachePath = $app->optionCachedPath();
        $this->basePath = $app->path();

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

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

    /**
     * 计算相对路径
     * 忽略未包含在基础路径中的缓存相对路径.
     *
     * @param string $cachePath
     *
     * @return int
     */
    protected function computeRelativePath(string $cachePath): int
    {
        if (false === strpos($cachePath, $this->basePath)) {
            return -1;
        }

        $relativePath = str_replace($this->basePath.'/', '', $cachePath);
        $relativePath = dirname($relativePath);

        return count(explode('/', $relativePath));
    }

    /**
     * 替换相对路径.
     *
     * @param string $data
     *
     * @return string
     */
    protected function replaceRelativePath(string $data): string
    {
        return str_replace("'".$this->basePath, '$baseDir.\'', $data);
    }

    /**
     * 写入缓存.
     *
     * @param string $cachePath
     * @param array  $data
     */
    protected function writeCache(string $cachePath, array $data): void
    {
        $relativePathLevel = $this->computeRelativePath($cachePath);
        $isRelativePath = $relativePathLevel > -1;

        $content = '<?'.'php /* '.date('Y-m-d H:i:s').
            ' */ ?'.'>';

        if ($isRelativePath) {
            $content .= PHP_EOL.'<?'.'php $baseDir = dirname(__DIR__, '.$relativePathLevel.'); ?>';
        }

        $content .= PHP_EOL.'<?'.'php return '.
            var_export($data, true).'; ?'.'>';

        if ($isRelativePath) {
            $content = $this->replaceRelativePath($content);
        }

        create_file($cachePath, $content);
    }

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

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

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


<?php

declare(strict_types=1);

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

namespace Leevel\Mail;

use Leevel\Manager\Manager as Managers;

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

    /**
     * 创建连接对象
     *
     * @param object $connect
     *
     * @return object
     */
    protected function createConnect(object $connect): object
    {
        return new Mail($connect,
            $this->container['view'],
            $this->container['event'],
            $this->getCommonOption()
        );
    }

    /**
     * 创建 nulls 连接.
     *
     * @param array $options
     *
     * @return \Leevel\Mail\Nulls
     */
    protected function makeConnectNulls(array $options = []): Nulls
    {
        return new Nulls(
            $this->normalizeConnectOption('nulls', $options)
        );
    }

    /**
     * 创建 smtp 连接.
     *
     * @param array $options
     *
     * @return \Leevel\Mail\Smtp
     */
    protected function makeConnectSmtp(array $options = []): Smtp
    {
        return new Smtp(
            $this->normalizeConnectOption('smtp', $options)
        );
    }

    /**
     * 创建 sendmail 连接.
     *
     * @param array $options
     *
     * @return \Leevel\Mail\Sendmail
     */
    protected function makeConnectSendmail(array $options = []): Sendmail
    {
        return new Sendmail(
            $this->normalizeConnectOption('sendmail', $options)
        );
    }

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


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

/**
 * 配置管理类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.02.13
 *
 * @version 1.0
 */
class Option implements IOption, ArrayAccess
{
    /**
     * 配置数据.
     *
     * @var array
     */
    protected $option = [];

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

    /**
     * 是否存在配置.
     *
     * @param string $name
     *
     * @return bool
     */
    public function has(string $name = 'app\\'): bool
    {
        $name = $this->parseNamespace($name);
        $namespaces = $name[0];
        $name = $name[1];

        if ('*' === $name) {
            return isset($this->option[$namespaces]);
        }

        if (!strpos($name, '.')) {
            return array_key_exists($name, $this->option[$namespaces]);
        }

        $parts = explode('.', $name);
        $option = $this->option[$namespaces];

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

            $option = $option[$part];
        }

        return true;
    }

    /**
     * 获取配置.
     *
     * @param string $name
     * @param mixed  $defaults
     *
     * @return mixed
     */
    public function get(string $name = 'app\\', $defaults = null)
    {
        $name = $this->parseNamespace($name);
        $namespaces = $name[0];
        $name = $name[1];

        if ('*' === $name) {
            return $this->option[$namespaces];
        }

        if (!strpos($name, '.')) {
            return array_key_exists($name, $this->option[$namespaces]) ?
                $this->option[$namespaces][$name] :
                $defaults;
        }

        $parts = explode('.', $name);
        $option = $this->option[$namespaces];

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

            $option = $option[$part];
        }

        return $option;
    }

    /**
     * 返回所有配置.
     *
     * @return array
     */
    public function all(): array
    {
        return $this->option;
    }

    /**
     * 设置配置.
     *
     * @param mixed $name
     * @param mixed $value
     */
    public function set($name, $value = null): void
    {
        if (is_array($name)) {
            foreach ($name as $key => $value) {
                $this->set($key, $value);
            }
        } else {
            $name = $this->parseNamespace($name);
            $namespaces = $name[0];
            $name = $name[1];

            if ('*' === $name) {
                $this->option[$namespaces] = $value;

                return;
            }

            if (!strpos($name, '.')) {
                $this->option[$namespaces][$name] = $value;
            } else {
                $parts = explode('.', $name);
                $max = count($parts) - 1;
                $option = &$this->option[$namespaces];

                for ($i = 0; $i <= $max; $i++) {
                    $part = $parts[$i];

                    if ($i < $max) {
                        if (!isset($option[$part])) {
                            $option[$part] = [];
                        }

                        $option = &$option[$part];
                    } else {
                        $option[$part] = $value;
                    }
                }
            }
        }
    }

    /**
     * 删除配置.
     *
     * @param string $name
     */
    public function delete(string $name): void
    {
        $name = $this->parseNamespace($name);
        $namespaces = $name[0];
        $name = $name[1];

        if ('*' === $name) {
            $this->option[$namespaces] = [];

            return;
        }

        if (!strpos($name, '.')) {
            if (isset($this->option[$namespaces][$name])) {
                unset($this->option[$namespaces][$name]);
            }
        } else {
            $parts = explode('.', $name);
            $max = count($parts) - 1;
            $option = &$this->option[$namespaces];

            for ($i = 0; $i <= $max; $i++) {
                $part = $parts[$i];

                if ($i < $max) {
                    if (!isset($option[$part])) {
                        break;
                    }

                    $option = &$option[$part];
                } else {
                    if (isset($option[$part])) {
                        unset($option[$part]);
                    }
                }
            }
        }
    }

    /**
     * 初始化配置参数.
     *
     * @param mixed $namespaces
     */
    public function reset($namespaces = null): void
    {
        if (is_array($namespaces)) {
            $this->option = $namespaces;
        } elseif (is_string($namespaces)) {
            $this->option[$namespaces] = [];
        } else {
            $this->option = [];
        }
    }

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

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

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

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

    /**
     * 分析命名空间.
     *
     * @param string $name
     *
     * @return array
     */
    protected function parseNamespace(string $name): array
    {
        if (strpos($name, '\\')) {
            $namespaces = explode('\\', $name);

            if (empty($namespaces[1])) {
                $namespaces[1] = '*';
            }

            $name = $namespaces[1];
            $namespaces = $namespaces[0];
        } else {
            $namespaces = static::DEFAUTL_NAMESPACE;
        }

        if (!isset($this->option[$namespaces])) {
            $this->option[$namespaces] = [];
        }

        return [
            $namespaces,
            $name,
        ];
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Cache;

use Leevel\Cache\Redis\IConnect as RedisIConnect;

/**
 * redis 扩展缓存.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.06.05
 *
 * @version 1.0
 */
class Redis extends Connect implements IConnect
{
    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'time_preset' => [],
        'expire'      => 86400,
        'serialize'   => true,
    ];

    /**
     * 构造函数.
     *
     * @param \Leevel\Cache\Redis\IConnect $handle
     * @param array                        $option
     */
    public function __construct(RedisIConnect $handle, array $option = [])
    {
        parent::__construct($option);

        $this->handle = $handle;
    }

    /**
     * 获取缓存.
     *
     * @param string $name
     * @param mixed  $defaults
     * @param array  $option
     *
     * @return mixed
     */
    public function get(string $name, $defaults = false, array $option = [])
    {
        $option = $this->normalizeOptions($option);

        $data = $this->handle->get(
            $this->getCacheName($name)
        );

        if (false === $data) {
            return $defaults;
        }

        if ($option['serialize'] && is_string($data)) {
            $data = unserialize($data);
        }

        return $data;
    }

    /**
     * 设置缓存.
     *
     * @param string $name
     * @param mixed  $data
     * @param array  $option
     */
    public function set(string $name, $data, array $option = []): void
    {
        $option = $this->normalizeOptions($option);

        if ($option['serialize']) {
            $data = serialize($data);
        }

        $option['expire'] = $this->cacheTime($name, (int) $option['expire']);

        $this->handle->set(
            $this->getCacheName($name), $data,
            $option['expire'] ? (int) $option['expire'] : null
        );
    }

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

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


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

use InvalidArgumentException;
use function Leevel\Support\Type\type_these;
use Leevel\Support\Type\type_these;
use SplDoublyLinkedList;

/**
 * 双向链表
 * 在 PHP 双向链表的基础上加上数据类型验证功能，不少业务场景中保证链表中数据一致性
 * 以及链表返回空数据时阻止抛出异常的默认行为.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.21
 * @see http://php.net/manual/zh/class.spldoublylinkedlist.php
 *
 * @version 1.0
 */
class LinkedList extends SplDoublyLinkedList
{
    /**
     * 允许的类型.
     *
     * @var array
     */
    protected $type = [];

    /**
     * 构造函数.
     *
     * @param array $type
     */
    public function __construct(array $type = null)
    {
        if ($type) {
            $this->type = $type;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function pop()
    {
        if ($this->isEmpty()) {
            return;
        }

        return parent::pop();
    }

    /**
     * {@inheritdoc}
     */
    public function add($index, $newval): void
    {
        $this->validate($newval);

        parent::add($index, $newval);
    }

    /**
     * {@inheritdoc}
     */
    public function offsetSet($index, $newval): void
    {
        $this->validate($newval);

        parent::offsetSet($index, $newval);
    }

    /**
     * {@inheritdoc}
     */
    public function push($value): void
    {
        $this->validate($value);

        parent::push($value);
    }

    /**
     * {@inheritdoc}
     */
    public function unshift($value)
    {
        $this->validate($value);

        parent::unshift($value);
    }

    /**
     * 验证类型是否正确遇到错误抛出异常.
     *
     * @param mixed $value
     */
    public function validate($value): void
    {
        if (!$this->checkType($value)) {
            $e = sprintf('The linkedlist element type verification failed, and the allowed type is %s.',
                implode(',', $this->type)
            );

            throw new InvalidArgumentException($e);
        }
    }

    /**
     * 验证类型是否正确.
     *
     * @param mixed $value
     *
     * @return bool
     */
    protected function checkType($value): bool
    {
        if (!$this->type) {
            return true;
        }

        return type_these($value, $this->type);
    }
}

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

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

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        // lg sm
        'size'          => '',
        'template'      => '{header} {ul} {prev} {first} {main} {last} {next} {endul} {footer}',
        'large_size'    => false,
        'small_size'    => 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 setLargeSize(): IRender
    {
        return $this->setOption('size', 'lg');
    }

    /**
     * 小尺寸样式.
     *
     * @return $this
     */
    public function setSmallSize(): IRender
    {
        return $this->setOption('size', 'sm');
    }

    /**
     * 渲染.
     *
     * @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['large_size']) {
            $this->setLargeSize();
        } elseif ($this->option['small_size']) {
            $this->setSmallSize();
        }
    }

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

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

    /**
     * 返回渲染 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(
                '
'.
                    '«
',
                $this->replace($this->page->parsePrevRenderPrev())
            );
        }

        return '
'.
            '«
';
    }

    /**
     * 返回渲染 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(
                '
'.
                    '»
',
                $this->replace($this->page->getCurrentPage() + 1)
            );
        }

        return '
'.
            '»
';
    }

    /**
     * 返回渲染 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 '
';
    }

    /**
     * 返回渲染 footer.
     *
     * @return string
     */
    protected function getFooterRender(): string
    {
        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\I18n;

use RuntimeException;

/**
 * 语言包工具类导入类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.25
 *
 * @version 1.0
 */
class Load
{
    /**
     * 当前语言包.
     *
     * @var string
     */
    protected $i18n = 'zh-CN';

    /**
     * 载入路径.
     *
     * @var array
     */
    protected $dirs = [];

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

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

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

        return $this;
    }

    /**
     * 添加目录.
     *
     * @param array $dirs
     *
     * @return $this
     */
    public function addDir(array $dirs): self
    {
        $this->dirs = array_unique(array_merge($this->dirs, $dirs));

        return $this;
    }

    /**
     * 载入语言包数据.
     *
     * @author 小牛
     *
     * @since 2016.11.27
     *
     * @return array
     */
    public function loadData(): array
    {
        if (null !== $this->loaded) {
            return $this->loaded;
        }

        $files = $this->findMoFile($this->parseDir($this->dirs));
        $texts = $this->parseMoData($files);

        return $this->loaded = $texts;
    }

    /**
     * 分析目录中的 PHP 语言包包含的文件.
     *
     * @param array $dirs
     *
     * @author 小牛
     *
     * @since 2016.11.27
     *
     * @return array
     */
    protected function findMoFile(array $dirs): array
    {
        $files = [];

        foreach ($dirs as $dir) {
            if (!is_dir($dir)) {
                throw new RuntimeException(sprintf('I18n load dir %s is not exits.', $dir));
            }

            $files = array_merge($files, $this->getMoFiles($dir));
        }

        return $files;
    }

    /**
     * 获取目录中的 MO 文件.
     *
     * @param string $dir
     *
     * @return array
     */
    protected function getMoFiles(string $dir): array
    {
        return glob($dir.'/*.mo');
    }

    /**
     * 分析 mo 文件语言包数据.
     *
     * @param array $files
     *
     * @author 小牛
     *
     * @since 2016.11.25
     *
     * @return array
     */
    protected function parseMoData(array $files): array
    {
        return (new Mo())->read($files);
    }

    /**
     * 分析目录.
     *
     * @param array $dirs
     *
     * @return array
     */
    protected function parseDir(array $dirs): array
    {
        $i18n = $this->i18n;

        return array_map(function ($dir) use ($i18n) {
            return $dir.'/'.$i18n;
        }, $dirs);
    }
}