<?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 Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UploadedFileInterface;
use function Zend\Diactoros\normalizeServer;
use function Zend\Diactoros\normalizeUploadedFiles;
use Zend\Diactoros\Response as DiactorosResponse;
use Zend\Diactoros\ServerRequest;
use Zend\Diactoros\Stream as DiactorosStream;
use Zend\Diactoros\UploadedFile as DiactorosUploadedFile;

/**
 * Leevel 规范请求转 Psr
 * This class borrows heavily from the Symfony4 Framework and is part of the symfony package.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 * @author Kévin Dunglas <dunglas@gmail.com>
 *
 * @since 2019.03.11
 *
 * @version 1.0
 *
 * @see Symfony\Bridge\PsrHttpMessage (https://github.com/symfony/psr-http-message-bridge)
 */
class Leevel2Psr implements ILeevel2Psr
{
    /**
     * 从 Leevel 请求对象创建 Psr 请求对象
     *
     * @param \Leevel\Http\IRequest $leevelRequest
     *
     * @return \Psr\Http\Message\ServerRequestInterface
     */
    public function createRequest(IRequest $leevelRequest): ServerRequestInterface
    {
        $server = normalizeServer($leevelRequest->server->all());
        $headers = $leevelRequest->headers->all();

        $body = new DiactorosStream($this->normalizeContent($leevelRequest->getContent()));

        $request = new ServerRequest(
            $server,
            normalizeUploadedFiles($this->getFiles($leevelRequest->files->all())),
            $leevelRequest->getSchemeAndHttpHost().$leevelRequest->getRequestUri(),
            $leevelRequest->getMethod(),
            $body,
            $headers
        );

        $request = $request
            ->withCookieParams($leevelRequest->cookies->all())
            ->withQueryParams($leevelRequest->query->all())
            ->withParsedBody($leevelRequest->request->all())
            ->withRequestTarget($leevelRequest->getRequestUri());

        foreach ($leevelRequest->params->all() as $key => $value) {
            $request = $request->withAttribute($key, $value);
        }

        return $request;
    }

    /**
     * 从 Leevel 响应对象创建 Psr 响应对象
     *
     * @param \Leevel\Http\IResponse $leevelResponse
     *
     * @return \Psr\Http\Message\ResponseInterface
     */
    public function createResponse(Response $leevelResponse): ResponseInterface
    {
        $stream = new DiactorosStream('php://temp', 'wb+');
        $stream->write($leevelResponse->getContent());

        $headers = $leevelResponse->headers->all();

        if (!isset($headers['Set-Cookie']) &&
            !isset($headers['set-sookie']) &&
            $cookies = $leevelResponse->getCookies()) {
            foreach ($cookies as $item) {
                $headers['Set-Cookie'][] = Cookie::format($item);
            }
        }

        $response = new DiactorosResponse(
            $stream,
            $leevelResponse->getStatusCode(),
            $headers
        );

        $protocolVersion = $leevelResponse->getProtocolVersion();

        if ('1.1' !== $protocolVersion) {
            $response = $response->withProtocolVersion($protocolVersion);
        }

        return $response;
    }

    /**
     * 格式化响应内容.
     *
     * @param string $content
     *
     * @return resource
     */
    protected function normalizeContent(string $content)
    {
        $resource = fopen('php://temp', 'r+');
        fwrite($resource, $content);
        rewind($resource);

        return $resource;
    }

    /**
     * 转换上传文件结构.
     *
     * @param array $uploadedFiles
     *
     * @return array
     */
    protected function getFiles(array $uploadedFiles): array
    {
        $files = [];

        foreach ($uploadedFiles as $key => $value) {
            if (null === $value) {
                $files[$key] = new DiactorosUploadedFile(null, 0, UPLOAD_ERR_NO_FILE, null, null);

                continue;
            }

            if ($value instanceof UploadedFile) {
                $files[$key] = $this->createUploadedFile($value);
            } else {
                $files[$key] = $this->getFiles($value);
            }
        }

        return $files;
    }

    /**
     * 创建 PSR-7 上传对象.
     *
     * @param \Leevel\Http\UploadedFile $leevelUploadedFile
     *
     * @return \Psr\Http\Message\UploadedFileInterface
     */
    protected function createUploadedFile(UploadedFile $leevelUploadedFile): UploadedFileInterface
    {
        return new DiactorosUploadedFile(
            $leevelUploadedFile->getRealPath(),
            (int) $leevelUploadedFile->getSize(),
            $leevelUploadedFile->getError(),
            $leevelUploadedFile->getOriginalName(),
            $leevelUploadedFile->getMimeType()
        );
    }
}


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

use Leevel\Console\Command;
use Leevel\Filesystem\Fso\create_file;
use function Leevel\Filesystem\Fso\create_file;
use Leevel\Kernel\IApp;
use Leevel\Router\RouterProvider;

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

    /**
     * 命令行描述.
     *
     * @var string
     */
    protected $description = 'OpenApi as the router.';

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

        $data = $routerProvider->getRouters();

        $cachePath = $app->routerCachedPath();

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

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

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

        create_file($cachePath, $content);
    }

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

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

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


<?php

declare(strict_types=1);

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

namespace Leevel\Protocol\Process;

use function Leevel\Filesystem\Fso\list_directory;
use Leevel\Filesystem\Fso\list_directory;
use Leevel\Option\IOption;
use Leevel\Protocol\IServer;

/**
 * Swoole 热重载.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.12.13
 *
 * @version 1.0
 */
class HotOverload extends Process
{
    /**
     * 进程名字.
     *
     * @var string
     */
    protected $name = 'hot.overload';

    /**
     * 时间记录.
     *
     * @var int
     */
    protected $count = 0;

    /**
     * 文件 MD5 值.
     *
     * @var string
     */
    protected $md5;

    /**
     * 正在 reload.
     *
     * @var bool
     */
    protected $reloading = false;

    /**
     * 重启延迟时间.
     *
     * @var int
     */
    protected $after = 2;

    /**
     * 配置.
     *
     * @var \Leevel\Option\IOption
     */
    protected $option;

    /**
     * 构造函数.
     *
     * @param \Leevel\Option\IOption $option
     */
    public function __construct(IOption $option)
    {
        $this->option = $option;

        $this->after = (int) $this->option->get('swoole\\hotoverload_after');
    }

    /**
     * 响应句柄.
     *
     * @param \Leevel\Protocol\IServer $server
     */
    public function handle(IServer $server): void
    {
        while (true) {
            sleep(1);

            $newMd5 = $this->md5();
            $this->count++;

            if ($this->md5 && $newMd5 !== $this->md5) {
                $this->log('The Swoole server will reload.');

                $this->count = 0;
                $this->reloading = true;
            }

            if (true === $this->reloading && $this->count > $this->after) {
                $this->reload($server);
            }

            $this->md5 = $newMd5;
        }
    }

    /**
     * 当前文件 MD5 值
     *
     * @return string
     */
    protected function md5(): string
    {
        $md5File = [];

        foreach ($this->files() as $file) {
            $md5File[$file] = md5_file($file);
        }

        ksort($md5File);

        return md5(json_encode($md5File));
    }

    /**
     * 扫描文件.
     *
     * @return array
     */
    protected function files(): array
    {
        $files = [];

        foreach ((array) $this->option->get('swoole\\hotoverload_watch') as $dir) {
            if (!is_dir($dir)) {
                continue;
            }

            list_directory($dir, true, function ($file) use (&$files) {
                if ($file->isFile()) {
                    if (in_array($file->getExtension(), ['php'], true)) {
                        $files[] = $file->getPath().'/'.$file->getFilename();
                    }
                }
            });
        }

        return $files;
    }

