<?php

declare(strict_types=1);

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

namespace Leevel\Log;

use Monolog\Formatter\LineFormatter;
use Monolog\Handler\HandlerInterface;
use Monolog\Logger;

/**
 * connect 驱动抽象类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.09.01
 *
 * @version 1.0
 */
abstract class Connect implements IConnect
{
    /**
     * Monolog.
     *
     * @var \Monolog\Logger
     */
    protected $monolog;

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'channel' => 'development',
    ];

    /**
     * Monolog 支持日志级别.
     *
     * @var array
     */
    protected $supportLevel = [
        ILog::DEBUG     => Logger::DEBUG,
        ILog::INFO      => Logger::INFO,
        ILog::NOTICE    => Logger::NOTICE,
        ILog::WARNING   => Logger::WARNING,
        ILog::ERROR     => Logger::ERROR,
        ILog::CRITICAL  => Logger::CRITICAL,
        ILog::ALERT     => Logger::ALERT,
        ILog::EMERGENCY => Logger::EMERGENCY,
    ];

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

        $this->monolog = new Logger($this->option['channel']);
    }

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

        return $this;
    }

    /**
     * 日志写入接口.
     *
     * @param array $data
     */
    public function flush(array $data): void
    {
        foreach ($data as $value) {
            $method = $this->normalizeLevel(array_shift($value));

            $this->monolog->{$method}(...$value);
        }
    }

    /**
     * 取得 Monolog.
     *
     * @return \Monolog\Logger
     */
    public function getMonolog(): Logger
    {
        return $this->monolog;
    }

    /**
     * 设置默认格式化.
     *
     * @param \Monolog\Handler\HandlerInterface $handler
     *
     * @return \Monolog\Handler\HandlerInterface
     */
    protected function normalizeHandler(HandlerInterface $handler): HandlerInterface
    {
        return $handler->setFormatter($this->lineFormatter());
    }

    /**
     * 默认行格式化.
     *
     * @return \Monolog\Formatter\LineFormatter
     */
    protected function lineFormatter(): LineFormatter
    {
        return new LineFormatter(null, null, true, true);
    }

    /**
     * 格式化级别
     * 不支持级别归并到 DEBUG.
     *
     * @param string $level
     *
     * @return string
     */
    protected function normalizeLevel(string $level): string
    {
        if (!in_array($level, array_keys($this->supportLevel), true)) {
            return ILog::DEBUG;
        }

        return $level;
    }

    /**
     * 获取 Monolog 级别
     * 不支持级别归并到 DEBUG.
     *
     * @param string $level
     *
     * @return int
     */
    protected function normalizeMonologLevel(string $level): int
    {
        if (isset($this->supportLevel[$level])) {
            return $this->supportLevel[$level];
        }

        return $this->supportLevel[ILog::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\Kernel;

use Exception;
use Leevel\Database\Ddd\EntityNotFoundException;
use Leevel\Di\IContainer;
use Leevel\Http\IRequest;
use Leevel\Http\IResponse;
use Leevel\Http\JsonResponse;
use Leevel\Http\Response;
use Leevel\Kernel\Exception\HttpException;
use Leevel\Kernel\Exception\NotFoundHttpException;
use Leevel\Log\ILog;
use NunoMaduro\Collision\Provider as CollisionProvider;
use Symfony\Component\Console\Output\OutputInterface;
use Whoops\Handler\JsonResponseHandler;
use Whoops\Handler\PrettyPageHandler;
use Whoops\Run;

/**
 * 异常处理.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.05.04
 *
 * @version 1.0
 */
abstract class Runtime implements IRuntime
{
    /**
     * 容器.
     *
     * @var \Leevel\Di\IContainer
     */
    protected $container;

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

    /**
     * 异常上报.
     *
     * @param \Exception $e
     *
     * @return mixed
     */
    public function report(Exception $e)
    {
        if (method_exists($e, 'report')) {
            return $e->report();
        }

        // @codeCoverageIgnoreStart
        try {
            // 系统核心组件遇到异常，直接抛出异常
            if (!is_object($this->container->make('option'))) {
                echo $this->renderExceptionWithWhoops($e);
                exit();
            }

            $log = $this->container->make(ILog::class);
        } catch (Exception $e) {
            throw $e;
        }
        // @codeCoverageIgnoreEnd

        $log->error(
            $e->getMessage(),
            [
                'exception' => (string) $e,
            ]
        );

        $log->flush();
    }

    /**
     * 异常渲染.
     *
     * @param \Leevel\Http\IRequest $request
     * @param \Exception            $e
     *
     * @return \Leevel\Http\IResponse
     */
    public function render(IRequest $request, Exception $e): IResponse
    {
        if (method_exists($e, 'render') && $response = $e->render($request, $e)) {
            if (!($response instanceof IResponse)) {
                $response = new Response($response,
                    $this->normalizeStatusCode($e),
                    $this->normalizeHeaders($e)
                );
            }

            return $response;
        }

        $e = $this->prepareException($e);

        if ($request->isAcceptJson()) {
            return $this->makeJsonResponse($e);
        }

        return $this->makeHttpResponse($e);
    }

    /**
     * 命令行渲染.
     *
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     * @param \Exception                                        $e
     * @codeCoverageIgnore
     */
    public function renderForConsole(OutputInterface $output, Exception $e): void
    {
        $handler = (new CollisionProvider())
            ->register()
            ->getHandler()
            ->setOutput($output);

        $handler->setInspector(new Inspector($e));
        $handler->handle();
    }

    /**
     * 尝试返回 HTTP 异常响应.
     *
     * @param \Exception $e
     *
     * @return \Leevel\Http\IResponse
     */
    public function rendorWithHttpExceptionView(Exception $e): IResponse
    {
        $filepath = $this->getHttpExceptionView($e);

        if (file_exists($filepath)) {
            $vars = $this->getExceptionVars($e);

            $content = $this->renderWithFile($filepath, $vars);

            return new Response($content,
                $e->getStatusCode(),
                $e->getHeaders()
            );
        }

        return $this->convertExceptionToResponse($e);
    }

    /**
     * HTTP 响应异常.
     *
     * @param \Exception $e
     *
     * @return \Leevel\Http\IResponse
     */
    protected function makeHttpResponse(Exception $e): IResponse
    {
        if ($this->container->debug()) {
            return $this->convertExceptionToResponse($e);
        }

        if (!$this->isHttpException($e)) {
            $e = new HttpException(500, $e->getMessage());
        }

        return $this->rendorWithHttpExceptionView($e);
    }

    /**
     * JSON 响应异常.
     *
     * @param \Exception $e
     *
     * @return \Leevel\Http\IResponse
     */
    protected function makeJsonResponse(Exception $e): IResponse
    {
        $whoops = $this->makeWhoops();
        $whoops->pushHandler($this->makeJsonResponseHandler());

        // JSON 响应结果的物理路径做安全处理
        $json = $whoops->handleException($e);
        $json = json_decode($json, true);
        $json['error']['file'] = $this->filterPhysicalPath($json['error']['file']);
        $json = json_encode($json);

        return JsonResponse::fromJsonString($json,
            $this->normalizeStatusCode($e),
            $this->normalizeHeaders($e)
        );
    }

    /**
     * 异常创建响应.
     *
     * @param \Exception $e
     *
     * @return \Leevel\Http\IResponse
     */
    protected function convertExceptionToResponse(Exception $e): IResponse
    {
        return new Response(
            $this->renderExceptionContent($e),
            $this->normalizeStatusCode($e),
            $this->normalizeHeaders($e)
        );
    }

    /**
     * 取得异常默认渲染.
     *
     * @param \Exception $e
     *
     * @return string
     */
    protected function renderExceptionContent(Exception $e): string
    {
        if ($this->container->debug()) {
            return $this->renderExceptionWithWhoops($e);
        }

        return $this->renderExceptionWithDefault($e);
    }

    /**
     * 默认异常渲染.
     *
     * @param \Exception $e
     *
     * @return string
     */
    protected function renderExceptionWithDefault(Exception $e): string
    {
        $vars = $this->getExceptionVars($e);

        return $this->renderWithFile($this->getDefaultHttpExceptionView(), $vars);
    }

    /**
     * Whoops 渲染异常.
     *
     * @param \Exception $e
     *
     * @return string
     */
    protected function renderExceptionWithWhoops(Exception $e): string
    {
        $whoops = $this->makeWhoops();

        $prettyPage = new PrettyPageHandler();
        $prettyPage->handleUnconditionally(true);

        $whoops->pushHandler($prettyPage);

        return $whoops->handleException($e);
    }

    /**
     * 获取异常格式化变量.
     *
     * @param \Exception $e
     *
     * @return array
     */
    protected function getExceptionVars(Exception $e): array
    {
        return [
            'e'       => $e,
            'code'    => $this->normalizeStatusCode($e),
            'message' => $e->getMessage(),
            'type'    => get_class($e),
            'file'    => $this->filterPhysicalPath($e->getFile()),
            'line'    => $e->getLine(),
        ];
    }

    /**
     * 格式化 HTTP 状态码
     *
     * @param \Exception $e
     *
     * @return int
     */
    protected function normalizeStatusCode(Exception $e): int
    {
        return $this->isHttpException($e) ? $e->getStatusCode() : 500;
    }

    /**
     * 格式化响应头.
     *
     * @param \Exception $e
     *
     * @return array
     */
    protected function normalizeHeaders(Exception $e): array
    {
        return $this->isHttpException($e) ? $e->getHeaders() : [];
    }

    /**
     * 创建 Whoops.
     *
     * @return \Whoops\Run
     */
    protected function makeWhoops(): Run
    {
        $whoops = new Run();

        $whoops->writeToOutput(false);
        $whoops->allowQuit(false);

        return $whoops;
    }

    /**
     * 创建 JSON 响应句柄.
     *
     * @return \Whoops\Handler\JsonResponseHandler
     */
    protected function makeJsonResponseHandler(): JsonResponseHandler
    {
        return (new JsonResponseHandler())->addTraceToOutput($this->container->debug());
    }

    /**
     * 准备异常.
     *
     * @param \Exception $e
     *
     * @return \Exception
     */
    protected function prepareException(Exception $e): Exception
    {
        if ($e instanceof EntityNotFoundException) {
            $e = new NotFoundHttpException($e->getMessage(), $e->getCode());
        }

        return $e;
    }

    /**
     * 是否为 HTTP 异常.
     *
     * @param \Exception $e
     *
     * @return bool
     */
    protected function isHttpException(Exception $e): bool
    {
        return $e instanceof HttpException;
    }

    /**
     * 通过模板渲染异常.
     *
     * @param string $filepath
     * @param array  $vars
     *
     * @return string
     */
    protected function renderWithFile(string $filepath, array $vars = []): string
    {
        if (!is_file($filepath)) {
            throw new Exception(sprintf('Exception file %s is not extis.', $filepath));
        }

        extract($vars);

        ob_start();
        require $filepath;
        $content = ob_get_contents();
        ob_end_clean();

        return $content;
    }

    /**
     * 过滤物理路径
     * 基于安全考虑.
     *
     * @param string $path
     *
     * @return string
     */
    protected function filterPhysicalPath(string $path): string
    {
        return str_replace($this->container->path().'/', '', $path);
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Validate;

/**
 * 验证在给定日期之后.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.11.26
 *
 * @version 1.0
 */
class AfterRule
{
    use Date;

    /**
     * 校验.
     *
     * @param mixed                       $value
     * @param array                       $parameter
     * @param \Leevel\Validate\IValidator $validator
     * @param string                      $field
     *
     * @return bool
     */
    public function validate($value, array $parameter, IValidator $validator, string $field): bool
    {
        return $this->validateDate($value, $parameter, $validator, $field);
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Log;

use InvalidArgumentException;
use Leevel\Filesystem\Fso\create_directory;
use function Leevel\Filesystem\Fso\create_directory;

/**
 * log.file.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.06.05
 *
 * @version 1.0
 */
class File implements IConnect
{
    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'channel' => 'development',
        'name'    => 'Y-m-d H',
        'size'    => 2097152,
        'path'    => '',
    ];

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

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

        return $this;
    }

    /**
     * 日志写入接口.
     *
     * @param array $datas
     */
    public function flush(array $datas): void
    {
        $level = $datas[0][0];

        $this->checkSize($filepath = $this->normalizePath($level));

        foreach ($datas as $value) {
            error_log(self::formatMessage(...$value), 3, $filepath);
        }
    }

    /**
     * 格式化日志信息.
     *
     * @param string $level
     * @param string $message
     * @param array  $context
     *
     * @return string
     */
    public static function formatMessage(string $level, string $message, array $context = []): string
    {
        return sprintf(
            '[%s] %s %s: %s'.PHP_EOL,
            date('Y-m-d H:i:s'), $message, $level,
            json_encode($context, JSON_UNESCAPED_UNICODE)
        );
    }

    /**
     * 验证日志文件大小.
     *
     * @param string $filePath
     */
    protected function checkSize(string $filePath): void
    {
        $dirname = dirname($filePath);

        if (!is_file($filePath)) {
            create_directory($dirname);
        }

        // 清理文件状态缓存 http://php.net/manual/zh/function.clearstatcache.php
        clearstatcache();

        if (is_file($filePath) &&
            floor($this->option['size']) <= filesize($filePath)) {
            rename($filePath,
                $dirname.'/'.basename($filePath, '.log').'_'.
                (time() - filemtime($filePath)).'.log');
        }
    }

    /**
     * 格式化日志路径.
     *
     * @param string $level
     *
     * @return string
     */
    protected function normalizePath(string $level): string
    {
        if (!$this->option['path']) {
            throw new InvalidArgumentException(
                'Path for log has not set.'
            );
        }

        return $this->option['path'].'/'.$this->option['channel'].'.'.
            ($level ? $level.'/' : '').date($this->option['name']).'.log';
    }
}

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


<?php

declare(strict_types=1);

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

namespace Leevel\Router;

use Leevel\Http\IRequest;

/**
 * url 生成.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.01.10
 *
 * @version 1.0
 */
class Url implements IUrl
{
    /**
     * HTTP 请求
     *
     * @var \Leevel\Http\IRequest
     */
    protected $request;

    /**
     * URL 参数.
     *
     * @var array
     */
    protected $params = [];

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

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

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

    /**
     * 生成路由地址
     *
     * @param string      $url
     * @param array       $params
     * @param string      $subdomain
     * @param bool|string $suffix
     *
     * @return string
     */
    public function make(string $url, array $params = [], string $subdomain = 'www', $suffix = null): string
    {
        $url = $this->makeUrl($url, $params, null !== $suffix ? $suffix : $this->option['with_suffix']);
        $url = $this->withEnter($url);
        $url = $this->WithDomain($url, $subdomain);

        return $url;
    }

    /**
     * 返回 HTTP 请求
     *
     * @return \Leevel\Http\IRequest
     */
    public function getRequest(): IRequest
    {
        return $this->request;
    }

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

        return $this;
    }

    /**
     * 获取域名.
     *
     * @return string
     */
    public function getDomain(): string
    {
        return $this->option['domain'];
    }

    /**
     * 自定义 URL.
     *
     * @param string      $url
     * @param array       $params
     * @param bool|string $suffix
     *
     * @return string
     */
    protected function makeUrl(string $url, array $params, $suffix): string
    {
        $this->params = $params;

        if ('/' !== substr($url, 0, 1)) {
            $url = '/'.$url;
        }

        if (false !== strpos($url, '{')) {
            $url = preg_replace_callback('/{(.+?)}/', function ($matches) {
                if (isset($this->params[$matches[1]])) {
                    $value = $this->params[$matches[1]];
                    unset($this->params[$matches[1]]);
                } else {
                    $value = $matches[0];
                }

                return $value;
            }, $url);
        }

        if ($this->params) {
            $queryUrl = http_build_query($this->params);
            $url .= (false !== strpos($url, '?') ? '&' : '?').$queryUrl;
        }

        $url = $this->withSuffix($url, $suffix);

        return $url;
    }

    /**
     * 返回完整 URL 地址
     *
     * @param string $url
     * @param string $domain
     *
     * @return string
     */
    protected function withDomain(string $url, string $domain): string
    {
        if (!$this->option['domain'] || !$domain) {
            return $url;
        }

        return ($this->isSecure() ? 'https://' : 'http://').
            ($domain && '*' !== $domain ? $domain.'.' : '').
            $this->option['domain'].$url;
    }

    /**
     * 是否启用 https.
     *
     * @return bool
     */
    protected function isSecure(): bool
    {
        return $this->request->isSecure();
    }

    /**
     * url 带后缀
     *
     * @param string      $url
     * @param bool|string $suffix
     *
     * @return string
     */
    protected function withSuffix(string $url, $suffix): string
    {
        if ('/' === $url || 0 === strpos($url, '/?')) {
            return $url;
        }

        $suffix = true === $suffix ? $this->option['suffix'] : $suffix;

        if (false !== strpos($url, '?')) {
            $url = str_replace('?', $suffix.'?', $url);
        } else {
            $url .= $suffix;
        }

        return $url;
    }

    /**
     * 带上入口文件.
     *
     * @param string $url
     *
     * @return string
     */
    protected function withEnter(string $url): string
    {
        $enter = $this->request->getEnter();
        $enter = '/' !== $enter ? $enter : '';

        return $enter.$url;
    }
}


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

/**
 * server bag.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.02.25
 *
 * @version 1.0
 */
class ServerBag extends Bag
{
    /**
     * 取回 HTTP HEADERS.
     *
     * @return array
     */
    public function getHeaders(): array
    {
        $result = [];

        $contentHeaders = [
            'CONTENT_LENGTH',
            'CONTENT_MD5',
            'CONTENT_TYPE',
        ];

        foreach ($this->elements as $key => $value) {
            if (0 === strpos($key, 'HTTP_')) {
                $result[substr($key, 5)] = $value;
            } elseif (in_array($key, $contentHeaders, true)) {
                $result[$key] = $value;
            }
        }

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

use Leevel\Di\IContainer;
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Command\Command as SymfonyCommand;

/**
 * 命令行应用程序.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.04.28
 *
 * @version 1.0
 */
class Application extends SymfonyApplication implements IApplication
{
    /**
     * 应用容器.
     *
     * @var \Leevel\Di\IContainer
     */
    protected $container;

    /**
     * 创建一个命令行应用程序.
     *
     * @param \Leevel\Di\IContainer $container
     * @param string                $version
     *
     * @return $this
     */
    public function __construct(IContainer $container, string $version)
    {
        $this->container = $container;

        parent::__construct($this->getLogo(), $version);
    }

    /**
     * 添加一条命令.
     *
     * @param \Symfony\Component\Console\Command\Command $command
     *
     * @return \Symfony\Component\Console\Command\Command
     */
    public function add(SymfonyCommand $command)
    {
        if ($command instanceof Command) {
            $command->setContainer($this->container);
        }

        return parent::add($command);
    }

    /**
     * 格式化一个命令行.
     *
     * @param string $command
     *
     * @return \Symfony\Component\Console\Command\Command
     */
    public function normalizeCommand(string $command): SymfonyCommand
    {
        return $this->add($this->container->make($command));
    }

    /**
     * 批量格式化命令行.
     *
     * @param array $commands
     */
    public function normalizeCommands(array $commands): void
    {
        foreach ($commands as $command) {
            $this->normalizeCommand($command);
        }
    }

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

    /**
     * 返回 QueryPHP Logo.
     *
     * @return string
     */
    protected function getLogo(): string
    {
        return <<<'queryphp'
            _____________                           _______________
             ______/     \__  _____  ____  ______  / /_  _________
              ____/ __   / / / / _ \/ __`\/ / __ \/ __ \/ __ \___
               __/ / /  / /_/ /  __/ /  \  / /_/ / / / / /_/ /__
                 \_\ \_/\____/\___/_/   / / .___/_/ /_/ .___/
                    \_\                /_/_/         /_/
            queryphp;
    }
}


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

/**
 * 规约链式表达式实现.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.10.29
 *
 * @version 1.0
 */
class SpecificationExpression implements ISpecification
{
    /**
     * 闭包规约判断.
     *
     * @var \Closure
     */
    public $spec;

    /**
     * 闭包规约实现.
     *
     * @var \Closure
     */
    protected $handle;

    /**
     * 构造函数.
     *
     * @param \Closure $spec
     * @param \Closure $handle
     */
    public function __construct(Closure $spec, Closure $handle)
    {
        $this->spec = $spec;
        $this->handle = $handle;
    }

    /**
     * 创建规约表达式.
     *
     * @param \Closure $spec
     * @param \Closure $handle
     *
     * @return static
     */
    public static function make(Closure $spec, Closure $handle): ISpecification
    {
        return new static($spec, $handle);
    }

    /**
     * 是否满足规约.
     *
     * @param \Leevel\Database\Ddd\IEntity $entity
     *
     * @return bool
     */
    public function isSatisfiedBy(IEntity $entity): bool
    {
        return call_user_func($this->spec, $entity);
    }

    /**
     * 规约实现.
     *
     * @param \Leevel\Database\Ddd\Select  $select
     * @param \Leevel\Database\Ddd\IEntity $entity
     */
    public function handle(Select $select, IEntity $entity): void
    {
        call_user_func($this->handle, $select, $entity);
    }

    /**
     * 规约 And 操作.
     *
     * @param \Leevel\Database\Ddd\ISpecification $spec
     *
     * @return \Leevel\Database\Ddd\ISpecification
     */
    public function and(ISpecification $spec): ISpecification
    {
        $old = $this->spec;
        $oldHandle = $this->handle;

        $this->spec = function (IEntity $entity) use ($old, $spec): bool {
            return $old($entity) && $spec->isSatisfiedBy($entity);
        };

        $this->handle = function (Select $select, IEntity $entity) use ($spec, $oldHandle) {
            $oldHandle($select, $entity);
            $spec->handle($select, $entity);
        };

        return $this;
    }

    /**
     * 规约 Or 操作.
     *
     * @param \Leevel\Database\Ddd\ISpecification $spec
     *
     * @return \Leevel\Database\Ddd\ISpecification
     */
    public function or(ISpecification $spec): ISpecification
    {
        $old = $this->spec;
        $oldHandle = $this->handle;

        $this->spec = function (IEntity $entity): bool {
            return true;
        };

        $this->handle = function (Select $select, IEntity $entity) use ($old, $spec, $oldHandle) {
            if ($old($entity)) {
                $oldHandle($select, $entity);
            } elseif ($spec->isSatisfiedBy($entity)) {
                $spec->handle($select, $entity);
            }
        };

        return $this;
    }

    /**
     * 规约 Not 操作.
     *
     * @param \Leevel\Database\Ddd\ISpecification $spec
     *
     * @return \Leevel\Database\Ddd\ISpecification
     */
    public function not(): ISpecification
    {
        $old = $this->spec;

        $this->spec = function (IEntity $entity) use ($old): bool {
            return !$old($entity);
        };

        return $this;
    }

    /**
     * 闭包规约 And 操作.
     *
     * @param \Closure $spec
     * @param \Closure $handle
     *
     * @return \Leevel\Database\Ddd\ISpecification
     */
    public function andClosure(Closure $spec, Closure $handle): ISpecification
    {
        $old = $this->spec;
        $oldHandle = $this->handle;

        $this->spec = function (IEntity $entity) use ($old, $spec): bool {
            return $old($entity) && $spec($entity);
        };

        $this->handle = function (Select $select, IEntity $entity) use ($oldHandle, $handle) {
            $oldHandle($select, $entity);
            $handle($select, $entity);
        };

        return $this;
    }

    /**
     * 闭包规约 Or 操作.
     *
     * @param \Closure $spec
     * @param \Closure $handle
     *
     * @return \Leevel\Database\Ddd\ISpecification
     */
    public function orClosure(Closure $spec, Closure $handle): ISpecification
    {
        $old = $this->spec;
        $oldHandle = $this->handle;

        $this->spec = function (IEntity $entity): bool {
            return true;
        };

        $this->handle = function (Select $select, IEntity $entity) use ($old, $spec, $oldHandle, $handle) {
            if ($old($entity)) {
                $oldHandle($select, $entity);
            } elseif ($spec($entity)) {
                $handle($select, $entity);
            }
        };

        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\Event;

use Closure;
use InvalidArgumentException;
use SplObserver;
use SplSubject;

/**
 * 观察者角色 observer.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.06.23
 * @see http://php.net/manual/zh/class.splobserver.php
 *
 * @version 1.0
 */
class Observer implements SplObserver
{
    /**
     * 观察者目标角色 subject.
     *
     * @var \SplSubject
     */
    protected $subject;

    /**
     * 观察者实现.
     *
     * @var \Closure
     */
    protected $handle;

    /**
     * 构造函数.
     *
     * @param \Closure $handle
     */
    public function __construct(Closure $handle = null)
    {
        $this->handle = $handle;
    }

    /**
     * 观察者实现.
     */
    public function __invoke(...$args)
    {
        call_user_func($this->handle, ...$args);
    }

    /**
     * {@inheritdoc}
     */
    public function update(SplSubject $subject): void
    {
        if (method_exists($this, 'handle')) {
            $handle = [$this, 'handle'];
        } elseif ($this->handle) {
            $handle = [$this, '__invoke'];
        } else {
            $handle = null;
        }

        if (!is_callable($handle)) {
            throw new InvalidArgumentException(
                sprintf('Observer %s must has handle method.', get_class($this))
            );
        }

        $subject->container->call($handle, $subject->notifyArgs);
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Http;

use ArrayIterator;
use Countable;
use IteratorAggregate;
use JsonSerializable;
use Leevel\Support\IArray;
use Leevel\Support\IJson;

/**
 * http bag
 * This class borrows heavily from the Symfony4 Framework and is part of the symfony package.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.02.19
 *
 * @version 1.0
 *
 * @see Symfony\Component\HttpFoundation (https://github.com/symfony/symfony)
 */
class Bag implements IArray, IJson, Countable, IteratorAggregate, JsonSerializable
{
    /**
     * 元素合集.
     *
     * @var array
     */
    protected $elements = [];

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

    /**
     * 魔术方法 __toString.
     *
     * @return string
     */
    public function __toString(): string
    {
        return $this->toJson();
    }

    /**
     * 取回元素.
     *
     * @return array
     */
    public function all(): array
    {
        return $this->elements;
    }

    /**
     * 返回元素键值.
     *
     * @return array
     */
    public function keys(): array
    {
        return array_keys($this->elements);
    }

    /**
     * 替换当前所有元素.
     *
     * @param array $elements
     */
    public function replace(array $elements = []): void
    {
        $this->elements = $elements;
    }

    /**
     * 新增元素.
     *
     * @param array $elements
     */
    public function add(array $elements = []): void
    {
        $this->elements = array_replace($this->elements, $elements);
    }

    /**
     * 取回元素值.
     *
     * @param string $key
     * @param mixed  $defaults
     *
     * @return mixed
     */
    public function get(string $key, $defaults = null)
    {
        $key = $this->normalize($key);

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

    /**
     * 设置元素值
     *
     * @param string $key
     * @param mixed  $value
     */
    public function set(string $key, $value): void
    {
        $key = $this->normalize($key);

        $this->elements[$key] = $value;
    }

    /**
     * 判断是否存在元素值
     *
     * @param string $key
     *
     * @return bool
     */
    public function has(string $key): bool
    {
        $key = $this->normalize($key);

        return array_key_exists($key, $this->elements);
    }

    /**
     * 删除元素值
     *
     * @param string $key
     */
    public function remove(string $key): void
    {
        $key = $this->normalize($key);

        if ($this->has($key)) {
            unset($this->elements[$key]);
        }
    }

    /**
     * 获取过滤变量.
     *
     * @param string           $key
     * @param mixed            $defaults
     * @param array|int|string $filter
     * @param array            $options
     *
     * @return mixed
     */
    public function filter(string $key, $defaults = null, $filter = null, array $options = [])
    {
        $key = $this->normalize($key);

        $filter = $this->parseFilter($filter);

        list($key, $filter) = $this->parseKeyFilter($key, $filter);

        $part = '';

        if (false !== strpos($key, '\\')) {
            list($key, $part) = explode('\\', $key);
        }

        $result = array_key_exists($key, $this->elements) ? $this->elements[$key] : $defaults;

        if ($part) {
            $result = $this->getPartData($part, $result, $defaults);
        }

        if ($filter) {
            $result = $this->filterValue($result, $defaults, $filter, $options);
        }

        return $result;
    }

    /**
     * 实现 Countable::count.
     *
     * @return int
     */
    public function count(): int
    {
        return count($this->elements);
    }

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

    /**
     * 实现 JsonSerializable::jsonSerialize.
     *
     * @return array
     */
    public function jsonSerialize(): array
    {
        return $this->toArray();
    }

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

        return json_encode($this->jsonSerialize(), $option);
    }

    /**
     * 实现 IteratorAggregate::getIterator.
     *
     * @return \ArrayIterator
     */
    public function getIterator(): ArrayIterator
    {
        return new ArrayIterator($this->elements);
    }

    /**
     * 分析键值和过滤器.
     *
     * @param string $key
     * @param array  $filter
     *
     * @return array
     */
    protected function parseKeyFilter(string $key, array $filter): array
    {
        if (false !== strpos($key, '|')) {
            $temp = explode('|', $key);
            $key = array_shift($temp);
            $filter = array_merge($temp, $filter);
        }

        return [$key, $filter];
    }

    /**
     * 分析过滤器.
     *
     * @param array|string $filter
     *
     * @return array
     */
    protected function parseFilter($filter = null): array
    {
        if (null === $filter) {
            return [];
        }

        return is_array($filter) ? $filter : func_get_args();
    }

    /**
     * 过滤值
     *
     * @param mixed $value
     * @param mixed $defaults
     * @param array $filters
     * @param array $options
     *
     * @return mixed
     */
    protected function filterValue($value, $defaults, array $filters, array $options = [])
    {
        foreach ($filters as $item) {
            if (is_string($item) && false !== strpos($item, '=')) {
                $value = $this->filterValueWithFunc($value, $item);
            } elseif (is_callable($item)) {
                $value = $this->filterValueWithCallable($value, $item);
            } elseif (is_scalar($value) && !empty($item)) {
                $value = $this->filterValueWithFilterVar($value, $item, $options);

                if (false === $value) {
                    $value = $defaults;

                    break;
                }
            }
        }

        return $value;
    }

    /**
     * 使用函数过滤值
     *
     * @param mixed  $value
     * @param string $filter
     *
     * @return mixed
     */
    protected function filterValueWithFunc($value, string $filter)
    {
        list($filter, $extend) = explode('=', $filter);

        if ('default' === $filter) {
            if (!is_numeric($extend) && !preg_match('/^[A-Z\_]+$/', $extend)) {
                $extend = "'".$extend."'";
            }

            $evals = "\$value = '".($value ? '1' : '')."' ?: ".$extend.';';
        } elseif ($extend) {
            if (false !== strpos($extend, ',')) {
                $tmp = explode(',', $extend);
                $result = [];

                foreach ($tmp as $v) {
                    $v = trim($v);

                    if ('**' === $v || is_numeric($v) || preg_match('/^[A-Z\_]+$/', $v)) {
                        $result[] = $v;
                    } else {
                        $result[] = "'".$v."'";
                    }
                }

                $extend = implode(',', $result);
            }

            if (strstr($extend, '**')) {
                $extend = str_replace('**', '$value', $extend);
                $evals = "\$value = {$filter}({$extend});";
            } else {
                $evals = "\$value = {$filter}(\$value, {$extend});";
            }
        }

        eval($evals);

        return $value;
    }

    /**
     * 使用回调过滤值
     *
     * @param mixed  $value
     * @param string $filter
     *
     * @return mixed
     */
    protected function filterValueWithCallable($value, string $filter)
    {
        $value = call_user_func($filter, $value);

        return $value;
    }

    /**
     * 使用 filter_var 过滤值
     *
     * @param mixed $value
     * @param mixed $filter
     * @param array $options
     *
     * @see http://php.net/manual/en/function.filter-var.php
     *
     * @return mixed
     */
    protected function filterValueWithFilterVar($value, $filter, array $options)
    {
        $value = filter_var($value, $this->parseFilterId($filter), $options);

        return $value;
    }

    /**
     * 分析转换 filter_var 参数.
     *
     * @param mixed $filter
     *
     * @return int
     */
    protected function parseFilterId($filter): int
    {
        $filter = $this->isInt($filter) ? $filter : filter_id($filter);

        return $filter;
    }

    /**
     * 判断字符串是否为数字.
     *
     * @param mixed $value
     *
     * @since bool
     */
    protected function isInt($value): bool
    {
        if (is_int($value)) {
            return true;
        }

        return ctype_digit((string) $value);
    }

    /**
     * 返回部分数组数据.
     *
     * @param string $key
     * @param mixed  $value
     * @param mixed  $defaults
     *
     * @return mixed
     */
    protected function getPartData(string $key, $value, $defaults = null)
    {
        if (!is_array($value)) {
            return $value;
        }

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

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

            $value = $value[$item];
        }

        return $value;
    }

    /**
     * 格式化键值
     *
     * @param string $key
     *
     * @return string
     */
    protected function normalize(string $key): string
    {
        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\Cache;

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

/**
 * 文件缓存.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.02.15
 *
 * @version 1.0
 */
class File extends Connect implements IConnect
{
    /**
     * 缓存文件头部.
     *
     * @var string
     */
    const HEADER = '<?php die(%s); ?>';

    /**
     * 缓存文件头部长度.
     *
     * @var int
     */
    const HEADER_LENGTH = 41;

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'time_preset' => [],
        'expire'      => 86400,
        'path'        => '',
        'serialize'   => true,
    ];

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

        // 清理文件状态缓存 http://php.net/manual/zh/function.clearstatcache.php
        clearstatcache();

        if (!is_file($cachePath)) {
            return false;
        }

        if (!is_readable($cachePath)) {
            throw new InvalidArgumentException('Cache path is not readable.');
        }

        $fp = fopen($cachePath, 'r');
        flock($fp, LOCK_SH);

        // 头部的 41 个字节存储了安全代码
        $len = filesize($cachePath);
        fread($fp, static::HEADER_LENGTH);
        $len -= static::HEADER_LENGTH;

        do {
            // 检查缓存是否已经过期
            if ($this->isExpired($name, $option)) {
                $data = false;

                break;
            }

            if ($len > 0) {
                $data = fread($fp, $len);
            } else {
                $data = false;
            }
        } while (false);

        flock($fp, LOCK_UN);
        fclose($fp);

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

        // 解码
        if ($option['serialize']) {
            $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);
        }

        $data = sprintf(static::HEADER, '/* '.date('Y-m-d H:i:s').'  */').$data;

        $cachePath = $this->getCachePath($name);

        $this->writeData($cachePath, $data);
    }

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

        if ($this->exist($name)) {
            unlink($cachePath);
        }
    }

    /**
     * 关闭.
     */
    public function close(): void
    {
    }

    /**
     * 验证缓存是否过期
     *
     * @param string $name
     * @param array  $option
     *
     * @return bool
     */
    protected function isExpired(string $name, array $option): bool
    {
        $filePath = $this->getCachePath($name);

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

        if ($option['expire'] <= 0) {
            return true;
        }

        return filemtime($filePath) + (int) $option['expire'] < time();
    }

    /**
     * 获取缓存路径.
     *
     * @param string $name
     *
     * @return string
     */
    protected function getCachePath(string $name): string
    {
        if (!$this->option['path']) {
            throw new InvalidArgumentException('Cache path is not allowed empty.');
        }

        return $this->option['path'].'/'.$this->getCacheName($name).'.php';
    }

    /**
     * 写入缓存数据.
     *
     * @param string $fileName
     * @param string $data
     */
    protected function writeData(string $fileName, string $data): void
    {
        create_file($fileName);

        file_put_contents($fileName, $data, LOCK_EX);
    }

    /**
     * 验证缓存是否存在.
     *
     * @param string $name
     *
     * @return bool
     */
    protected function exist(string $name): bool
    {
        return is_file($this->getCachePath($name));
    }

    /**
     * 获取缓存名字
     * 去掉特殊缓存名字字符.
     *
     * @param string $name
     *
     * @return string
     */
    protected function getCacheName(string $name): string
    {
        return str_replace([
            '?',
            '*',
            ':',
            '"',
            '<',
            '>',
            '\\',
            '/',
            '|',
        ], '.', parent::getCacheName($name));
    }
}

// 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\Kernel\Bootstrap;

use Leevel\Kernel\IApp;
use Leevel\Option\Load;
use Leevel\Option\Option;
use RuntimeException;

/**
 * 读取配置.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.04.24
 *
 * @version 1.0
 */
class LoadOption
{
    /**
     * 响应.
     *
     * @param \Leevel\Kernel\IApp $app
     */
    public function handle(IApp $app): void
    {
        $this->checkRuntimeEnv($app);

        if ($app->isCachedOption()) {
            $data = (array) include $app->optionCachedPath();

            $this->setEnvs($data['app']['_env']);
        } else {
            $load = new Load($app->optionPath());

            $data = $load->loadData($app);
        }

        $app->instance('option', $option = new Option($data));

        $test = 2 === func_num_args();

        if (!$test) {
            // @codeCoverageIgnoreStart
            $this->initialization($option);
            // @codeCoverageIgnoreEnd
        }
    }

    /**
     * 载入运行时环境变量.
     *
     * @param \Leevel\Kernel\IApp $appy
     */
    protected function checkRuntimeEnv(IApp $app): void
    {
        if (!getenv('RUNTIME_ENVIRONMENT')) {
            return;
        }

        $file = '.'.getenv('RUNTIME_ENVIRONMENT');

        // 校验运行时环境，防止测试用例清空非测试库的业务数据
        if (!is_file($fullFile = $app->envPath().'/'.$file)) {
            throw new RuntimeException(sprintf('Env file `%s` was not found.', $fullFile));
        }

        $app->setEnvFile($file);
    }

    /**
     * 初始化处理.
     *
     * @param array $env
     */
    protected function setEnvs(array $env): void
    {
        foreach ($env as $name => $value) {
            $this->setEnvVar($name, $value);
        }
    }

    /**
     * 设置环境变量.
     *
     * @param string           $name
     * @param null|bool|string $value
     */
    protected function setEnvVar(string $name, $value = null): void
    {
        if (is_bool($value)) {
            putenv($name.'='.($value ? '(true)' : '(false)'));
        } elseif (null === $value) {
            putenv($name.'(null)');
        } else {
            putenv($name.'='.$value);
        }

        $_ENV[$name] = $value;
        $_SERVER[$name] = $value;
    }

    /**
     * 初始化处理.
     *
     * @param \Leevel\Option\Option $option
     * @codeCoverageIgnore
     */
    protected function initialization(Option $option): void
    {
        mb_internal_encoding('UTF-8');

        if (function_exists('date_default_timezone_set')) {
            date_default_timezone_set($option->get('time_zone', 'UTC'));
        }

        if (\PHP_SAPI === 'cli') {
            return;
        }

        if (function_exists('gz_handler') && $option->get('start_gzip')) {
            ob_start('gz_handler');
        } else {
            ob_start();
        }
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Kernel\Utils;

use Leevel\Filesystem\Fso\create_file;
use function Leevel\Filesystem\Fso\create_file;
use ReflectionClass;
use ReflectionMethod;

/**
 * 文档解析 Markdown.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2019.02.27
 *
 * @version 1.0
 *
 * @todo 将 markdown 模板提炼出来
 * @todo 为本功能编写单元测试用例
 * @codeCoverageIgnore
 */
class Doc
{
    /**
     * 解析文档保存基础路径.
     *
     * @var string
     */
    protected $basePath;

    /**
     * 解析文档保存路径.
     *
     * @var string
     */
    protected $savePath;

    /**
     * 解析文档行内容.
     *
     * @var array
     */
    protected $lines = [];

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

    /**
     * 解析文档.
     *
     * @param string $className
     *
     * @return string
     */
    public function handle(string $className): string
    {
        $this->lines = $this->parseFileContnet($reflection = new ReflectionClass($className));

        if (!($markdown = $this->parseClassContent($reflection))) {
            return '';
        }

        $markdown .= $this->parseMethodContents($reflection);

        return $markdown;
    }

    /**
     * 解析文档并保存.
     *
     * @param string $className
     * @param string $path
     *
     * @return bool
     */
    public function handleAndSave(string $className, ?string $path = null): bool
    {
        $this->setSavePath($path);

        $markdown = trim($this->handle($className));

        if (!$markdown || !$this->savePath) {
            return false;
        }

        $this->writeCache($this->savePath, $markdown);

        return true;
    }

    /**
     * 获取方法内容.
     *
     * @param string $className
     * @param string $method
     * @param bool   $isDoc
     *
     * @return string
     */
    public static function getMethodBody(string $className, string $method, bool $isDoc = false): string
    {
        $doc = new static('');

        $lines = $doc->parseFileContnet(new ReflectionClass($className));

        $method = new ReflectionMethod($className, $method);

        return $doc->parseMethodBody($lines, $method, $isDoc);
    }

    /**
     * 获取类内容.
     *
     * @param string $className
     *
     * @return string
     */
    public static function getClassBody(string $className): string
    {
        $doc = new static('');

        $lines = $doc->parseFileContnet($reflectionClass = new ReflectionClass($className));

        $startLine = $reflectionClass->getStartLine() - 1;
        $endLine = $reflectionClass->getEndLine();
        $hasUse = false;
        $result = [];
        $result[] = 'namespace '.$reflectionClass->getNamespaceName().';';
        $result[] = '';

        foreach ($lines as $k => $v) {
            if ($k < $startLine || $k >= $endLine) {
                // 适用于一个文件只有一个类的情况
                if ($k < $startLine && 0 === strpos($v, 'use ')) {
                    $result[] = $v;
                    $hasUse = true;
                }

                continue;
            }

            if ($k === $startLine && true === $hasUse) {
                $result[] = '';
            }

            $result[] = $v;
        }

        return implode(PHP_EOL, $result);
    }

    /**
     * 设置保存路径.
     *
     * @param string $path
     */
    protected function setSavePath(?string $path = null): void
    {
        if (null === $path) {
            $this->savePath = null;
        } else {
            $this->savePath = $this->basePath.'/'.$path.'.md';
        }
    }

    /**
     * 方法是否需要被解析.
     *
     * @param \ReflectionMethod $method
     *
     * @return bool
     */
    protected function isMethodNeedParsed(ReflectionMethod $method): bool
    {
        $name = $method->getName();

        return 0 === strpos($name, 'test') || 0 === strpos($name, 'doc');
    }

    /**
     * 解析文档内容.
     *
     * @param \ReflectionClass $reflection
     *
     * @return array
     */
    protected function parseFileContnet(ReflectionClass $reflection): array
    {
        return explode(PHP_EOL, file_get_contents($reflection->getFileName()));
    }

    /**
     * 解析文档注解内容.
     *
     * @param \ReflectionClass $reflection
     *
     * @return string
     */
    protected function parseClassContent(ReflectionClass $reflection): string
    {
        if (!($comment = $reflection->getDocComment()) ||
            !($info = $this->parseComment($comment))) {
            return '';
        }

        $data = [];
        $data[] = $this->formatTitle($info['title'] ?? '', true);
        $data[] = $this->formatDescription($info['description'] ?? '');
        $data[] = $this->formatUsers($reflection);

        // 指定保存路径
        if (isset($info['path'])) {
            $this->setSavePath($info['path']);
        }

        return implode(PHP_EOL, $data);
    }

    /**
     * 解析所有方法注解内容.
     *
     * @param \ReflectionClass $reflection
     *
     * @return string
     */
    protected function parseMethodContents(ReflectionClass $reflection): string
    {
        $markdown = '';

        foreach ($reflection->getMethods() as $method) {
            if (!$this->isMethodNeedParsed($method)) {
                continue;
            }

            $markdown .= $this->parseMethodContent($method);
        }

        return $markdown;
    }

    /**
     * 解析方法注解内容.
     *
     * @param \ReflectionMethod $comment
     *
     * @return string
     */
    protected function parseMethodContent(ReflectionMethod $method): string
    {
        if (!($comment = $method->getDocComment()) ||
            !($info = $this->parseComment($comment))) {
            return '';
        }

        $data = [];
        $data[] = $this->formatTitle($info['title'] ?? '');
        $data[] = $this->formatDescription($info['description'] ?? '');
        $data[] = $this->formatBody($method, $info['lang'] ?? 'php');
        $data[] = $this->formatNote($info['note'] ?? '');

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

    /**
     * 格式化标题.
     *
     * @param string $title
     * @param bool   $isHeader
     *
     * @return string
     */
    protected function formatTitle(string $title, bool $isHeader = false): string
    {
        if ($title) {
            $title = ($isHeader ? '#' : '##')." {$title}".PHP_EOL;
        }

        return $title;
    }

    /**
     * 格式化 uses.
     *
     * @param \ReflectionClass $reflection
     *
     * @return string
     */
    protected function formatUsers(ReflectionClass $reflection): string
    {
        $uses = $this->parseUseDefined($this->lines, $reflection);

        if ($uses) {
            $uses = <<<eot
                **引入相关类**

                {$uses}

                eot;
        }

        return $uses;
    }

    /**
     * 格式化描述.
     *
     * @param string $description
     *
     * @return string
     */
    protected function formatDescription(string $description): string
    {
        if ($description) {
            $description = $description.PHP_EOL;
        }

        return $description;
    }

    /**
     * 格式化注意事项.
     *
     * @param string $note
     *
     * @return string
     */
    protected function formatNote(string $note): string
    {
        if ($note) {
            $note = <<<eot
                ::: tip
                {$note}
                :::

                eot;
        }

        return $note;
    }

    /**
     * 格式化内容.
     *
     * @param \ReflectionMethod $method
     * @param string            $lang
     *
     * @return string
     */
    protected function formatBody(ReflectionMethod $method, string $lang): string
    {
        $body = $this->parseMethodBody($this->lines, $method, 0 === strpos($method->getName(), 'doc'));

        if ($body) {
            $body = <<<eot
                ``` {$lang}
                {$body}
                ```

                eot;
        }

        return $body;
    }

    /**
     * 解析 use 导入类.
     *
     * @param array            $lines
     * @param \ReflectionClass $classRef
     *
     * @return string
     */
    protected function parseUseDefined(array $lines, ReflectionClass $classRef): string
    {
        $startLine = $classRef->getStartLine() - 1;
        $result = [];

        foreach ($lines as $k => $v) {
            $v = trim($v);

            if ($k >= $startLine) {
                break;
            }

            if (0 === strpos($v, 'use ') &&
                !in_array($v, ['use Tests\TestCase;'], true) &&
                false === strpos($v, '\\Fixtures\\')) {
                $result[] = ' * '.$v;
            }
        }

        return implode(PHP_EOL, $result);
    }

    /**
     * 解析方法内容.
     *
     * @param array             $lines
     * @param \ReflectionMethod $method
     * @param bool              $isDoc
     *
     * @return string
     */
    protected function parseMethodBody(array $lines, ReflectionMethod $method, bool $isDoc = false): string
    {
        $startLine = $method->getStartLine() - 1;
        $endLine = $method->getEndLine();
        $offsetLength = 4;
        $findEot = false;
        $result = [];

        // 文档类删除周围的函数定制
        // 并且删除掉注释 `/**/` 标记
        if ($isDoc) {
            $startLine += 3;
            $endLine -= 2;
            $offsetLength = 8;
        }

        foreach ($lines as $k => $v) {
            if ($k < $startLine || $k >= $endLine) {
                continue;
            }

            if (false !== strpos($v, 'eot')) {
                $findEot = true;

                break;
            }
        }

        foreach ($lines as $k => $v) {
            if ($k < $startLine || $k >= $endLine) {
                continue;
            }

            // 占位符行
            if ('#_' === trim($v)) {
                continue;
            }

            if (true === $findEot) {
                // eot 需要特别处理
                if ($k === $startLine ||
                    $k === $startLine + 1 ||
                    $k === $endLine - 1 ||
                    (strlen($v) - strlen(ltrim($v)) >= $offsetLength + 4)) {
                    $result[] = substr($v, $offsetLength);
                } else {
                    $result[] = $v;
                }
            } else {
                $result[] = substr($v, $offsetLength);
            }
        }

        return implode(PHP_EOL, $result);
    }

    /**
     * 获取 API 注解信息.
     *
     * @param string $comment
     *
     * @return array
     */
    protected function parseComment(string $comment): array
    {
        $comments = explode(PHP_EOL, $comment);
        $findApi = false;
        $result = [];
        $code = ['$result = ['];

        foreach ($comments as $v) {
            $v = trim($v, '* ');
            $v = trim($v, '_'); // 删除占位符

            if ('@api(' === $v) {
                $findApi = true;
            } elseif (true === $findApi) {
                if (')' === $v) {
                    break;
                }
                // 匹配字段格式，以便于支持多行
                if (preg_match('/^\S+=\"/', $v)) {
                    $pos = strpos($v, '=');
                    $left = '"'.substr($v, 0, $pos).'"';
                    $right = substr($v, $pos + 1);
                    $right = str_replace('$', '\$', $right); // 转义变量

                    $code[] = $left.'=>'.$right;
                } else {
                    // 空行加两个换行符撑开
                    if ('' === $v) {
                        $code[] = PHP_EOL.PHP_EOL;
                    } else {
                        $code[] = str_replace('$', '\$', $v).PHP_EOL; // 转义变量
                    }
                }
            }
        }

        $code[] = '];';
        eval(implode('', $code));

        return $result;
    }

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

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

use BadMethodCallException;
use Closure;
use InvalidArgumentException;
use Leevel\Di\IContainer;
use Leevel\Flow\FlowControl;
use function Leevel\Support\Arr\normalize;
use Leevel\Support\Arr\normalize;
use function Leevel\Support\Str\camelize;
use Leevel\Support\Str\camelize;
use function Leevel\Support\Str\un_camelize;
use Leevel\Support\Str\un_camelize;

/**
 * Validator 数据验证器.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.05.02
 *
 * @version 1.0
 */
class Validator implements IValidator
{
    use FlowControl;

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

    /**
     * 待验证数据.
     *
     * @var array
     */
    protected $datas = [];

    /**
     * 验证规则.
     *
     * @var array
     */
    protected $rules = [];

    /**
     * 默认验证提示信息.
     *
     * @var array
     */
    protected static $defaultMessages = [];

    /**
     * 验证提示信息.
     *
     * @var array
     */
    protected $messages = [];

    /**
     * 字段名字.
     *
     * @var array
     */
    protected $names = [];

    /**
     * 错误规则.
     *
     * @var array
     */
    protected $failedRules;

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

    /**
     * 需要跳过的验证规则
     * 用于扩展属性支持
     *
     * @var array
     */
    protected $skipRule = [];

    /**
     * 扩展验证器.
     *
     * @var array
     */
    protected $extends = [];

    /**
     * 验证后续事件.
     *
     * @var array
     */
    protected $afters = [];

    /**
     * 验证别名.
     *
     * @var array
     */
    protected $alias = [
        'confirm' => 'equal_to',
        'gt'      => 'greater_than',
        '>'       => 'greater_than',
        'egt'     => 'equal_greater_than',
        '>='      => 'equal_greater_than',
        'lt'      => 'less_than',
        '<'       => 'less_than',
        'elt'     => 'equal_less_than',
        '<='      => 'equal_less_than',
        'eq'      => 'equal',
        '='       => 'equal',
        'neq'     => 'not_equal',
        '!='      => 'not_equal',
    ];

    /**
     * 构造函数.
     *
     * @param array $datas
     * @param array $rules
     * @param array $names
     * @param array $messages
     */
    public function __construct(array $datas = [], array $rules = [], array $names = [], array $messages = [])
    {
        $this->data($datas);
        $this->rule($rules);
        $this->name($names);
        $this->message($messages);
    }

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

        if (0 === strpos($method, 'validate')) {
            $extend = un_camelize(substr($method, 8));

            if (isset($this->extends[$extend])) {
                return $this->callExtend($extend, $args);
            }
        }

        if (count($args) > 0) {
            $extend = 'validate'.ucfirst($method);

            $parameter = [''];
            $parameter[] = array_shift($args);
            $parameter[] = $args;
            unset($args);

            if (class_exists($fn = __NAMESPACE__.'\\Helper\\validate_'.un_camelize($method))) {
                array_shift($parameter);

                return $fn(...$parameter);
            }

            $extend = un_camelize($method);

            if (isset($this->extends[$extend])) {
                return $this->callExtend($extend, $parameter);
            }
        }

        $e = sprintf('Method %s is not exits.', $method);

        throw new BadMethodCallException($e);
    }

    /**
     * 初始化验证器.
     *
     * @param array $datas
     * @param array $rules
     * @param array $names
     * @param array $messages
     *
     * @return \Leevel\Validate\IValidator
     */
    public static function make(array $datas = [], array $rules = [], array $names = [], array $messages = []): IValidator
    {
        return new static($datas, $rules, $names, $messages);
    }

    /**
     * 验证是否成功
     *
     * @return bool
     */
    public function success(): bool
    {
        $skipRule = $this->getSkipRule();

        $this->failedRules = $this->errorMessages = [];

        foreach ($this->rules as $field => $rules) {
            foreach ($rules as $rule) {
                if (in_array($rule, $skipRule, true)) {
                    continue;
                }

                if (false === $this->doValidateItem($field, $rule)) {
                    // 验证失败跳过剩余验证规则
                    if ($this->shouldSkipOther($field)) {
                        break 2;
                    }

                    // 验证失败跳过自身剩余验证规则
                    if ($this->shouldSkipSelf($field)) {
                        break;
                    }
                }
            }
        }

        unset($skipRule);

        foreach ($this->afters as $after) {
            $after();
        }

        return 0 === count($this->errorMessages);
    }

    /**
     * 验证是否失败.
     *
     * @return bool
     */
    public function fail(): bool
    {
        return !$this->success();
    }

    /**
     * 返回所有错误消息.
     *
     * @return array
     */
    public function error(): array
    {
        return $this->errorMessages;
    }

    /**
     * 返回验证数据.
     *
     * @return array
     */
    public function getData(): array
    {
        return $this->datas;
    }

    /**
     * 设置验证数据.
     *
     * @param array $datas
     *
     * @return $this
     */
    public function data(array $datas): IValidator
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->datas = $datas;

        return $this;
    }

    /**
     * 添加验证数据.
     *
     * @param array $datas
     *
     * @return $this
     */
    public function addData(array $datas): IValidator
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

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

        return $this;
    }

    /**
     * 返回验证规则.
     *
     * @return array
     */
    public function getRule(): array
    {
        return $this->rules;
    }

    /**
     * 设置验证规则.
     *
     * @param array         $rules
     * @param null|\Closure $calCallback
     *
     * @return $this
     */
    public function rule(array $rules, Closure $callbacks = null): IValidator
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        if (null === $callbacks || $this->isCallbackValid($callbacks)) {
            $this->rules = $this->arrayRule($rules);
        }

        return $this;
    }

    /**
     * 添加验证规则.
     *
     * @param array         $rules
     * @param null|\Closure $calCallback
     *
     * @return $this
     */
    public function addRule(array $rules, Closure $callbacks = null): IValidator
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        if (null === $callbacks || $this->isCallbackValid($callbacks)) {
            $this->rules = array_merge($this->rules, $this->arrayRule($rules));
        }

        return $this;
    }

    /**
     * 返回验证消息.
     *
     * @return array
     */
    public function getMessage(): array
    {
        return $this->messages;
    }

    /**
     * 设置验证消息.
     *
     * @param array $messages
     *
     * @return $this
     */
    public function message(array $messages): IValidator
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->messages = $this->arrayMessage($messages);

        return $this;
    }

    /**
     * 添加验证消息.
     *
     * @param array $messages
     *
     * @return $this
     */
    public function addMessage(array $messages): IValidator
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->messages = array_merge(
            $this->messages,
            $this->arrayMessage($messages)
        );

        return $this;
    }

    /**
     * 返回名字.
     *
     * @return array
     */
    public function getName(): array
    {
        return $this->names;
    }

    /**
     * 设置名字.
     *
     * @param array $names
     *
     * @return $this
     */
    public function name(array $names): IValidator
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->names = $names;

        return $this;
    }

    /**
     * 添加名字.
     *
     * @param array $names
     *
     * @return $this
     */
    public function addName(array $names): IValidator
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

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

        return $this;
    }

    /**
     * 设置别名.
     *
     * @param string $name
     * @param string $alias
     *
     * @return $this
     */
    public function alias(string $name, string $alias): IValidator
    {
        if (in_array($name, $this->getSkipRule(), true)) {
            $e = sprintf('You cannot set alias for skip rule %s.', $name);

            throw new InvalidArgumentException($e);
        }

        $this->alias[$alias] = $name;

        return $this;
    }

    /**
     * 批量设置别名.
     *
     * @param array $alias
     *
     * @return $this
     */
    public function aliasMany(array $alias): IValidator
    {
        foreach ($alias as $alias => $value) {
            $this->alias($alias, $value);
        }

        return $this;
    }

    /**
     * 设置验证后事件.
     *
     * @param \Closure $callbacks
     *
     * @return $this
     */
    public function after(Closure $callbacks): IValidator
    {
        $this->afters[] = function () use ($callbacks) {
            return $callbacks($this);
        };

        return $this;
    }

    /**
     * 注册自定义扩展.
     *
     * @param string          $rule
     * @param \Closure|string $extends
     *
     * @return $this
     */
    public function extend(string $rule, $extends): IValidator
    {
        $this->extends[strtolower($rule)] = $extends;

        return $this;
    }

    /**
     * 设置 IOC 容器.
     *
     * @param \Leevel\Di\IContainer $container
     *
     * @return $this
     */
    public function setContainer(IContainer $container): IValidator
    {
        $this->container = $container;

        return $this;
    }

    /**
     * 初始化默认的消息.
     *
     * @param array $messages
     */
    public static function initMessages(array $messages): void
    {
        static::$defaultMessages = $messages;
    }

    /**
     * 尝试读取格式化条件.
     *
     * @param string       $field
     * @param array|string $rules
     *
     * @return array
     */
    public function getParseRule(string $field, $rules): array
    {
        $rules = (array) $rules;

        foreach ($this->rules[$field] as $rule) {
            list($rule, $parameter) = $this->parseRule($rule);

            if (in_array($rule, $rules, true)) {
                return [$rule, $parameter];
            }
        }

        return [];
    }

    /**
     * 获取字段的值
     *
     * @param string $rule
     *
     * @return mixed
     */
    public function getFieldValue(string $rule)
    {
        if (false === strpos($rule, '.')) {
            if (isset($this->datas[$rule])) {
                return $this->datas[$rule];
            }
        } else {
            $parts = explode('.', $rule);
            $datas = $this->datas;

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

                $datas = $datas[$part];
            }

            return $datas;
        }
    }

    /**
     * 转换消息为数组.
     *
     * @param array $messages
     *
     * @return array
     */
    protected function arrayMessage(array $messages): array
    {
        $result = [];

        foreach ($messages as $field => $message) {
            // 字段消息或者通配符
            // ['name' => ['required' => '{field} required']]
            // ['na*' => 'foo bar']
            if (is_array($message) || false !== strpos($field, '*')) {
                if (false === strpos($field, '*')) {
                    $result = array_merge($result,
                        $this->arrayMessageItem($field, $message)
                    );
                } else {
                    $result = array_merge($result,
                        $this->wildcardMessageItem($field, $message)
                    );
                }
            }

            // 直接消息
            // ['required' => '{field} required']
            // ['name.required' => '{field} required']
            else {
                $result[$field] = $message;
            }
        }

        return $result;
    }

    /**
     * 分析通配符消息.
     *
     * @param string $field
     * @param mixed  $message
     *
     * @return array
     */
    protected function wildcardMessageItem(string $field, $message): array
    {
        $field = $this->prepareRegexForWildcard($field);

        $messages = [];

        foreach ($this->parseDataKey() as $key) {
            if (preg_match($field, $key, $matche)) {
                $messages = array_merge($messages,
                    $this->arrayMessageItem($key, $message)
                );
            }
        }

        return $messages;
    }

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

        return $regex;
    }

    /**
     * 转换单条消息为数组.
     *
     * @param string       $field
     * @param array|string $message
     *
     * @return array
     */
    protected function arrayMessageItem(string $field, $message): array
    {
        $result = [];

        if (is_array($message)) {
            foreach ($message as $key => $message) {
                $result[$field.'.'.$key] = $message;
            }
        } else {
            foreach ($this->getFieldRuleWithoutSkip($field) as $rule) {
                $result[$field.'.'.$rule] = $message;
            }
        }

        return $result;
    }

    /**
     * 获取单个字段验证规则，排除掉绕过的规则.
     *
     * @param string $field
     *
     * @return array
     */
    protected function getFieldRuleWithoutSkip(string $field): array
    {
        return array_diff($this->getFieldRule($field), $this->getSkipRule());
    }

    /**
     * 获取需要跳过的验证规则.
     *
     * @return array
     */
    protected function getSkipRule(): array
    {
        return array_merge([
            static::CONDITION_EXISTS,
            static::CONDITION_MUST,
            static::CONDITION_VALUE,
            static::SKIP_SELF,
            static::SKIP_OTHER,
        ], $this->skipRule);
    }

    /**
     * 获取单个字段验证规则.
     *
     * @param string $field
     *
     * @return array
     */
    protected function getFieldRule(string $field): array
    {
        if (isset($this->rules[$field])) {
            return $this->rules[$field];
        }

        return [];
    }

    /**
     * 分析验证规则和参数.
     *
     * @param string $rule
     *
     * @return array
     */
    protected function parseRule(string $rule): array
    {
        $parameter = [];

        if (false !== strpos($rule, ':')) {
            list($rule, $parameter) = explode(':', $rule, 2);

            if (isset($this->alias[$rule])) {
                $rule = $this->alias[$rule];
            }

            $parameter = $this->parseParameters($rule, $parameter);
        }

        return [trim($rule), $parameter];
    }

    /**
     * 转换规则为数组.
     *
     * @param array $rules
     *
     * @return array
     */
    protected function arrayRule(array $rules): array
    {
        $result = [];

        foreach ($rules as $field => $rules) {
            if (false === strpos($field, '*')) {
                $result[$field] = $this->arrayRuleItem($rules);
            } else {
                $result = array_merge($result, $this->wildcardRuleItem($field, $rules));
            }
        }

        return $result;
    }

    /**
     * 转换单条规则为数组.
     *
     *
     * @param mixed $rules
     *
     * @return array
     */
    protected function arrayRuleItem($rules): array
    {
        return normalize($rules, '|');
    }

    /**
     * 分析通配符规则.
     *
     * @param string $field
     * @param mixed  $rules
     *
     * @return array
     */
    protected function wildcardRuleItem(string $field, $rules): array
    {
        $field = $this->prepareRegexForWildcard($field);

        $result = [];

        foreach ($this->parseDataKey() as $key) {
            if (preg_match($field, $key, $matche)) {
                $result[$key] = $this->arrayRuleItem($rules);
            }
        }

        return $result;
    }

    /**
     * 返回分析后的数据键.
     *
     * @return array
     */
    protected function parseDataKey(): array
    {
        return $this->parseDataKeyRecursion($this->getData());
    }

    /**
     * 递归分析.
     *
     * @param array  $datas
     * @param string $parentKey
     *
     * @return array
     */
    protected function parseDataKeyRecursion(array $datas, string $parentKey = ''): array
    {
        $dataKeys = [];

        foreach ($datas as $key => $datas) {
            $first = ($parentKey ? $parentKey.'.' : '').$key;

            if (is_array($datas)) {
                $dataKeys = array_merge($dataKeys, $this->parseDataKeyRecursion($datas, $first));
            } else {
                $dataKeys[] = $first;
            }
        }

        return $dataKeys;
    }

    /**
     * 是否存在单个字段验证规则
     * 不带条件的简单规则.
     *
     * @param string $field
     * @param string $rule
     *
     * @return mixed
     */
    protected function hasFieldRuleWithoutParameter(string $field, string $rule)
    {
        $result = $this->hasFieldRuleWithoutParameterReal($field, $rule);

        if (!$result && $rule === static::DEFAULT_CONDITION) {
            return !$this->hasFieldRuleWithoutParameterReal($field, [
                static::CONDITION_MUST,
                static::CONDITION_VALUE,
            ]);
        }

        return $result;
    }

    /**
     * 是否存在单个字段验证规则
     * 不带条件的简单规则.
     *
     * @param string $field
     * @param mixed  $rules
     *
     * @return bool
     */
    protected function hasFieldRuleWithoutParameterReal(string $field, $rules): bool
    {
        if (!isset($this->rules[$field])) {
            return false;
        }

        $rules = (array) $rules;

        foreach ($rules as $rule) {
            if (in_array($rule, $this->rules[$field], true)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 解析变量.
     *
     * @param string $rule
     * @param string $parameter
     *
     * @return array
     */
    protected function parseParameters(string $rule, string $parameter): array
    {
        if ('regex' === strtolower($rule)) {
            return [$parameter];
        }

        return explode(',', $parameter);
    }

    /**
     * 验证字段规则.
     *
     * @param string $field
     * @param string $rule
     *
     * @return bool|void
     */
    protected function doValidateItem(string $field, string $rule)
    {
        list($rule, $parameter) = $this->parseRule($rule);

        if ('' === $rule) {
            return;
        }

        $fieldValue = $this->getFieldValue($field);

        // 默认情况下存在即验证，没有设置字段则跳过
        if (!$this->hasFieldValue($field) &&
            $this->hasFieldRuleWithoutParameter($field, static::CONDITION_EXISTS)) {
            return;
        }

        // 值不为空就验证，那么为的空的值将会跳过
        if (empty($fieldValue) &&
            $this->hasFieldRuleWithoutParameter($field, static::CONDITION_VALUE)) {
            return;
        }

        if (class_exists($fn = __NAMESPACE__.'\\Helper\\validate_'.$rule)) {
            if (!$fn($fieldValue, $parameter, $this, $field)) {
                $this->addFailure($field, $rule, $parameter);

                return false;
            }
        } elseif (class_exists($className = __NAMESPACE__.'\\'.($camelizeRule = ucwords(camelize($rule))).'Rule')) {
            if ($this->container) {
                $validateRule = $this->container->make($className);
            } else {
                $validateRule = new $className();
            }

            if (false === $validateRule->validate($fieldValue, $parameter, $this, $field)) {
                $this->addFailure($field, $rule, $parameter);

                return false;
            }
        } elseif (!$this->{'validate'.$camelizeRule}($fieldValue, $parameter, $this, $field)) {
            $this->addFailure($field, $rule, $parameter);

            return false;
        }

        unset($fieldValue);

        return true;
    }

    /**
     * 是否需要终止其他验证
     *
     * @param string $field
     *
     * @return bool
     */
    protected function shouldSkipOther(string $field): bool
    {
        return $this->hasFieldRuleWithoutParameter($field, static::SKIP_OTHER);
    }

    /**
     * 是否需要终止自己其他验证
     *
     * @param string $field
     *
     * @return bool
     */
    protected function shouldSkipSelf(string $field): bool
    {
        return $this->hasFieldRuleWithoutParameter($field, static::SKIP_SELF);
    }

    /**
     * 添加错误规则和验证错误消息.
     *
     * @param string $field
     * @param string $rule
     * @param array  $parameter
     */
    protected function addFailure(string $field, string $rule, array $parameter): void
    {
        $this->addError($field, $rule, $parameter);

        $this->failedRules[$field][$rule] = $parameter;
    }

    /**
     * 添加验证错误消息.
     *
     * @param string $field
     * @param string $rule
     * @param array  $parameter
     */
    protected function addError(string $field, string $rule, array $parameter): void
    {
        $message = $this->getFieldRuleMessage($field, $rule);

        $replace = [
            'field' => $this->parseFieldName($field),
        ];

        if (!$this->isImplodeRuleParameter($rule)) {
            foreach ($parameter as $key => $parameter) {
                $replace['rule'.($key ?: '')] = $parameter;
            }
        } else {
            $replace['rule'] = implode(',', $parameter);
        }

        $message = preg_replace_callback('/{(.+?)}/', function ($matche) use ($replace) {
            return $replace[$matche[1]] ?? $matche[0];
        }, $message);

        $this->errorMessages[$field][] = $message;

        unset($replace, $message);
    }

    /**
     * 获取验证消息.
     *
     * @param string $field
     * @param string $rule
     *
     * @return string
     */
    protected function getFieldRuleMessage(string $field, string $rule): string
    {
        return $this->messages[$field.'.'.$rule] ?? $this->messages[$rule] ??
            static::$defaultMessages[$rule] ?? '';
    }

    /**
     * 获取字段名字.
     *
     * @param string $field
     *
     * @return string
     */
    protected function parseFieldName(string $field): string
    {
        return $this->names[$field] ?? $field;
    }

    /**
     * 是否存在字段的值
     *
     * @param string $rule
     *
     * @return bool
     */
    protected function hasFieldValue(string $rule): bool
    {
        return array_key_exists($rule, $this->datas);
    }

    /**
     * 返回需要合并的规则参数.
     *
     * @param string $rule
     *
     * @return bool
     */
    protected function isImplodeRuleParameter(string $rule): bool
    {
        return in_array($rule, [
            'in',
            'not_in',
            'allow_ip',
            'deny_ip',
        ], true);
    }

    /**
     * 调用自定义验证器类.
     *
     * @param string $extend
     * @param array  $parameter
     *
     * @return bool
     */
    protected function callClassExtend(string $extend, array $parameter): bool
    {
        if (!$this->container) {
            throw new InvalidArgumentException('Container was not set.');
        }

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

        if (!is_object($extend = $this->container->make($className))) {
            $e = sprintf('Extend class %s is not valid.', $className);

            throw new InvalidArgumentException($e);
        }

        $parameter[] = $this;

        return $extend->{$method}(...$parameter);
    }

    /**
     * 调用自定义验证器.
     *
     * @param string $rule
     * @param array  $parameter
     *
     * @return bool
     */
    protected function callExtend(string $rule, array $parameter): bool
    {
        $extends = $this->extends[$rule];

        if (is_callable($extends)) {
            $parameter[] = $this;

            return $extends(...$parameter);
        }

        if (is_string($extends)) {
            return $this->callClassExtend($extends, $parameter);
        }

        $e = sprintf('Extend in rule %s is not valid.', $rule);

        throw new InvalidArgumentException($e);
    }

    /**
     * 验证条件是否通过.
     *
     * @param \Closure $calCallback
     *
     * @return bool
     */
    protected function isCallbackValid(Closure $callbacks): bool
    {
        return $callbacks($this->getData());
    }
}

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


<?php

declare(strict_types=1);

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

namespace Leevel\Protocol;

use Leevel\Di\IContainer;
use Leevel\Protocol\Thrift\Base\TFramedTransportFactory;
use Leevel\Protocol\Thrift\Base\ThriftServer;
use Leevel\Protocol\Thrift\Service\ThriftHandler;
use Leevel\Protocol\Thrift\Service\ThriftProcessor;
use Swoole\Server as SwooleServer;
use Thrift\Factory\TBinaryProtocolFactory;
use Thrift\Server\TServerSocket;

/**
 * Rpc 服务
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.04.04
 * @see https://wiki.swoole.com/wiki/page/287.html
 *
 * @version 1.0
 */
class RpcServer extends Server implements IServer
{
    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        // 监听 IP 地址
        // see https://wiki.swoole.com/wiki/page/p-server.html
        // see https://wiki.swoole.com/wiki/page/327.html
        'host' => '0.0.0.0',

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

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

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

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

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

        // 数据包分发策略
        // 1：收到会轮询分配给每一个 worker 进程
        // 3：抢占模式，系统会根据 worker 进程的闲置状态，只会投递给闲置的 worker 进程
        // https://wiki.swoole.com/wiki/page/277.html
        'dispatch_mode' => 1,

        // 打开包长检测
        // 包体长度检测提供了固定包头和包体这种协议格式的检测。
        // 启用后可以保证 worker 进程 onReceive 每一次都收到完整的包
        // https://wiki.swoole.com/wiki/page/287.html
        'open_length_check' => true,

        // 最大请求包长度，8M
        // https://wiki.swoole.com/wiki/page/301.html
        'package_max_length' => 8192000,

        // 长度的类型,参见 PHP 的 pack 函数
        // http://php.net/manual/zh/function.pack.php
        // https://wiki.swoole.com/wiki/page/463.html
        'package_length_type' => 'N',

        // 第 N 个字节是包长度的值
        // 如果未 0，表示整个包，包含包体和包头
        // https://wiki.swoole.com/wiki/page/287.html
        'package_length_offset' => 0,

        // 从第几个字节计算长度
        // https://wiki.swoole.com/wiki/page/287.html
        'package_body_offset' => 4,

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

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

    /**
     * Thrift 服务
     *
     * @var \Leevel\Protocol\Thrift\Base\ThriftServer
     */
    protected $thriftServer;

    /**
     * 构造函数.
     *
     * @param \Leevel\Di\IContainer $container
     * @param array                 $option
     */
    public function __construct(IContainer $container, array $option = [])
    {
        parent::__construct($container, $option);

        $this->thriftServer = $this->makeThriftServer();
    }

    /**                                                                                }
     * 监听数据发送事件.
     *
     * @param \Swoole\Server $server
     * @param int            $fd
     * @param int            $reactorId
     * @param string         $data
     *
     * @see https://wiki.swoole.com/wiki/page/50.html
     */
    public function onReceive(SwooleServer $server, int $fd, int $reactorId, string $data): void
    {
        parent::onReceive($server, $fd, $reactorId, $data);

        $this->thriftServer->receive(
            $server,
            $fd,
            $reactorId,
            $data
        );
    }

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

    /**
     * 创建 Thrift 服务
     *
     * @return \Leevel\Protocol\Thrift\Base\ThriftServer
     */
    protected function makeThriftServer(): ThriftServer
    {
        $service = new ThriftHandler();
        $processor = new ThriftProcessor($service);

        $socketTranport = new TServerSocket(
            $this->option['host'],
            (int) ($this->option['port'])
        );

        $outFactory = $inFactory = new TFramedTransportFactory();
        $outProtocol = $inProtocol = new TBinaryProtocolFactory();

        $server = new ThriftServer(
            $processor,
            $socketTranport,
            $inFactory,
            $outFactory,
            $inProtocol,
            $outProtocol
        );

        return $server;
    }
}