    /**
     * 重启.
     *
     * @param \Leevel\Protocol\IServer $server
     */
    protected function reload(IServer $server): void
    {
        $this->reloading = false;

        $this->log('The Swoole server is start reloading.');

        $server->getServer()->reload();

        $this->log('The Swoole server has reloaded.');
    }

    /**
     * 记录日志.
     *
     * @param string $log
     */
    protected function log(string $log): void
    {
        fwrite(STDOUT, $log.PHP_EOL);
    }
}

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


<?php

declare(strict_types=1);

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

namespace Leevel\Cache;

use Closure;

/**
 * cache 仓储.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.02.15
 *
 * @version 1.0
 */
class Cache implements ICache
{
    /**
     * 缓存连接对象
     *
     * @var \Leevel\Cache\IConnect
     */
    protected $connect;

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

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

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

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

    /**
     * 缓存存在读取否则重新设置.
     *
     * @param string $name
     * @param mixed  $data
     * @param array  $option
     *
     * @return mixed
     */
    public function remember(string $name, $data, array $option = [])
    {
        if (false !== ($result = $this->connect->get($name, false, $option))) {
            return $result;
        }

        if (is_object($data) && $data instanceof Closure) {
            $data = $data($name);
        }

        $this->connect->set($name, $data, $option);

        return $data;
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Protocol\Console\Base;

use Leevel\Console\Command;
use Leevel\Console\Option;
use Leevel\Protocol\IServer;

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

        $server = $this->createServer();

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

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

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

    /**
     * 获取 Swoole 服务状态.
     *
     * @param array $option
     */
    protected function status(array $option): void
    {
        $processName = $option['process_name'];

        if ($this->option('all')) {
            $item = '$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11';

            $nikename = [
                // 进程的用户
                'USER',

                // 进程的 ID
                'PID',

                // 进程占用的 CPU 百分比
                '%CPU',

                // 占用内存的百分比
                '%MEM',

                // 该进程使用的虚拟内存量（KB）
                'VSZ(kb)',

                // 该进程占用的固定内存量（KB）
                'RSS(kb)',

                // 该进程在哪个终端上运行
                'TTY',

                // STAT 状态
                'STAT',

                // 该进程被触发启动时间
                'START',

                // 该进程实际使用CPU运行的时间
                'TIME',

                // 命令的名称和参数
                'COMMAND',
            ];
        } else {
            $item = '$1, $2, $3, $4, $8, $9, $10, $11';

            $nikename = [
                // 进程的用户
                'USER',

                // 进程的 ID
                'PID',

                // 进程占用的 CPU 百分比
                '%CPU',

                // 占用内存的百分比
                '%MEM',

                // STAT 状态
                'STAT',

                // 该进程被触发启动时间
                'START',

                // 该进程实际使用CPU运行的时间
                'TIME',

                // 命令的名称和参数
                'COMMAND',
            ];
        }

        $cmd = 'ps aux|grep '.$processName."|grep -v grep|awk '{print ".$item."}'";

        exec($cmd, $out);

        $this->info($cmd, true);

        if (empty($out)) {
            $this->warn('No swoole service process was found', true);

            return;
        }

        foreach ($out as &$v) {
            $v = explode(' ', $v);
        }

        $this->table($nikename, $out);
    }

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

    /**
     * 命令配置.
     *
     * @return array
     */
    protected function getOptions(): array
    {
        return [
            [
                'all',
                null,
                Option::VALUE_OPTIONAL,
                'Show all item of process.',
            ],
        ];
    }
}


<?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\RedirectResponse;
use Leevel\Session\ISession;

/**
 * Redirect.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.03.02
 *
 * @version 1.0
 */
class Redirect
{
    /**
     * URL 生成实例.
     *
     * @var \Leevel\Router\Url
     */
    protected $url;

    /**
     * SESSION 仓储.
     *
     * @var \Leevel\Session\ISession
     */
    protected $session;

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

    /**
     * 返回一个 URL 生成跳转响应.
     *
     * @param string      $url
     * @param array       $params
     * @param string      $subdomain
     * @param bool|string $suffix
     * @param int         $status
     * @param array       $headers
     *
     * @return \Leevel\Http\RedirectResponse
     */
    public function url(string $url, array $params = [], string $subdomain = 'www', $suffix = null, int $status = 302, array $headers = []): RedirectResponse
    {
        $url = $this->url->make($url, $params, $subdomain, $suffix);

        return $this->createRedirectResponse($url, $status, $headers);
    }

    /**
     * 返回一个跳转响应.
     *
     * @param string $url
     * @param int    $status
     * @param array  $headers
     *
     * @return \Leevel\Http\RedirectResponse
     */
    public function raw(string $url, int $status = 302, array $headers = []): RedirectResponse
    {
        return $this->createRedirectResponse($url, $status, $headers);
    }

    /**
     * 取回 URL 生成实例.
     *
     * @return \Leevel\Router\IUrl
     */
    public function getUrl(): IUrl
    {
        return $this->url;
    }

    /**
     * 设置 SESSION 仓储.
     *
     * @param \Leevel\Session\ISession $session
     */
    public function setSession(ISession $session): void
    {
        $this->session = $session;
    }

    /**
     * 返回一个跳转响应.
     *
     * @param string $url
     * @param int    $status
     * @param array  $headers
     *
     * @return \Leevel\Http\RedirectResponse
     */
    protected function createRedirectResponse(string $url, int $status = 302, array $headers = []): RedirectResponse
    {
        $redirect = new RedirectResponse($url, $status, $headers);

        if (isset($this->session)) {
            $redirect->setSession($this->session);
        }

        $redirect->setRequest($this->url->getRequest());

        return $redirect;
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Auth\Middleware;

use Closure;
use Leevel\Auth\AuthException;
use Leevel\Auth\Manager;
use Leevel\Http\IRequest;

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

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

    /**
     * 请求
     *
     * @param \Closure              $next
     * @param \Leevel\Http\IRequest $request
     */
    public function handle(Closure $next, IRequest $request): void
    {
        if (!$this->manager->isLogin()) {
            throw new AuthException('User authorization failed.');
        }

        $next($request);
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Auth;

/**
 * Hash.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.11.18
 *
 * @version 1.0
 */
class Hash implements IHash
{
    /**
     * 加密算法.
     *
     * @var string
     */
    const ALGO = ':algo';

    /**
     * 生成密码.
     *
     * @param string $password
     * @param array  $option
     *
     * @return string
     */
    public function password(string $password, array $option = []): string
    {
        $algo = $option[self::ALGO] ?? PASSWORD_BCRYPT;

        return password_hash($password, (int) $algo, $option);
    }

    /**
     * 校验密码.
     *
     * @param string $password
     * @param string $hash
     *
     * @return bool
     */
    public function verify(string $password, string $hash): bool
    {
        return password_verify($password, $hash);
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Debug\Middleware;

use Closure;
use Leevel\Debug\Debug as Debugs;
use Leevel\Http\IRequest;
use Leevel\Http\IResponse;
use Leevel\Kernel\IApp;

/**
 * Debug 中间件.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.09.20
 *
 * @version 1.0
 */
class Debug
{
    /**
     * 应用管理.
     *
     * @var \Leevel\Kernel\IApp
     */
    protected $app;

    /**
     * debug 管理.
     *
     * @var \Leevel\Debug\Debug
     */
    protected $debug;

    /**
     * 构造函数.
     *
     * @param \Leevel\Kernel\IApp $app
     * @param \Leevel\Debug\Debug $debug
     */
    public function __construct(IApp $app, Debugs $debug)
    {
        $this->app = $app;
        $this->debug = $debug;
    }

    /**
     * 请求.
     *
     * @param \Closure              $next
     * @param \Leevel\Http\IRequest $request
     */
    public function handle(Closure $next, IRequest $request): void
    {
        if (!$this->app->debug()) {
            $next($request);

            return;
        }

        $this->debug->bootstrap();

        $next($request);
    }

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

            return;
        }

        $this->debug->handle($request, $response);

        $next($request, $response);
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Kernel\Console;

use Leevel\Console\Command;
use Leevel\Console\Option;

/**
 * 生产环境性能一键优化.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.09.02
 *
 * @version 1.0
 * @codeCoverageIgnore
 */
class Production extends Command
{
    /**
     * 命令名字.
     *
     * @var string
     */
    protected $name = 'production';

    /**
     * 命令行描述.
     *
     * @var string
     */
    protected $description = 'Let your app run faster in production.';

    /**
     * 响应命令.
     */
    public function handle(): void
    {
        $this->line('Start to optimize you app.');

        $this->callRouter();

        $this->callOption();

        $this->callI18n();

        $this->callView();

        $this->callAutoload();

        $this->line('');
        $this->info('Optimize successed.');
    }

    /**
     * 执行路由缓存.
     */
    protected function callRouter(): void
    {
        $this->line('');
        $this->call('router:cache');
    }

    /**
     * 执行配置缓存.
     */
    protected function callOption(): void
    {
        $this->line('');
        $this->call('option:cache');
    }

    /**
     * 执行语言缓存.
     */
    protected function callI18n(): void
    {
        $this->line('');
        $this->call('i18n:cache');
    }

    /**
     * 执行视图缓存.
     */
    protected function callView(): void
    {
        $this->line('');
        $this->call('view:cache');
    }

    /**
     * 执行自动载入缓存.
     */
    protected function callAutoload(): void
    {
        $this->line('');
        $this->call('autoload', [
            '--composer' => $this->composer(),
        ]);
    }

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

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

    /**
     * 命令配置.
     *
     * @return array
     */
    protected function getOptions(): array
    {
        return [
            [
                'composer',
                null,
                Option::VALUE_OPTIONAL,
                'Where is composer.',
                'composer',
            ],
        ];
    }
}


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

use Leevel\Di\IContainer;
use Leevel\Di\Provider;
use Leevel\Router\Redirect;
use Leevel\Router\ResponseFactory;
use Leevel\Router\Router;
use Leevel\Router\Url;
use Leevel\Router\View;

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

    /**
     * 可用服务提供者.
     *
     * @return array
     */
    public static function providers(): array
    {
        return [
            'router' => [
                'Leevel\\Router\\Router',
                'Leevel\\Router\\IRouter',
            ],
            'url' => [
                'Leevel\\Router\\Url',
            ],
            'redirect' => [
                'Leevel\\Router\\Redirect',
            ],
            'response' => [
                'Leevel\\Router\\IResponseFactory',
                'Leevel\\Router\\ResponseFactory',
            ],
            'view' => [
                'Leevel\\Router\\View',
                'Leevel\\Router\\IView',
            ],
        ];
    }

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

    /**
     * 注册 url 服务
     */
    protected function url(): void
    {
        $this->container->singleton('url', function (IContainer $container) {
            $option = $container['option'];
            $router = $container['router'];

            $options = [];

            foreach ([
                'with_suffix',
                'suffix',
                'domain',
            ] as $item) {
                $options[$item] = $option->get($item);
            }

            return new Url($container['request'], $options);
        });
    }

    /**
     * 注册 redirect 服务
     */
    protected function redirect(): void
    {
        $this->container->singleton('redirect', function (IContainer $container) {
            $redirect = new Redirect($container['url']);

            if (isset($container['session'])) {
                $redirect->setSession($container['session']);
            }

            return $redirect;
        });
    }

    /**
     * 注册 response 服务
     */
    protected function response(): void
    {
        $this->container->singleton('response', function (IContainer $container) {
            $option = $container['option'];

            return (new ResponseFactory($container['view'], $container['redirect']))->
            setViewSuccessTemplate($option->get('view\\success'))->

            setViewFailTemplate($option->get('view\\fail'));
        });
    }

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


<?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 ErrorException;
use Exception;
use Leevel\Http\IRequest;
use Leevel\Http\IResponse;
use Leevel\Kernel\Bootstrap\LoadI18n;
use Leevel\Kernel\Bootstrap\LoadOption;
use Leevel\Kernel\Bootstrap\RegisterRuntime;
use Leevel\Kernel\Bootstrap\TraverseProvider;
use Leevel\Router\IRouter;
use Throwable;

/**
 * 内核执行.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.18
 *
 * @version 1.0
 */
abstract class Kernel implements IKernel
{
    /**
     * 应用.
     *
     * @var \Leevel\Kernel\IApp
     */
    protected $app;

    /**
     * 路由.
     *
     * @var \Leevel\Router\IRouter
     */
    protected $router;

    /**
     * 应用初始化执行.
     *
     * @var array
     */
    protected $bootstraps = [
        LoadOption::class,
        LoadI18n::class,
        RegisterRuntime::class,
        TraverseProvider::class,
    ];

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

    /**
     * 响应 HTTP 请求
     *
     * @param \Leevel\Http\IRequest $request
     *
     * @return \Leevel\Http\IResponse
     */
    public function handle(IRequest $request): IResponse
    {
        try {
            $this->registerBaseService($request);

            $this->bootstrap();

            $response = $this->getResponseWithRequest($request);

            $this->middlewareTerminate($request, $response);
        } catch (Exception $e) {
            $this->reportException($e);

            $response = $this->renderException($request, $e);
        } catch (Throwable $e) {
            $e = new ErrorException(
                $e->getMessage(),
                $e->getCode(),
                E_ERROR,
                $e->getFile(),
                $e->getLine(),
                $e->getPrevious()
            );

            $this->reportException($e);

            $response = $this->renderException($request, $e);
        }

        return $response;
    }

    /**
     * 执行结束
     *
     * @param \Leevel\Http\IRequest  $request
     * @param \Leevel\Http\IResponse $response
     * @codeCoverageIgnore
     */
    public function terminate(IRequest $request, IResponse $response): void
    {
    }

    /**
     * 初始化.
     *
     * @codeCoverageIgnore
     */
    public function bootstrap(): void
    {
        $this->app->bootstrap($this->bootstraps);
    }

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

    /**
     * 返回运行处理器.
     *
     * @return \Leevel\Kernel\Runtime\IRuntime
     */
    protected function getRuntime(): IRuntime
    {
        return $this->app->make(IRuntime::class);
    }

    /**
     * 注册基础服务
     *
     * @param \Leevel\Http\IRequest $request
     */
    protected function registerBaseService(IRequest $request): void
    {
        $this->app->instance('request', $request);
    }

    /**
     * 根据请求返回响应.
     *
     * @param \Leevel\Http\IRequest $request
     *
     * @return \Leevel\Http\IResponse
     */
    protected function getResponseWithRequest(IRequest $request): IResponse
    {
        return $this->dispatchRouter($request);
    }

    /**
     * 路由调度.
     *
     * @param \Leevel\Http\IRequest $request
     *
     * @return \Leevel\Http\IResponse
     */
    protected function dispatchRouter(IRequest $request): IResponse
    {
        return $this->router->dispatch($request);
    }

    /**
     * 上报错误.
     *
     * @param \Exception $e
     */
    protected function reportException(Exception $e): void
    {
        $this->getRuntime()->report($e);
    }

    /**
     * 渲染异常.
     *
     * @param \Leevel\Http\IRequest $request
     * @param \Exception            $e
     *
     * @return \Leevel\Http\IResponse
     */
    protected function renderException(IRequest $request, Exception $e): IResponse
    {
        return $this->getRuntime()->render($request, $e);
    }

    /**
     * 中间件结束响应.
     *
     * @param \Leevel\Http\IRequest  $request
     * @param \Leevel\Http\IResponse $response
     */
    protected function middlewareTerminate(IRequest $request, IResponse $response): void
    {
        $this->router->throughMiddleware($request, [
            $response,
        ]);
    }
}


<?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 Leevel\Event\IDispatch;
use Monolog\Logger;

/**
 * 日志仓储.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.03.03
 *
 * @version 1.0
 */
class Log implements ILog
{
    /**
     * 存储连接对象
     *
     * @var \Leevel\Log\IConnect
     */
    protected $connect;

    /**
     * 当前记录的日志信息.
     *
     * @var array
     */
    protected $logs = [];

    /**
     * 日志数量.
     *
     * @var int
     */
    protected $count = 0;

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

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'levels'   => [
            self::DEBUG,
            self::INFO,
            self::NOTICE,
            self::WARNING,
            self::ERROR,
            self::CRITICAL,
            self::ALERT,
            self::EMERGENCY,
        ],
        'buffer'      => true,
        'buffer_size' => 100,
    ];

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

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

        $this->dispatch = $dispatch;
    }

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

        return $this;
    }

    /**
     * 系统无法使用.
     *
     * @param string $message
     * @param array  $context
     */
    public function emergency(string $message, array $context = []): void
    {
        $this->log(static::EMERGENCY, $message, $context);
    }

    /**
     * 必须立即采取行动.
     *
     * 比如: 整个网站宕机，数据库不可用等等.
     * 这种错误应该通过短信通知你.
     *
     * @param string $message
     * @param array  $context
     */
    public function alert(string $message, array $context = []): void
    {
        $this->log(static::ALERT, $message, $context);
    }

    /**
     * 临界条件.
     *
     * 比如: 应用程序组件不可用，意外异常.
     *
     * @param string $message
     * @param array  $context
     */
    public function critical(string $message, array $context = []): void
    {
        $this->log(static::CRITICAL, $message, $context);
    }

    /**
     * 运行时错误，不需要立即处理.
     * 但是需要被记录和监控.
     *
     * @param string $message
     * @param array  $context
     */
    public function error(string $message, array $context = []): void
    {
        $this->log(static::ERROR, $message, $context);
    }

    /**
     * 非错误的异常事件.
     *
     * 比如: 弃用的 API 接口, API 使用不足, 不良事物.
     * 它们不一定是错误的.
     *
     * @param string $message
     * @param array  $context
     */
    public function warning(string $message, array $context = []): void
    {
        $this->log(static::WARNING, $message, $context);
    }

    /**
     * 正常重要事件.
     *
     * @param string $message
     * @param array  $context
     */
    public function notice(string $message, array $context = []): void
    {
        $this->log(static::NOTICE, $message, $context);
    }

    /**
     * 想记录的日志.
     *
     * 比如: 用户日志, SQL 日志.
     *
     * @param string $message
     * @param array  $context
     */
    public function info(string $message, array $context = []): void
    {
        $this->log(static::INFO, $message, $context);
    }

    /**
     * 调试信息.
     *
     * @param string $message
     * @param array  $context
     */
    public function debug(string $message, array $context = []): void
    {
        $this->log(static::DEBUG, $message, $context);
    }

    /**
     * 记录特定级别的日志信息.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     */
    public function log(string $level, string $message, array $context = []): void
    {
        if (!in_array($level, $this->option['levels'], true)) {
            return;
        }

        $data = [$level, $message, $context];

        $this->handleDispatch($data);

        $this->count++;
        $this->logs[$level][] = $data;

        if (false === $this->option['buffer'] ||
            ($this->option['buffer_size'] && $this->count >= $this->option['buffer_size'])) {
            $this->flush();
        }
    }

    /**
     * 保存日志信息.
     */
    public function flush(): void
    {
        foreach ($this->logs as $data) {
            $this->saveStore($data);
        }

        $this->clear();
    }

    /**
     * 清理日志记录.
     *
     * @param string $level
     */
    public function clear(?string $level = null): void
    {
        if (null === $level) {
            $this->count = 0;
            $this->logs = [];
        }

        if (isset($this->logs[$level])) {
            $this->count -= count($this->logs[$level]);
            $this->logs[$level] = [];
        }
    }

    /**
     * 获取日志记录.
     *
     * @param string $level
     *
     * @return array
     */
    public function all(?string $level = null): array
    {
        if (null === $level) {
            return $this->logs;
        }

        if (isset($this->logs[$level])) {
            return $this->logs[$level];
        }

        return [];
    }

    /**
     * 获取日志记录数量.
     *
     * @param string $level
     *
     * @return int
     */
    public function count(?string $level = null): int
    {
        if (null === $level) {
            return $this->count;
        }

        return count($this->all($level));
    }

    /**
     * 是否为 Monolog.
     *
     * @return bool
     */
    public function isMonolog(): bool
    {
        return method_exists($this->connect, 'getMonolog');
    }

    /**
     * 取得 Monolog.
     *
     * @return null|\Monolog\Logger
     */
    public function getMonolog(): ?Logger
    {
        if (!$this->isMonolog()) {
            return null;
        }

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

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

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

    /**
     * 存储日志.
     *
     * @param array $data
     */
    protected function saveStore(array $data): void
    {
        $this->connect->flush($data);
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Event;

use Leevel\Di\Provider;

/**
 * 事件服务提供者.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.04.26
 *
 * @version 1.0
 */
abstract class EventProvider extends Provider
{
    /**
     * 监听器列表.
     *
     * @var array
     */
    protected $listeners = [];

    /**
     * 注册事件监听器.
     *
     * @param \Leevel\Event\IDispatch $dispatch
     */
    public function bootstrap(IDispatch $dispatch): void
    {
        foreach ($this->get() as $event => $listeners) {
            foreach ($listeners as $key => $item) {
                if (is_int($item)) {
                    $dispatch->register($event, $key, $item);
                } else {
                    $dispatch->register($event, $item);
                }
            }
        }
    }

    /**
     * 注册一个提供者.
     */
    public function register(): void
    {
    }

    /**
     * 取得监听器.
     *
     * @return array
     */
    public function get(): array
    {
        return $this->listeners;
    }
}


<?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\Di\IContainer;

/**
 * cache 快捷载入.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.11.20
 *
 * @version 1.0
 */
class Load
{
    /**
     * IOC Container.
     *
     * @var \Leevel\Di\IContainer
     */
    protected $container;

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

    /**
     * 已经载入的缓存数据.
     *
     * @var array
     */
    protected $cacheLoaded = [];

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

    /**
     * 切换缓存仓储.
     *
     * @param \Leevel\Cache\ICache $cache
     */
    public function switchCache(ICache $cache): void
    {
        $this->cache = $cache;
    }

    /**
     * 返回缓存仓储.
     *
     * @return \Leevel\Cache\ICache
     */
    public function getCache(): ICache
    {
        return $this->cache;
    }

    /**
     * 载入缓存数据
     * 系统自动存储缓存到内存，可重复执行不会重复载入数据.
     *
     * @param array|string $names
     * @param array        $option
     * @param bool         $force
     *
     * @return array
     */
    public function data($names, array $option = [], bool $force = false): array
    {
        $names = is_array($names) ? $names : [
            $names,
        ];

        foreach ($names as $name) {
            if (!isset($this->cacheLoaded[$name]) || $force) {
                $this->cacheLoaded[$name] = $this->cache($name, $option, $force);
            }
        }

        $result = [];

        foreach ($names as $name) {
            $result[$name] = $this->cacheLoaded[$name];
        }

        return count($result) > 1 ? $result : reset($result);
    }

    /**
     * 刷新缓存数据.
     *
     * @param array|string $names
     */
    public function refresh($names): void
    {
        $tmp = is_array($names) ? $names : [
            $names,
        ];

        foreach ($tmp as $name) {
            $this->delete($name);
        }
    }

    /**
     * 从载入缓存数据中获取
     * 不存在不用更新缓存，返回 false.
     *
     * @param array|string $names
     * @param mixed        $force
     *
     * @return mixed
     */
    public function dataLoaded($names, array $option = [], bool $force = false)
    {
        $result = [];

        $names = is_array($names) ? $names : [
            $names,
        ];

        foreach ($names as $name) {
            $result[$name] = array_key_exists($name, $this->cacheLoaded) ?
                $this->cacheLoaded[$name] :
                false;
        }

        return count($result) > 1 ? $result : reset($result);
    }

    /**
     * 删除缓存数据.
     *
     * @param string $name
     */
    protected function delete(string $name): void
    {
        $this->deletePersistence($name);
    }

    /**
     * 读取缓存数据.
     *
     * @param string $name
     * @param array  $option
     * @param bool   $force
     *
     * @return mixed
     */
    protected function cache(string $name, array $option = [], bool $force = false)
    {
        if (false === $force) {
            $data = $this->getPersistence($name, false, $option);
        } else {
            $data = false;
        }

        if (false === $data) {
            $data = $this->update($name, $option);
        }

        return $data;
    }

    /**
     * 更新缓存数据.
     *
     * @param string $name
     * @param array  $option
     *
     * @return mixed
     */
    protected function update(string $name, array $option = [])
    {
        $sourceName = $name;

        list($name, $params) = $this->parse($name);

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

        if (!is_object($cache = $this->container->make($name))) {
            throw new InvalidArgumentException(sprintf('Cache %s is not valid.', $name));
        }

        if (!is_callable([$cache, $method])) {
            throw new InvalidArgumentException(
                sprintf('Cache %s is not a callable.', $name.'@'.$method)
            );
        }

        $sourceData = $cache->{$method}(...$params);

        $this->setPersistence($sourceName, $sourceData, $option);

        return $sourceData;
    }

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

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

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

    /**
     * 解析缓存.
     *
     * @param string $name
     *
     * @return array
     */
    protected function parse(string $name): array
    {
        list($name, $args) = array_pad(explode(':', $name, 2), 2, []);

        if (is_string($args)) {
            $args = explode(',', $args);
        }

        $args = array_map(function (string $item) {
            return ctype_digit($item) ? (int) $item : $item;
        }, $args);

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


<?php

declare(strict_types=1);

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

namespace Leevel\Database\Ddd\Relation;

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

/**
 * 关联模型实体基类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.09.28
 *
 * @version 1.0
 */
abstract class Relation
{
    /**
     * 查询对象
     *
     * @var \Leevel\Database\Ddd\Select
     */
    protected $select;

    /**
     * 关联目标模型实体.
     *
     * @var \Leevel\Database\Ddd\IEntity
     */
    protected $targetEntity;

    /**
     * 源模型实体.
     *
     * @var \Leevel\Database\Ddd\IEntity
     */
    protected $sourceEntity;

    /**
     * 目标关联字段.
     *
     * @var string
     */
    protected $targetKey;

    /**
     * 源关联字段.
     *
     * @var string
     */
    protected $sourceKey;

    /**
     * 是否初始化查询.
     *
     * @var bool
     */
    protected static $relationCondition = true;

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

        $this->getSelectFromEntity();
        $this->addRelationCondition();
    }

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

        if ($this->getSelect() === $select) {
            return $this;
        }

        return $select;
    }

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

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

    /**
     * 取得关联目标模型实体.
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    public function getTargetEntity(): IEntity
    {
        return $this->targetEntity;
    }

    /**
     * 取得源模型实体.
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    public function getSourceEntity(): IEntity
    {
        return $this->sourceEntity;
    }

    /**
     * 取得目标字段.
     *
     * @return string
     */
    public function getTargetKey(): string
    {
        return $this->targetKey;
    }

    /**
     * 取得源字段.
     *
     * @return string
     */
    public function getSourceKey(): string
    {
        return $this->sourceKey;
    }

    /**
     * 获取不带关联条件的关联对象.
     *
     * @param \Closure $returnRelation
     *
     * @return \Leevel\Database\Ddd\Relation\Relation
     */
    public static function withoutRelationCondition(Closure $returnRelation): self
    {
        $old = static::$relationCondition;
        static::$relationCondition = false;

        $relation = call_user_func($returnRelation);

        static::$relationCondition = $old;

        return $relation;
    }

    /**
     * 关联基础查询条件.
     */
    abstract public function addRelationCondition(): void;

    /**
     * 设置预载入关联查询条件.
     *
     * @param \Leevel\Database\Ddd\IEntity[] $entitys
     */
    abstract public function preLoadCondition(array $entitys): void;

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

    /**
     * 查询关联对象
     *
     * @return mixed
     */
    abstract public function sourceQuery();

    /**
     * 返回模型实体的主键.
     *
     * @param \Leevel\Database\Ddd\IEntity[] $entitys
     * @param string                         $key
     *
     * @return array
     */
    protected function getEntityKey(array $entitys, ?string $key = null): array
    {
        return array_unique(
            array_values(
                array_map(function ($entity) use ($key) {
                    return $key ?
                        $entity->__get($key) :
                        $entity->singleId();
                }, $entitys)
            )
        );
    }

    /**
     * 从模型实体返回查询.
     */
    protected function getSelectFromEntity(): void
    {
        $this->select = $this->targetEntity->select();
    }
}


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

use Leevel\Di\IContainer;
use Leevel\Di\Provider;
use Leevel\Encryption\Encryption;

/**
 * encryption 服务提供者.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.06.03
 *
 * @version 1.0
 */
class Register extends Provider
{
    /**
     * 注册服务.
     */
    public function register(): void
    {
        $this->container->singleton('encryption', function (IContainer $container) {
            $option = $container['option'];

            return new Encryption(
                $option['auth_key'], $option['auth_cipher'],
                $option['auth_rsa_private'], $option['auth_rsa_public']
            );
        });
    }

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

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


<?php

declare(strict_types=1);

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

namespace Leevel\Http;

use ArrayObject;
use DateTime;
use DateTimeZone;
use InvalidArgumentException;
use JsonSerializable;
use Leevel\Flow\FlowControl;
use Leevel\Support\IArray;
use Leevel\Support\IJson;
use UnexpectedValueException;

/**
 * HTTP 响应
 * This class borrows heavily from the Symfony4 Framework and is part of the symfony package.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.04.18
 *
 * @version 1.0
 *
 * @see Symfony\Component\HttpFoundation (https://github.com/symfony/symfony)
 */
class Response implements IResponse
{
    use FlowControl;

    /**
     * 响应头.
     *
     * @var \Leevel\Http\ResponseHeaderBag
     */
    public $headers;

    /**
     * 原生响应内容.
     *
     * @var mixed
     */
    public $original;

    /**
     * 状态码
     *
     * @see http://www.iana.org/assignments/http-status-codes/
     *
     * @var array
     */
    public static $statusTexts = [
        100 => 'Continue',
        101 => 'Switching Protocols',
        102 => 'Processing', // RFC2518
        103 => 'Early Hints',
        200 => 'OK',
        201 => 'Created',
        202 => 'Accepted',
        203 => 'Non-Authoritative Information',
        204 => 'No Content',
        205 => 'Reset Content',
        206 => 'Partial Content',
        207 => 'Multi-Status', // RFC4918
        208 => 'Already Reported', // RFC5842
        226 => 'IM Used', // RFC3229
        300 => 'Multiple Choices',
        301 => 'Moved Permanently',
        302 => 'Found',
        303 => 'See Other',
        304 => 'Not Modified',
        305 => 'Use Proxy',
        307 => 'Temporary Redirect',
        308 => 'Permanent Redirect', // RFC7238
        400 => 'Bad Request',
        401 => 'Unauthorized',
        402 => 'Payment Required',
        403 => 'Forbidden',
        404 => 'Not Found',
        405 => 'Method Not Allowed',
        406 => 'Not Acceptable',
        407 => 'Proxy Authentication Required',
        408 => 'Request Timeout',
        409 => 'Conflict',
        410 => 'Gone',
        411 => 'Length Required',
        412 => 'Precondition Failed',
        413 => 'Payload Too Large',
        414 => 'URI Too Long',
        415 => 'Unsupported Media Type',
        416 => 'Range Not Satisfiable',
        417 => 'Expectation Failed',
        418 => 'I\'m a teapot', // RFC2324
        421 => 'Misdirected Request', // RFC7540
        422 => 'Unprocessable Entity', // RFC4918
        423 => 'Locked', // RFC4918
        424 => 'Failed Dependency', // RFC4918
        425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817
        426 => 'Upgrade Required', // RFC2817
        428 => 'Precondition Required', // RFC6585
        429 => 'Too Many Requests', // RFC6585
        431 => 'Request Header Fields Too Large', // RFC6585
        451 => 'Unavailable For Legal Reasons', // RFC7725
        500 => 'Internal Server Error',
        501 => 'Not Implemented',
        502 => 'Bad Gateway',
        503 => 'Service Unavailable',
        504 => 'Gateway Timeout',
        505 => 'HTTP Version Not Supported',
        506 => 'Variant Also Negotiates', // RFC2295
        507 => 'Insufficient Storage', // RFC4918
        508 => 'Loop Detected', // RFC5842
        510 => 'Not Extended', // RFC2774
        511 => 'Network Authentication Required', // RFC6585
    ];

    /**
     * 响应内容.
     *
     * @var string
     */
    protected $content;

    /**
     * HTTP 协议版本.
     *
     * @var string
     */
    protected $protocolVersion;

    /**
     * 状态码
     *
     * @var int
     */
    protected $statusCode;

    /**
     * 状态码内容.
     *
     * @var string
     */
    protected $statusText;

    /**
     * 字符编码
     *
     * @var string
     */
    protected $charset;

    /**
     * 是否为 JSON.
     *
     * @var bool
     */
    protected $isJson = false;

    /**
     * 构造函数.
     *
     * @param string $content
     * @param int    $status
     * @param array  $headers
     */
    public function __construct($content = '', int $status = 200, array $headers = [])
    {
        $this->headers = new ResponseHeaderBag($headers);
        $this->setContent($content);
        $this->setStatusCode($status);
        $this->setProtocolVersion('1.0');
    }

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

    /**
     * 发送 HTTP 响应.
     *
     * @return $this
     */
    public function send(): IResponse
    {
        $this->sendHeaders();

        $this->sendContent();

        return $this;
    }

    /**
     * 发送响应头.
     *
     * @return $this
     */
    public function sendHeaders(): IResponse
    {
        if (headers_sent()) {
            return $this;
        }

        // @codeCoverageIgnoreStart
        foreach ($this->headers->all() as $name => $value) {
            header($name.': '.$value, false, $this->statusCode);
        }

        // 状态码
        header(sprintf('HTTP/%s %s %s', $this->protocolVersion, $this->statusCode, $this->statusText), true, $this->statusCode);

        // COOKIE
        foreach ($this->getCookies() as $item) {
            call_user_func_array('setcookie', $item);
        }

        return $this;
        // @codeCoverageIgnoreEnd
    }

    /**
     * 发送响应内容.
     *
     * @return $this
     */
    public function sendContent(): IResponse
    {
        echo $this->content;

        return $this;
    }

    /**
     * 设置内容.
     *
     * @param mixed $content
     *
     * @return $this
     */
    public function setContent($content): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->original = $content;

        if ($this->contentShouldJson($content)) {
            $this->setHeader('Content-Type', 'application/json');

            $this->isJson = true;

            $content = $this->contentToJson($content);
        }

        if (null !== $content &&
            !is_scalar($content) &&
            !is_callable([$content, '__toString'])) {
            $e = sprintf('The Response content must be a scalar or object implementing __toString(), %s given.', gettype($content));

            throw new UnexpectedValueException($e);
        }

        $this->content = (string) $content;

        return $this;
    }

    /**
     * 附加内容.
     *
     * @param string $content
     *
     * @return $this
     */
    public function appendContent(string $content = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->content = $this->getContent().$content;

        return $this;
    }

    /**
     * 设置响应头.
     *
     * @param string $key
     * @param string $value
     * @param bool   $replace
     *
     * @return $this
     */
    public function setHeader(string $key, string $value, bool $replace = true): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        if (true === $replace || !$this->headers->has($key)) {
            $this->headers->set($key, $value);
        }

        return $this;
    }

    /**
     * 批量设置响应头.
     *
     * @param array $headers
     *
     * @return $this
     */
    public function withHeaders(array $headers): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        foreach ($headers as $key => $value) {
            $this->headers->set($key, $value);
        }

        return $this;
    }

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

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

        $this->headers->setCookie($name, $value, $option);

        return $this;
    }

    /**
     * 批量设置 COOKIE.
     *
     * @param array $cookies
     * @param array $option
     *
     * @return $this
     */
    public function withCookies(array $cookies, array $option = []): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        foreach ($cookies as $key => $value) {
            $this->setCookie($key, $value, $option);
        }

        return $this;
    }

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

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

        return $this->content;
    }

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

        $this->original = $data;

        if (null === $encodingOptions) {
            $encodingOptions = 256;
        }

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

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

        $this->content = (string) $data;

        return $this;
    }

    /**
     * 获取内容.
     *
     * @return mixed
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * 获取内容.
     *
     * @return mixed
     */
    public function content()
    {
        return $this->getContent();
    }

    /**
     * 获取原始内容.
     *
     * @return mixed
     */
    public function getOriginal()
    {
        return $this->original;
    }

    /**
     * 设置 HTTP 协议版本 (1.0 or 1.1).
     *
     * @param string $protocolVersion
     *
     * @return $this
     */
    public function setProtocolVersion(string $protocolVersion): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->protocolVersion = $protocolVersion;

        return $this;
    }

    /**
     * 获取 HTTP 协议版本.
     */
    public function getProtocolVersion(): string
    {
        return $this->protocolVersion;
    }

    /**
     * 设置相应状态码.
     *
     * @param int         $code
     * @param null|string $text
     *
     * @return $this
     */
    public function setStatusCode(int $code, ?string $text = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->statusCode = $code;

        if ($this->isInvalid()) {
            $e = sprintf('The HTTP status code %s is not valid.', $code);

            throw new InvalidArgumentException($e);
        }

        if (null === $text) {
            $this->statusText = self::$statusTexts[$code] ?? 'unknown status';

            return $this;
        }

        $this->statusText = $text;

        return $this;
    }

    /**
     * 获取状态码.
     *
     * @return int
     */
    public function status(): int
    {
        return $this->getStatusCode();
    }

    /**
     * 获取状态码.
     *
     * @return int
     */
    public function getStatusCode(): int
    {
        return $this->statusCode;
    }

    /**
     * 编码设置.
     *
     * @param string $charset
     *
     * @return $this
     */
    public function setCharset(string $charset): IResponse
    {
        return $this->charset($charset);
    }

    /**
     * 编码设置.
     *
     * @param string $charset
     *
     * @return $this
     */
    public function charset(string $charset): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->charset = $charset;

        return $this;
    }

    /**
     * 获取编码.
     *
     * @return string
     */
    public function getCharset(): ?string
    {
        return $this->charset;
    }

    /**
     * 设置过期时间.
     *
     * @param \DateTime $datetime
     *
     * @return $this
     */
    public function setExpires(DateTime $datetime = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        if (null === $datetime) {
            $this->headers->remove('Expires');

            return $this;
        }

        $this->setHeader('Expires', $this->normalizeDateTime($datetime));

        return $this;
    }

    /**
     * 设置最后修改时间.
     *
     * @param \DateTime $datetime
     *
     * @return $this
     */
    public function setLastModified(DateTime $datetime = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        if (null === $datetime) {
            $this->headers->remove('Last-Modified');

            return $this;
        }

        $this->setHeader('Last-Modified', $this->normalizeDateTime($datetime));

        return $this;
    }

    /**
     * 设置缓存.
     *
     * @param int $minutes
     *
     * @return $this
     */
    public function setCache(int $minutes): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $date = new DateTime();
        $date->modify('+'.$minutes.'minutes');

        $this->setExpires($date);
        $this->setHeader('Cache-Control', 'max-age='.($minutes * 60));

        return $this;
    }

    /**
     * 设置响应未修改.
     *
     * @return $this
     */
    public function setNotModified(): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->setStatusCode(304, self::$statusTexts[304]);

        return $this;
    }

    /**
     * 设置响应内容类型.
     *
     * @param string $contentType
     * @param string $charset
     *
     * @return $this
     */
    public function setContentType(string $contentType, ?string $charset = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        if (null === $charset) {
            $charset = $this->getCharset();
        }

        if (null === $charset) {
            $this->setHeader('Content-Type', $contentType);
        } else {
            $this->setHeader('Content-Type', $contentType.'; charset='.$charset);
        }

        return $this;
    }

    /**
     * 设置响应内容长度.
     *
     * @param int $contentLength
     *
     * @return $this
     */
    public function setContentLength(int $contentLength): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->setHeader('Content-Length', (string) $contentLength);

        return $this;
    }

    /**
     * 设置自定义标识符.
     *
     * @param string $etag
     *
     * @return $this
     */
    public function setEtag(string $etag): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->setHeader('Etag', $etag);

        return $this;
    }

    /**
     * 响应是否为 JSON.
     *
     * @return bool
     */
    public function isJson(): bool
    {
        return $this->isJson;
    }

    /**
     * 响应是否正确.
     *
     * @return bool
     */
    public function isInvalid(): bool
    {
        return $this->statusCode < 100 || $this->statusCode >= 600;
    }

    /**
     * 是否为信息性响应.
     *
     * @return bool
     */
    public function isInformational(): bool
    {
        return $this->statusCode >= 100 && $this->statusCode < 200;
    }

    /**
     * 是否为正确响应.
     *
     * @return bool
     */
    public function isSuccessful(): bool
    {
        return $this->statusCode >= 200 && $this->statusCode < 300;
    }

    /**
     * 是否为重定向响应.
     *
     * @return bool
     */
    public function isRedirection(): bool
    {
        return $this->statusCode >= 300 && $this->statusCode < 400;
    }

    /**
     * 是否为客户端错误响应.
     *
     * @return bool
     */
    public function isClientError(): bool
    {
        return $this->statusCode >= 400 && $this->statusCode < 500;
    }

    /**
     * 是否为服务端错误响应.
     *
     * @return bool
     */
    public function isServerError(): bool
    {
        return $this->statusCode >= 500 && $this->statusCode < 600;
    }

    /**
     * 是否为正常响应.
     *
     * @return bool
     */
    public function isOk(): bool
    {
        return 200 === $this->statusCode;
    }

    /**
     * 是否为受限响应.
     *
     * @return bool
     */
    public function isForbidden(): bool
    {
        return 403 === $this->statusCode;
    }

    /**
     * 是否为 404 NOT FOUND.
     *
     * @return bool
     */
    public function isNotFound(): bool
    {
        return 404 === $this->statusCode;
    }

    /**
     * 是否为表单重定向响应.
     *
     * @param string $location
     *
     * @return bool
     */
    public function isRedirect(?string $location = null): bool
    {
        return in_array($this->statusCode, [
            201,
            301,
            302,
            303,
            307,
            308,
        ], true)
            && (null === $location ?: $location === $this->headers->get('Location'));
    }

    /**
     * 是否为空响应.
     *
     * @return bool
     */
    public function isEmpty(): bool
    {
        return in_array($this->statusCode, [
            204,
            304,
        ], true);
    }

    /**
     * 格式化响应时间.
     *
     * @param \DateTime $datetime
     *
     * @return string
     */
    protected function normalizeDateTime(DateTime $datetime): string
    {
        $date = clone $datetime;

        $date->setTimezone(new DateTimeZone('UTC'));

        return $date->format('D, d M Y H:i:s').' GMT';
    }

    /**
     * 内容转换为 JSON.
     *
     * @param mixed $content
     *
     * @return string
     */
    protected function contentToJson($content): string
    {
        if ($content instanceof IJson) {
            return $content->toJson();
        }

        if ($content instanceof IArray) {
            $content = $content->toArray();
        }

        return json_encode($content, JSON_UNESCAPED_UNICODE);
    }

    /**
     * 可以转换为 JSON.
     *
     * @param mixed $content
     *
     * @return bool
     */
    protected function contentShouldJson($content): bool
    {
        return $content instanceof IJson ||
               $content instanceof IArray ||
               $content instanceof ArrayObject ||
               $content instanceof JsonSerializable ||
               is_array($content);
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Database\Ddd\Relation;

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

/**
 * 关联模型实体 BelongsTo.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.09.28
 *
 * @version 1.0
 */
class BelongsTo extends Relation
{
    /**
     * 构造函数.
     *
     * @param \Leevel\Database\Ddd\IEntity $targetEntity
     * @param \Leevel\Database\Ddd\IEntity $sourceEntity
     * @param string                       $targetKey
     * @param string                       $sourceKey
     */
    public function __construct(IEntity $targetEntity, IEntity $sourceEntity, string $targetKey, string $sourceKey)
    {
        parent::__construct($targetEntity, $sourceEntity, $targetKey, $sourceKey);
    }

    /**
     * 关联基础查询条件.
     */
    public function addRelationCondition(): void
    {
        if (static::$relationCondition) {
            $this->select->where(
                $this->targetKey,
                $this->getSourceValue()
            );
        }
    }

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

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

            if (isset($maps[$key])) {
                $value->withRelationProp($relation, $maps[$key]);
            }
        }

        return $entitys;
    }

    /**
     * 设置预载入关联查询条件.
     *
     * @param \Leevel\Database\Ddd\IEntity[] $entitys
     */
    public function preLoadCondition(array $entitys): void
    {
        $this->select->whereIn(
            $this->targetKey,
            $this->getPreLoadEntityValue($entitys)
        );
    }

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

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

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

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

        return $maps;
    }

    /**
     * 分析预载入模型实体中对应的源数据.
     *
     * @param \Leevel\Database\Ddd\IEntity[] $entitys
     *
     * @return array
     */
    protected function getPreLoadEntityValue(array $entitys): array
    {
        $arr = [];

        foreach ($entitys as $value) {
            if (null !== ($tmp = $value->__get($this->sourceKey))) {
                $arr[] = $tmp;
            }
        }

        if (0 === count($arr)) {
            return [0];
        }

        return array_values(array_unique($arr));
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Protocol\Console\Base;

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

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

        $server = $this->createServer();

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

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

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

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

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

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

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

        Process::kill($pid, $this->option('all') ? SIGUSR1 : SIGUSR2);

        // 开启 opcache 重连后需要刷新
        if (function_exists('opcache_reset')) {
            opcache_reset();
        }

        if (function_exists('apc_clear_cache')) {
            apc_clear_cache();
        }

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

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

    /**
     * 命令配置.
     *
     * @return array
     */
    protected function getOptions(): array
    {
        return [
            [
                'all',
                null,
                Option::VALUE_OPTIONAL,
                'Reload all progress or only task process.',
            ],
        ];
    }
}


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

/**
 * Api 响应请求
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.03.01
 *
 * @version 1.0
 */
class ApiResponse extends JsonResponse
{
    /**
     * 创建一个 API 响应.
     *
     * @param mixed $data
     * @param int   $status
     * @param array $headers
     *
     * @return static
     */
    public static function create($data = '', int $status = 200, array $headers = []): IResponse
    {
        return new static($data, $status, $headers);
    }

    /**
     * 请求成功
     * 一般用于GET与POST请求: 200.
     *
     * @param mixed  $content
     * @param string $text
     *
     * @return $this
     */
    public function ok($content = '', ?string $text = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->setStatusCode(static::HTTP_OK, $text);

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

    /**
     * 已创建
     * 成功请求并创建了新的资源: 201.
     *
     * @param null|string $location
     * @param mixed       $content
     *
     * @return $this
     */
    public function created(?string $location = null, $content = ''): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->setData($content);
        $this->setStatusCode(static::HTTP_CREATED);

        if (null !== $location) {
            $this->setHeader('Location', $location);
        }

        return $this;
    }

    /**
     * 已接受
     * 已经接受请求，但未处理完成: 202.
     *
     * @param null|string $location
     * @param mixed       $content
     *
     * @return $this
     */
    public function accepted(?string $location = null, $content = ''): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->setData($content);
        $this->setStatusCode(static::HTTP_ACCEPTED);

        if (null !== $location) {
            $this->setHeader('Location', $location);
        }

        return $this;
    }

    /**
     * 无内容
     * 服务器成功处理，但未返回内容: 204.
     *
     * @return $this
     */
    public function noContent(): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        return $this->setStatusCode(static::HTTP_NO_CONTENT);
    }

    /**
     * 无法处理的实体
     * 请求格式正确，但是由于含有语义错误，无法响应: 422.
     *
     * @param array  $errors
     * @param string $message
     * @param string $text
     *
     * @return $this
     */
    public function unprocessableEntity(?array $errors = null, ?string $message = null, ?string $text = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->setStatusCode(static::HTTP_UNPROCESSABLE_ENTITY, $text);

        $this->setData([
            'message' => $this->parseErrorMessage($message), 'errors'  => $errors ?: [],
        ]);

        return $this;
    }

    /**
     * 错误请求
     * 服务器不理解请求的语法: 400.
     *
     * @param string $message
     * @param int    $statusCode
     * @param string $text
     * @param mixed  $statusCode
     *
     * @return $this
     */
    public function error(?string $message, int $statusCode, ?string $text = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $this->setStatusCode($statusCode, $text);

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

    /**
     * 错误请求
     * 服务器不理解请求的语法: 400.
     *
     * @param string $message
     * @param string $text
     *
     * @return $this
     */
    public function badRequest(?string $message = null, ?string $text = null): IResponse
    {
        return $this->error($message, static::HTTP_BAD_REQUEST, $text);
    }

    /**
     * 未授权
     * 对于需要登录的网页，服务器可能返回此响应: 401.
     *
     * @param string $message
     * @param string $text
     *
     * @return $this
     */
    public function unauthorized(?string $message = null, ?string $text = null): IResponse
    {
        return $this->error($message, static::HTTP_UNAUTHORIZED, $text);
    }

    /**
     * 禁止
     * 服务器拒绝请求: 403.
     *
     * @param string $message
     * @param string $text
     *
     * @return $this
     */
    public function forbidden(?string $message = null, ?string $text = null): IResponse
    {
        return $this->error($message, static::HTTP_FORBIDDEN, $text);
    }

    /**
     * 未找到
     * 用户发出的请求针对的是不存在的记录: 404.
     *
     * @param string $message
     * @param string $text
     *
     * @return $this
     */
    public function notFound(?string $message = null, ?string $text = null): IResponse
    {
        return $this->error($message, static::HTTP_NOT_FOUND, $text);
    }

    /**
     * 方法禁用
     * 禁用请求中指定的方法: 405.
     *
     * @param string $message
     * @param string $text
     *
     * @return $this
     */
    public function methodNotAllowed(?string $message = null, ?string $text = null): IResponse
    {
        return $this->error($message, static::HTTP_METHOD_NOT_ALLOWED, $text);
    }

    /**
     * 太多请求
     * 用户在给定的时间内发送了太多的请求: 429.
     *
     * @param string $message
     * @param string $text
     *
     * @return $this
     */
    public function tooManyRequests(?string $message = null, ?string $text = null): IResponse
    {
        return $this->error($message, static::HTTP_TOO_MANY_REQUESTS, $text);
    }

    /**
     * 服务器内部错误
     * 服务器遇到错误，无法完成请求: 500.
     *
     * @param string $message
     * @param string $text
     *
     * @return $this
     */
    public function internalServerError(?string $message = null, ?string $text = null): IResponse
    {
        return $this->error($message, static::HTTP_INTERNAL_SERVER_ERROR, $text);
    }

    /**
     * 格式化错误消息.
     *
     * @param string      $message
     * @param null|string $text
     *
     * @return $this
     */
    protected function normalizeErrorMessage(?string $message = null, ?string $text = null): IResponse
    {
        return $this->setData([
            'message' => $this->parseErrorMessage($message),
        ]);
    }

    /**
     * 分析错误消息.
     *
     * @param string $message
     *
     * @return string
     */
    protected function parseErrorMessage(?string $message = null): string
    {
        return $message ?: $this->statusText;
    }
}