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

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

/**
 * 验证码
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.07.27
 *
 * @version 1.0
 */
class Seccode implements ISeccode
{
    /**
     * 验证码
     *
     * @var string
     */
    protected $code;

    /**
     * 字体颜色.
     *
     * @var array
     */
    protected $fontColor = [];

    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        // 验证码宽度
        'width' => 160,

        // 验证码高度
        'height' => 60,

        // 随机背景图形
        'adulterate' => true,

        // 随机倾斜度
        'tilt' => true,

        // 随机颜色
        'color' => true,

        // 随机大小
        'size' => true,

        // 文字阴影
        'shadow' => true,

        // 英文字体路径
        'font_path' => '',

        // 中文字体路径
        'chinese_font_path' => '',

        // 背景图路径
        'background_path' => '',

        // 启用背景图像
        'background' => true,
    ];

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

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

        return $this;
    }

    /**
     * 设置验证码
     *
     * @param mixed  $code
     * @param string $outPath
     * @param bool   $autoCode
     * @param string $autoType
     */
    public function display($code = null, ?string $outPath = null, bool $autoCode = true, string $autoType = self::ALPHA_UPPERCASE): void
    {
        if (is_int($code) && $autoCode) {
            $this->autoCode($code, $autoType);
        } else {
            $code && $this->code($code);
        }

        $resImage = imagecreatefromstring($this->makeBackground());

        if ($this->option['adulterate']) {
            $this->makeAdulterate($resImage);
        }

        $this->makeTtfFont($resImage);

        if ($outPath) {
            create_directory(dirname($outPath));
            imagepng($resImage, $outPath, 9);
        } else {
            // @codeCoverageIgnoreStart
            header('Content-type: image/png');
            imagepng($resImage);
            // @codeCoverageIgnoreEnd
        }

        imagedestroy($resImage);
    }

    /**
     * 设置验证码
     *
     * @param string $code
     *
     * @return $this
     */
    public function code(string $code): ISeccode
    {
        $this->code = $code;

        return $this;
    }

    /**
     * 返回验证码
     *
     * @return string
     */
    public function getCode(): ?string
    {
        return $this->code;
    }

    /**
     * 返回宽度.
     *
     * @return int
     */
    protected function normalizeWidth(): int
    {
        if ($this->option['width'] < static::MIN_WIDTH) {
            return static::MIN_WIDTH;
        }

        if ($this->option['width'] > static::MAX_WIDTH) {
            return static::MAX_WIDTH;
        }

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

    /**
     * 返回高度.
     *
     * @return int
     */
    protected function normalizeHeight(): int
    {
        if ($this->option['height'] < static::MIN_HEIGHT) {
            return static::MIN_HEIGHT;
        }

        if ($this->option['height'] > static::MAX_HEIGHT) {
            return static::MAX_HEIGHT;
        }

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

    /**
     * 创建背景图像.
     *
     * @return string
     */
    protected function makeBackground(): string
    {
        $resImage = imagecreatetruecolor($this->normalizeWidth(), $this->normalizeHeight());
        $resColor = imagecolorallocate($resImage, 255, 255, 255);

        if (false === $this->makeBackgroundWithImage($resImage)) {
            $this->makeBackgroundDefault($resImage);
        }

        ob_start();

        imagepng($resImage);

        imagedestroy($resImage);
        $background = ob_get_contents();

        ob_end_clean();

        return $background;
    }

    /**
     * 创建随机背景图形.
     *
     * @param resource $resImage
     */
    protected function makeAdulterate(&$resImage): void
    {
        $width = $this->normalizeWidth();
        $height = $this->normalizeHeight();

        $lineNum = $height / 10;

        for ($i = 0; $i <= $lineNum; $i++) {
            $resColor = $this->option['color'] ?
                imagecolorallocate(
                    $resImage,
                    $this->mtRand(0, 255),
                    $this->mtRand(0, 255),
                    $this->mtRand(0, 255)
                ) :
                imagecolorallocate(
                    $resImage,
                    $this->fontColor[0],
                    $this->fontColor[1],
                    $this->fontColor[2]
                );

            $x = $this->mtRand(0, $width);
            $y = $this->mtRand(0, $height);

            if (mt_rand(0, 1)) {
                imagearc(
                    $resImage,
                    $x,
                    $y,
                    $this->mtRand(0, $width),
                    $this->mtRand(0, $height),
                    $this->mtRand(0, 360),
                    $this->mtRand(0, 360),
                    $resColor
                );
            } else {
                imageline(
                    $resImage,
                    $x,
                    $y,
                    0 + $this->mtRand(0, 0),
                    0 + $this->mtRand(0, $this->mtRand($height, $width)),
                    $resColor
                );
            }
        }
    }

    /**
     * 创建字体信息.
     *
     * @param resource $resImage
     */
    protected function makeTtfFont(&$resImage): void
    {
        if (!function_exists('imagettftext')) {
            // @codeCoverageIgnoreStart
            throw new InvalidArgumentException('Function imagettftext is not exits.');
            // @codeCoverageIgnoreEnd
        }

        list($font, $code, $widthTotal) = $this->getFontOption();

        $width = $this->normalizeWidth();
        $height = $this->normalizeHeight();

        // deg2rad() 函数将角度转换为弧度 cos 是 cosine 的简写
        // 表示余弦函数
        $x = $this->mtRand(
            $font[0]['tilt'] > 0 ?
                cos(deg2rad(90 - $font[0]['tilt'])) * $font[0]['zheight'] :
                1,
            $width - $widthTotal
        );

        // 是否启用随机颜色
        if (!$this->option['color']) {
            $resTextColor = imagecolorallocate(
                $resImage,
                $this->fontColor[0],
                $this->fontColor[1],
                $this->fontColor[2]
            );
        }

        for ($i = 0; $i < count($font); $i++) {
            if ($this->option['color']) {
                $this->fontColor = [
                    $this->mtRand(0, 255),
                    $this->mtRand(0, 255),
                    $this->mtRand(0, 255),
                ];

                $resTextColor = imagecolorallocate(
                    $resImage,
                    $this->fontColor[0],
                    $this->fontColor[1],
                    $this->fontColor[2]
                );
            }

            if ($this->option['shadow']) {
                $resTextShadowColor = imagecolorallocate(
                    $resImage,
                    255 - $this->fontColor[0],
                    255 - $this->fontColor[1],
                    255 - $this->fontColor[2]
                );
            }

            $y = $font[0]['tilt'] > 0 ?
                $this->mtRand(
                    $font[$i]['height'],
                    $height
                ) :
                $this->mtRand(
                    $font[$i]['height'] - $font[$i]['hd'],
                    $height - $font[$i]['hd']
                );

            if ($this->option['shadow']) {
                imagettftext($resImage,
                    $font[$i]['size'],
                    $font[$i]['tilt'],
                    (int) ($x + 1),
                    (int) ($y + 1),
                    $resTextShadowColor,
                    $font[$i]['font'],
                    $code[$i]
                );
            }

            imagettftext($resImage,
                $font[$i]['size'],
                $font[$i]['tilt'],
                (int) ($x),
                (int) ($y),
                $resTextColor,
                $font[$i]['font'],
                $code[$i]
            );

            $x += $font[$i]['width'];
        }
    }

    /**
     * 返回字体参数.
     *
     * @return array
     */
    protected function getFontOption(): array
    {
        $code = $this->getCode();
        $ttf = $this->getTtf();

        if ($this->isChinese($code)) {
            $code = str_split($code, 3);
            $codeLength = count($code);
        } else {
            $codeLength = strlen($code);
        }

        $font = [];
        $widthTotal = 0;
        $width = $this->normalizeWidth();

        for ($i = 0; $i < $codeLength; $i++) {
            if (!isset($font[$i])) {
                $font[$i] = [];
            }

            $font[$i]['font'] = $ttf[array_rand($ttf)];
            $font[$i]['tilt'] = $this->option['tilt'] ? $this->mtRand(-30, 30) : 0;
            $font[$i]['size'] = $width / 6;

            $this->option['size'] &&
                $font[$i]['size'] = $this->mtRand(
                    $font[$i]['size'] - $width / 40,
                    $font[$i]['size'] + $width / 20
                );

            $resBox = imagettfbbox(
                $font[$i]['size'],
                0,
                $font[$i]['font'],
                $code[$i]
            );

            $font[$i]['zheight'] = max($resBox[1], $resBox[3]) -
                min($resBox[5], $resBox[7]);

            $resBox = imagettfbbox(
                $font[$i]['size'],
                $font[$i]['tilt'],
                $font[$i]['font'],
                $code[$i]
            );

            $font[$i]['height'] = max($resBox[1], $resBox[3]) -
                min($resBox[5], $resBox[7]);

            $font[$i]['hd'] = $font[$i]['height'] - $font[$i]['zheight'];

            $font[$i]['width'] =
                (max($resBox[2], $resBox[4]) - min($resBox[0], $resBox[6])) +
                $this->mtRand(0, (int) ($width / 8));

            $font[$i]['width'] =
                $font[$i]['width'] > $width / $codeLength ?
                $width / $codeLength :
                $font[$i]['width'];

            $widthTotal += $font[$i]['width'];
        }

        return [
            $font,
            $code,
            $widthTotal,
        ];
    }

    /**
     * 创建图片背景图像.
     *
     * @param resource $resImage
     *
     * @return bool
     */
    protected function makeBackgroundWithImage(&$resImage): bool
    {
        $findBackground = false;
        $backgroundPath = $this->option['background_path'];
        $width = $this->normalizeWidth();
        $height = $this->normalizeHeight();

        if ($this->option['background'] &&
            function_exists('imagecreatefromjpeg') &&
            function_exists('imagecolorat') &&
            function_exists('imagecopymerge') &&
            function_exists('imagesetpixel') &&
            function_exists('imageSX') &&
            function_exists('imageSY')) {
            if (!is_dir($backgroundPath)) {
                $e = sprintf('Background path %s is not exists.', $backgroundPath);

                throw new InvalidArgumentException($e);
            }

            $background = glob($backgroundPath.'/*.*');

            if ($background) {
                $resBackground = imagecreatefromjpeg($background[array_rand($background)]);
                $resColorIndex = imagecolorat($resBackground, 0, 0);
                $color = imagecolorsforindex($resBackground, $resColorIndex);
                $resColorIndex = imagecolorat($resBackground, 1, 0);

                imagesetpixel($resBackground, 0, 0, $resColorIndex);

                $color[0] = (int) ($color['red']);
                $color[1] = (int) ($color['green']);
                $color[2] = (int) ($color['blue']);

                imagecopymerge(
                    $resImage,
                    $resBackground,
                    0,
                    0,
                    $this->mtRand(0, 200 - $width),
                    $this->mtRand(0, 80 - $height),
                    imagesx($resBackground),
                    imagesy($resBackground),
                    100
                );

                imagedestroy($resBackground);

                $findBackground = true;

                $this->fontColor = $color;
            }
        }

        return $findBackground;
    }

    /**
     * 创建默认背景图像.
     *
     * @param resource $resImage
     */
    protected function makeBackgroundDefault(&$resImage): void
    {
        $width = $this->normalizeWidth();
        $height = $this->normalizeHeight();

        for ($i = 0; $i < 3; $i++) {
            $start[$i] = $this->mtRand(200, 255);
            $end[$i] = $this->mtRand(100, 150);
            $step[$i] = ($end[$i] - $start[$i]) / $width;
            $color[$i] = $start[$i];
        }

        for ($i = 0; $i < $width; $i++) {
            $resColor = imagecolorallocate(
                $resImage,
                (int) ($color[0]),
                (int) ($color[1]),
                (int) ($color[2])
            );

            imageline(
                $resImage,
                $i,
                0,
                $i - ($this->option['tilt'] ? $this->mtRand(-30, 30) : 0),
                $height,
                $resColor
            );

            $color[0] += $step[0];
            $color[1] += $step[1];
            $color[2] += $step[2];
        }

        $color[0] -= 20;
        $color[1] -= 20;
        $color[2] -= 20;

        $color[0] = (int) ($color[0]);
        $color[1] = (int) ($color[1]);
        $color[2] = (int) ($color[2]);

        $this->fontColor = $color;

        unset($color);
    }

    /**
     * 返回验证字体.
     *
     * @return array
     */
    protected function getTtf(): array
    {
        $fontPath = $this->isChinese($this->getCode()) ?
            $this->option['chinese_font_path'] :
            $this->option['font_path'];

        if (!is_dir($fontPath)) {
            $e = sprintf('Font path %s is not exits.', $fontPath);

            throw new InvalidArgumentException($e);
        }

        $ttf = glob($fontPath.'/*.*');

        if (empty($ttf)) {
            throw new InvalidArgumentException('Font files not found.');
        }

        return $ttf;
    }

    /**
     * 自动产生验证码
     *
     * @param int    $size
     * @param string $autoType
     */
    protected function autoCode(int $size, string $autoType = self::ALPHA_UPPERCASE): void
    {
        if ($size < 1) {
            $e = sprintf('Code must be greater than %d.', 0);

            throw new InvalidArgumentException($e);
        }

        if (!in_array($autoType, $this->getAllowedAutoType(), true)) {
            $e = sprintf(
                'Code type must be these %s.',
                implode(',', $this->getAllowedAutoType())
            );

            throw new InvalidArgumentException($e);
        }

        $randMethod = 'Leevel\\Support\\Str\\rand_'.$autoType;

        $this->code(fn($randMethod, $size));
    }

    /**
     * 返回允许自动验证码类型.
     *
     * @return array
     */
    protected function getAllowedAutoType(): array
    {
        return [
            static::ALPHA_NUM,
            static::ALPHA_NUM_LOWERCASE,
            static::ALPHA_NUM_UPPERCASE,
            static::ALPHA,
            static::ALPHA_LOWERCASE,
            static::ALPHA_UPPERCASE,
            static::NUM,
            static::CHINESE,
        ];
    }

    /**
     * 是否为中文.
     *
     * @param string $code
     *
     * @return bool
     */
    protected function isChinese(?string $code = null): bool
    {
        return preg_match('/^[\x{4e00}-\x{9fa5}]+$/u', (string) ($code)) > 0;
    }

    /**
     * 生成随机数.
     *
     * @param number $numFirst
     * @param number $numSecond
     *
     * @return int
     */
    protected function mtRand($numFirst, $numSecond): int
    {
        $numFirst = (int) ($numFirst);
        $numSecond = (int) ($numSecond);

        if ($numFirst > $numSecond) {
            list($numSecond, $numFirst) = [$numFirst, $numSecond];
        }

        return (int) (mt_rand($numFirst, $numSecond));
    }
}

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


<?php

declare(strict_types=1);

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

namespace Leevel\View;

use Leevel\Manager\Manager as Managers;

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

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

    /**
     * 创建 html 模板驱动.
     *
     * @param array $options
     *
     * @return \Leevel\View\Html
     */
    protected function makeConnectHtml(array $options = []): Html
    {
        $options = $this->normalizeConnectOption('html', $options);
        $options = array_merge(
            $options, $this->viewOptionCommon()
        );

        $container = $this->container;

        $html = new Html($options);

        $html->setParseResolver(function () use ($container) {
            return $container['view.parser'];
        });

        return $html;
    }

    /**
     * 创建 phpui 模板驱动.
     *
     * @param array $options
     *
     * @return \Leevel\View\Phpui
     */
    protected function makeConnectPhpui(array $options = []): Phpui
    {
        $options = $this->normalizeConnectOption('phpui', $options);
        $options = array_merge(
            $options, $this->viewOptionCommon()
        );

        return new Phpui($options);
    }

    /**
     * 视图公共配置.
     *
     * @return array
     */
    protected function viewOptionCommon(): array
    {
        return [
            'theme_path' => $this->container->themesPath(),
            'cache_path' => $this->container->runtimePath('theme'),
        ];
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Database;

use Closure;
use InvalidArgumentException;
use Leevel\Collection\Collection;
use Leevel\Page\IPage;
use Leevel\Page\Page;

/**
 * 数据库查询器
 * This class borrows heavily from the QeePHP Framework and is part of the QeePHP package.
 * 查询器主体方法来自于早年 QeePHP 数据库查询 Api,这个 10 年前的作品设计理念非常先进.
 * 在这个思想下大量进行了重构，在查询 API 用法上我们将一些与 Laravel 的用法习惯靠拢，实现了大量语法糖.
 * 也支持 ThinkPHP 这种的数组方式传入查询，查询构造器非常复杂，为保证结果符合预期这里编写了大量的单元测试.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.03.09
 *
 * @version 1.0
 *
 * @see http://qeephp.com
 * @see http://qeephp.cn/docs/qeephp-manual/
 */
class Select
{
    /**
     * 分页查询结果标识.
     *
     * @var string
     */
    const PAGE = ':page';

    /**
     * 数据库连接.
     *
     * @var \Leevel\Database\Connect
     */
    protected $connect;

    /**
     * 查询条件.
     *
     * @var \Leevel\Database\Condition
     */
    protected $condition;

    /**
     * 分页查询条件备份.
     *
     * @var array
     */
    protected $backupPage = [];

    /**
     * 不查询直接返回 SQL.
     *
     * @var bool
     */
    protected $onlyMakeSql = false;

    /**
     * 查询类型.
     *
     * @var array
     */
    protected static $queryParamsDefault = [
        // PDO:fetchAll 参数
        'fetch_args' => [
            'fetch_style'     => null,
            'fetch_argument'  => null,
            'ctor_args'       => [],
        ],

        // 查询主服务器
        'master' => false,

        // 每一项记录以对象返回
        'as_class' => null,

        // 对象附加参数
        'class_args' => [],

        // 数组或者默认
        'as_default' => true,

        // 以对象集合方法返回
        'as_collection' => false,
    ];

    /**
     * 查询类型.
     *
     * @var array
     */
    protected $queryParams = [];

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

        $this->condition = new Condition($connect);

        $this->initOption();
    }

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

            return $this;
        } catch (ConditionNotFoundException $e) {
            // 不使用反射
            if (method_exists($this->connect, $method) &&
                is_callable([$this->connect, $method])) {
                return $this->connect->{$method}(...$args);
            }
        }

        // 动态查询支持
        if (0 === strncasecmp($method, 'find', 4)) {
            $method = substr($method, 4);

            // support find10start3 etc.
            if (false !== strpos(strtolower($method), 'start')) {
                $values = explode('start', strtolower($method));
                $num = (int) (array_shift($values));
                $offset = (int) (array_shift($values));

                return $this->limit($offset, $num)->
                find();
            }

            // support findByName findByNameAndSex etc.
            // support findAllByNameAndSex etc.
            if (0 === strncasecmp($method, 'By', 2) ||
                0 === strncasecmp($method, 'AllBy', 5)) {
                $method = substr($method, ($isOne = 0 === strncasecmp($method, 'By', 2)) ? 2 : 5);

                $isKeep = false;

                if ('_' === substr($method, -1)) {
                    $isKeep = true;
                    $method = substr($method, 0, -1);
                }

                $keys = explode('And', $method);

                if (count($keys) !== count($args)) {
                    $e = 'Parameters of findBy or findAllBy was not matched.';

                    throw new InvalidArgumentException($e);
                }

                if (!$isKeep) {
                    $keys = array_map(function ($item) {
                        return $this->unCamelize($item);
                    }, $keys);
                }

                $method = 'find'.($isOne ? 'One' : 'All');

                return $this
                    ->where(array_combine($keys, $args))
                    ->{$method}();
            }

            return $this
                ->top((int) ($method))
                ->find();
        }

        $e = sprintf('Select do not implement magic method %s.', $method);

        throw new InvalidArgumentException($e);
    }

    /**
     * 查询对象
     *
     * @return \Leevel\Database\Condition
     */
    public function databaseCondition(): Condition
    {
        return $this->condition;
    }

    /**
     * 返回数据库连接对象
     *
     * @return \Leevel\Database\Connect
     */
    public function databaseConnect(): Connect
    {
        return $this->connect;
    }

    /**
     * 占位符返回本对象
     *
     * @return $this
     */
    public function selfDatabaseSelect(): self
    {
        return $this;
    }

    /**
     * 指定返回 SQL 不做任何操作.
     *
     * @param bool $flag 指示是否不做任何操作只返回 SQL
     *
     * @return $this
     */
    public function sql(bool $flag = true): self
    {
        $this->onlyMakeSql = $flag;

        return $this;
    }

    /**
     * 设置是否查询主服务器.
     *
     * @param bool $master
     *
     * @return $this
     */
    public function master(bool $master = false): self
    {
        $this->queryParams['master'] = $master;

        return $this;
    }

    /**
     * 设置查询参数.
     *
     * @param int   $fetchStyle
     * @param mixed $fetchArgument
     * @param array $ctorArgs
     *
     * @return $this
     */
    public function fetchArgs(int $fetchStyle, $fetchArgument = null, array $ctorArgs = []): self
    {
        $this->queryParams['fetch_args']['fetch_style'] = $fetchStyle;

        if ($fetchArgument) {
            $this->queryParams['fetch_args']['fetch_argument'] = $fetchArgument;
        }

        $this->queryParams['fetch_args']['ctor_args'] = $ctorArgs;

        return $this;
    }

    /**
     * 设置以类返会结果.
     *
     * @param string $className
     * @param array  $args
     *
     * @return $this
     */
    public function asClass(string $className, array $args = []): self
    {
        $this->queryParams['as_class'] = $className;
        $this->queryParams['class_args'] = $args;
        $this->queryParams['as_default'] = false;

        return $this;
    }

    /**
     * 设置默认形式返回.
     *
     * @return $this
     */
    public function asDefault(): self
    {
        $this->queryParams['as_class'] = null;
        $this->queryParams['as_default'] = true;

        return $this;
    }

    /**
     * 设置是否以集合返回.
     *
     * @param bool $acollection
     *
     * @return $this
     */
    public function asCollection(bool $acollection = true): self
    {
        $this->queryParams['as_collection'] = $acollection;

        return $this;
    }

    /**
     * 原生 sql 查询数据 select.
     *
     * @param null|callable|select|string $data
     * @param array                       $bind
     * @param bool                        $flag 指示是否不做任何操作只返回 SQL
     *
     * @return mixed
     */
    public function select($data = null, array $bind = [], bool $flag = false)
    {
        // 查询对象直接查询
        if ($data instanceof self) {
            return $data->find(null, $this->onlyMakeSql);
        }

        // 回调
        if ($data instanceof Closure) {
            call_user_func_array($data, [
                $this,
            ]);
            $data = null;
        }

        // 调用查询
        if (null === $data) {
            return $this->find(null, $flag);
        }

        return $this
            ->safeSql($flag)
            ->runNativeSql(...[
                'select',
                $data,
                $bind,
            ]);
    }

    /**
     * 插入数据 insert (支持原生 sql).
     *
     * @param array|string $data
     * @param array        $bind
     * @param bool         $replace
     * @param bool         $flag    指示是否不做任何操作只返回 SQL
     *
     * @return null|array|int
     */
    public function insert($data, array $bind = [], bool $replace = false, bool $flag = false)
    {
        return $this
            ->safeSql($flag)
            ->runNativeSql(
                ...$this
                    ->condition
                    ->insert($data, $bind, $replace)
            );
    }

    /**
     * 批量插入数据 insertAll.
     *
     * @param array $data
     * @param array $bind
     * @param bool  $replace
     * @param bool  $flag    指示是否不做任何操作只返回 SQL
     *
     * @return null|array|int
     */
    public function insertAll(array $data, array $bind = [], bool $replace = false, bool $flag = false)
    {
        return $this
            ->safeSql($flag)
            ->runNativeSql(
                ...$this
                    ->condition
                    ->insertAll($data, $bind, $replace)
            );
    }

    /**
     * 更新数据 update (支持原生 sql).
     *
     * @param array|string $data
     * @param array        $bind
     * @param bool         $flag 指示是否不做任何操作只返回 SQL
     *
     * @return array|int
     */
    public function update($data, array $bind = [], bool $flag = false)
    {
        return $this
            ->safeSql($flag)
            ->runNativeSql(
                ...$this
                    ->condition
                    ->update($data, $bind)
            );
    }

    /**
     * 更新某个字段的值
     *
     * @param string $column
     * @param mixed  $value
     * @param array  $bind
     * @param bool   $flag   指示是否不做任何操作只返回 SQL
     *
     * @return array|int
     */
    public function updateColumn(string $column, $value, array $bind = [], bool $flag = false)
    {
        return $this->update([$column => $value], $bind, $flag);
    }

    /**
     * 字段递增.
     *
     * @param string $column
     * @param int    $step
     * @param array  $bind
     * @param bool   $flag   指示是否不做任何操作只返回 SQL
     *
     * @return array|int
     */
    public function updateIncrease(string $column, int $step = 1, array $bind = [], bool $flag = false)
    {
        return $this->updateColumn($column, '{['.$column.']+'.$step.'}', $bind, $flag);
    }

    /**
     * 字段减少.
     *
     * @param string $column
     * @param int    $step
     * @param array  $bind
     * @param bool   $flag   指示是否不做任何操作只返回 SQL
     *
     * @return array|int
     */
    public function updateDecrease(string $column, int $step = 1, array $bind = [], bool $flag = false)
    {
        return $this->updateColumn($column, '{['.$column.']-'.$step.'}', $bind, $flag);
    }

    /**
     * 删除数据 delete (支持原生 sql).
     *
     * @param null|string $data
     * @param array       $bind
     * @param bool        $flag 指示是否不做任何操作只返回 SQL
     *
     * @return array|int
     */
    public function delete(?string $data = null, array $bind = [], bool $flag = false)
    {
        return $this
            ->safeSql($flag)
            ->runNativeSql(
                ...$this
                    ->condition
                    ->delete($data, $bind)
            );
    }

    /**
     * 清空表重置自增 ID.
     *
     * @param bool $flag 指示是否不做任何操作只返回 SQL
     *
     * @return array|int
     */
    public function truncate(bool $flag = false)
    {
        return $this
            ->safeSql($flag)
            ->runNativeSql(
                ...$this
                    ->condition
                    ->truncate()
            );
    }

    /**
     * 返回一条记录.
     *
     * @param bool $flag 指示是否不做任何操作只返回 SQL
     *
     * @return mixed
     */
    public function findOne(bool $flag = false)
    {
        $this->condition->one();

        return $this
            ->safeSql($flag)
            ->query();
    }

    /**
     * 返回所有记录.
     *
     * @param bool $flag 指示是否不做任何操作只返回 SQL
     *
     * @return mixed
     */
    public function findAll(bool $flag = false)
    {
        $this->condition->all();

        return $this
            ->safeSql($flag)
            ->query();
    }

    /**
     * 返回最后几条记录.
     *
     * @param int  $num
     * @param bool $flag 指示是否不做任何操作只返回 SQL
     *
     * @return mixed
     */
    public function find(?int $num = null, bool $flag = false)
    {
        if (null !== $num) {
            $this->condition->top($num);
        }

        return $this
            ->safeSql($flag)
            ->query();
    }

    /**
     * 返回一个字段的值
     *
     * @param string $field
     * @param bool   $flag  指示是否不做任何操作只返回 SQL
     *
     * @return mixed
     */
    public function value(string $field, bool $flag = false)
    {
        $this
            ->condition
            ->setColumns($field)
            ->one();

        $result = (array) $this
            ->safeSql($flag)
            ->asDefault()
            ->query();

        if (true === $this->onlyMakeSql) {
            return $result;
        }

        return $result[$field] ?? null;
    }

    /**
     * 返回一个字段的值(别名).
     *
     * @param string $field
     * @param bool   $flag  指示是否不做任何操作只返回 SQL
     *
     * @return mixed
     */
    public function pull(string $field, bool $flag = false)
    {
        return $this->value($field, $flag);
    }

    /**
     * 返回一列数据.
     *
     * @param mixed  $fieldValue
     * @param string $fieldKey
     * @param bool   $flag       指示是否不做任何操作只返回 SQL
     *
     * @return array
     */
    public function list($fieldValue, ?string $fieldKey = null, bool $flag = false): array
    {
        // 纵然有弱水三千，我也只取一瓢 (第一个字段为值，第二个字段为键值，多余的字段丢弃)
        $fields = [];

        if (is_array($fieldValue)) {
            $fields = $fieldValue;
        } else {
            $fields[] = $fieldValue;
        }

        if ($fieldKey) {
            $fields[] = $fieldKey;
        }

        $this->condition->setColumns($fields);

        $tmps = $this
            ->safeSql($flag)
            ->asDefault()
            ->findAll();

        if (true === $this->onlyMakeSql) {
            return $tmps;
        }

        // 解析结果
        $result = [];

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

            if (1 === count($tmp)) {
                $result[] = reset($tmp);
            } else {
                $value = array_shift($tmp);
                $key = array_shift($tmp);
                $result[$key] = $value;
            }
        }

        return $result;
    }

    /**
     * 数据分块处理.
     *
     * @param int      $count
     * @param \Closure $chunk
     */
    public function chunk(int $count, Closure $chunk): void
    {
        $result = $this->forPage($page = 1, $count)->

        findAll();

        while (count($result) > 0) {
            if (false === $chunk($result, $page)) {
                break;
            }

            $page++;

            $result = $this
                ->forPage($page, $count)
                ->findAll();
        }
    }

    /**
     * 数据分块处理依次回调.
     *
     * @param int     $count
     * @param Closure $each
     */
    public function each(int $count, Closure $each): void
    {
        $this->chunk($count, function ($result, $page) use ($each) {
            foreach ($result as $key => $value) {
                if (false === $each($value, $key, $page)) {
                    return false;
                }
            }
        });
    }

    /**
     * 总记录数.
     *
     * @param string $field
     * @param string $alias
     * @param bool   $flag  指示是否不做任何操作只返回 SQL
     *
     * @return array|int
     */
    public function findCount(string $field = '*', string $alias = 'row_count', bool $flag = false)
    {
        $result = $this->findAggregateResult('count', $field, $alias, $flag);

        if (!is_array($result)) {
            $result = (int) $result;
        }

        return $result;
    }

    /**
     * 平均数.
     *
     * @param string $field
     * @param string $alias
     * @param bool   $flag  指示是否不做任何操作只返回 SQL
     *
     * @return mixed
     */
    public function findAvg(string $field, string $alias = 'avg_value', bool $flag = false)
    {
        return $this->findAggregateResult('avg', $field, $alias, $flag);
    }

    /**
     * 最大值
     *
     * @param string $field
     * @param string $alias
     * @param bool   $flag  指示是否不做任何操作只返回 SQL
     *
     * @return mixed
     */
    public function findMax(string $field, string $alias = 'max_value', bool $flag = false)
    {
        return $this->findAggregateResult('max', $field, $alias, $flag);
    }

    /**
     * 最小值
     *
     * @param string $field
     * @param string $alias
     * @param bool   $flag  指示是否不做任何操作只返回 SQL
     *
     * @return mixed
     */
    public function findMin(string $field, string $alias = 'min_value', bool $flag = false)
    {
        return $this->findAggregateResult('min', $field, $alias, $flag);
    }

    /**
     * 合计
     *
     * @param string $field
     * @param string $alias
     * @param bool   $flag  指示是否不做任何操作只返回 SQL
     *
     * @return mixed
     */
    public function findSum(string $field, string $alias = 'sum_value', bool $flag = false)
    {
        return $this->findAggregateResult('sum', $field, $alias, $flag);
    }

    /**
     * 分页查询.
     *
     * @param int    $currentPage
     * @param int    $perPage
     * @param bool   $flag
     * @param bool   $withTotal
     * @param string $column
     *
     * @return array
     */
    public function page(int $currentPage, int $perPage = 10, bool $flag = false, bool $withTotal = true, string $column = '*'): array
    {
        $from = ($currentPage - 1) * $perPage;

        return [
            [
                'per_page'     => $perPage,
                'current_page' => $currentPage,
                'total_record' => $withTotal ? $this->pageCount($column) : null,
                'from'         => $from,
            ],
            $this
                ->limit($from, $perPage)
                ->findAll($flag),
            self::PAGE => true,
        ];
    }

    /**
     * 分页查询.
     * 可以渲染 HTML.
     *
     * @param int    $currentPage
     * @param int    $perPage
     * @param bool   $flag
     * @param string $column
     * @param array  $option
     *
     * @return array
     */
    public function pageHtml(int $currentPage, int $perPage = 10, bool $flag = false, string $column = '*', array $option = []): array
    {
        $page = new Page($currentPage, $perPage, $this->pageCount($column), $option);

        return [
            $page,
            $this
                ->limit($page->getFromRecord(), $perPage)
                ->findAll($flag),
            self::PAGE => true,
        ];
    }

    /**
     * 创建一个无限数据的分页查询.
     *
     * @param int   $currentPage
     * @param int   $perPage
     * @param bool  $flag
     * @param array $option
     *
     * @return array
     */
    public function pageMacro(int $currentPage, int $perPage = 10, bool $flag = false, array $option = []): array
    {
        $page = new Page($currentPage, $perPage, IPage::MACRO, $option);

        return [
            $page,
            $this
                ->limit($page->getFromRecord(), $perPage)
                ->findAll($flag),
            self::PAGE => true,
        ];
    }

    /**
     * 创建一个只有上下页的分页查询.
     *
     * @param int   $currentPage
     * @param int   $perPage
     * @param bool  $flag
     * @param array $option
     *
     * @return array
     */
    public function pagePrevNext(int $currentPage, int $perPage = 10, bool $flag = false, array $option = []): array
    {
        $page = new Page($currentPage, $perPage, null, $option);

        return [
            $page,
            $this
                ->limit($page->getFromRecord(), $perPage)
                ->findAll($flag),
            self::PAGE => true,
        ];
    }

    /**
     * 取得分页查询记录数量.
     *
     * @param string $cols
     *
     * @return int
     */
    public function pageCount(string $cols = '*'): int
    {
        $this->backupPageArgs();

        $count = $this->findCount($cols);

        $this->restorePageArgs();

        return $count;
    }

    /**
     * 获得查询字符串.
     *
     * @param $withLogicGroup
     *
     * @return string
     */
    public function makeSql(bool $withLogicGroup = false): string
    {
        return $this->condition->
        makeSql($withLogicGroup);
    }

    /**
     * 安全格式指定返回 SQL 不做任何操作.
     *
     * @param bool $flag 指示是否不做任何操作只返回 SQL
     *
     * @return $this
     */
    protected function safeSql(bool $flag = true): self
    {
        if (true === $this->onlyMakeSql) {
            return $this;
        }

        $this->onlyMakeSql = $flag;

        return $this;
    }

    /**
     * 初始化查询条件.
     */
    protected function initOption(): void
    {
        $this->queryParams = static::$queryParamsDefault;
    }

    /**
     * 查询获得结果.
     *
     * @return mixed
     */
    protected function query()
    {
        $sql = $this->makeSql();

        $args = [
            $sql,
            $this->condition->getBindParams(),
            $this->queryParams['master'],
            $this->queryParams['fetch_args']['fetch_style'],
            $this->queryParams['fetch_args']['fetch_argument'],
            $this->queryParams['fetch_args']['ctor_args'],
        ];

        // 只返回 SQL，不做任何实际操作
        if (true === $this->onlyMakeSql) {
            return $args;
        }

        $data = $this->connect->query(...$args);

        if ($this->queryParams['as_default']) {
            $data = $this->queryDefault($data);
        } else {
            $data = $this->queryClass($data);
        }

        return $data;
    }

    /**
     * 以默认返回结果.
     *
     * @param array $data
     *
     * @return mixed
     */
    protected function queryDefault(array $data)
    {
        if (!$this->condition->getOption()['limitQuery']) {
            return reset($data) ?: [];
        }

        return $this->queryParams['as_collection'] ? new Collection($data) : $data;
    }

    /**
     * 以 class 返回结果.
     *
     * @param array $data
     *
     * @return mixed
     */
    protected function queryClass(array $data)
    {
        $className = $this->queryParams['as_class'];

        if (!class_exists($className)) {
            $e = sprintf('The class of query `%s` was not found.', $className);

            throw new InvalidArgumentException($e);
        }

        foreach ($data as $key => $tmp) {
            $data[$key] = new $className((array) $tmp, ...$this->queryParams['class_args']);
        }

        if (!$this->condition->getOption()['limitQuery']) {
            $data = reset($data) ?: new $className([], ...$this->queryParams['class_args']);
        } elseif ($this->queryParams['as_collection']) {
            $data = new Collection($data, [$className]);
        }

        return $data;
    }

    /**
     * 获取聚合结果.
     *
     * @param string $method
     * @param string $field
     * @param string $alias
     * @param bool   $flag   指示是否不做任何操作只返回 SQL
     *
     * @return mixed
     */
    protected function findAggregateResult(string $method, string $field, string $alias, bool $flag = false)
    {
        $this->condition->{$method}($field, $alias);

        $result = $this
            ->safeSql($flag)
            ->asDefault()
            ->query();

        if (true === $this->onlyMakeSql) {
            return $result;
        }

        return is_object($result) ? $result->{$alias} : $result[$alias];
    }

    /**
     * 原生 sql 执行方法.
     *
     * @param string $nativeType
     * @param string $data
     * @param array  $bindParams
     *
     * @return mixed
     */
    protected function runNativeSql(string $nativeType, string $data, array $bindParams = [])
    {
        $sqlType = $this->connect->normalizeSqlType($data);

        if ('procedure' === $sqlType) {
            $sqlType = 'select';
        }

        if ($sqlType !== $nativeType) {
            $e = sprintf('The SQL type `%s` must be consistent with the provided `%s`.', $sqlType, $nativeType);

            throw new InvalidArgumentException($e);
        }

        $args = [$data, $bindParams];

        // 只返回 SQL，不做任何实际操作
        if (true === $this->onlyMakeSql) {
            return $args;
        }

        $type = 'select' === $nativeType ? 'query' : 'execute';

        return $this->connect->{$type}(...$args);
    }

    /**
     * 备份分页查询条件.
     */
    protected function backupPageArgs(): void
    {
        $this->backupPage = [];
        $this->backupPage['query_params'] = $this->queryParams;
        $this->backupPage['aggregate'] = $this->condition->getOption()['aggregate'];
        $this->backupPage['columns'] = $this->condition->getOption()['columns'];
    }

    /**
     * 恢复分页查询条件.
     */
    protected function restorePageArgs(): void
    {
        $this->queryParams = $this->backupPage['query_params'];
        $this->condition->setOption('aggregate', $this->backupPage['aggregate']);
        $this->condition->setOption('columns', $this->backupPage['columns']);
    }

    /**
     * 驼峰转下划线.
     *
     * @param string $value
     * @param string $separator
     *
     * @return string
     */
    protected function unCamelize(string $value, string $separator = '_'): string
    {
        return strtolower(
            preg_replace(
                '/([a-z])([A-Z])/',
                '$1'.$separator.'$2',
                $value
            )
        );
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Session;

use Leevel\Cache\ICache;
use SessionHandlerInterface;

/**
 * connect 驱动抽象类.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.06.06
 * @see http://php.net/manual/zh/class.sessionhandlerinterface.php
 *
 * @version 1.0
 */
abstract class Connect implements SessionHandlerInterface
{
    /**
     * 缓存仓储.
     *
     * @var \Leevel\Cache\ICache
     */
    protected $cache;

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

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

    /**
     * {@inheritdoc}
     */
    public function open($savePath, $sessionName): bool
    {
        return true;
    }

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

    /**
     * {@inheritdoc}
     */
    public function read($sessionid): string
    {
        return serialize($this->cache->get(
            $this->getSessionName($sessionid), []
        ) ?: []);
    }

    /**
     * {@inheritdoc}
     */
    public function write($sessionid, $sessiondata): bool
    {
        $this->cache->set(
            $this->getSessionName($sessionid),
            unserialize($sessiondata)
        );

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function destroy($sessionid): bool
    {
        $this->cache->delete(
            $this->getSessionName($sessionid)
        );

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function gc($maxlifetime): int
    {
        return 0;
    }

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

    /**
     * 获取 session 名字.
     *
     * @param string $sessionid
     *
     * @return string
     */
    protected function getSessionName(string $sessionid): string
    {
        return $sessionid;
    }
}


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

/**
 * 上传文件
 * This class borrows heavily from the Symfony4 Framework and is part of the symfony package.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.02.26
 *
 * @version 1.0
 *
 * @see Symfony\Component\HttpFoundation (https://github.com/symfony/symfony)
 */
class UploadedFile extends File
{
    /**
     * 文件原始名字.
     *
     * @var string
     */
    protected $originalName;

    /**
     * 文件类型.
     *
     * @var string
     */
    protected $mimeType;

    /**
     * 上传错误.
     *
     * @var null|int
     */
    protected $error;

    /**
     * 上传错误消息格式化.
     *
     * @var array
     */
    protected static $errors = [
        UPLOAD_ERR_INI_SIZE   => 'The file %s exceeds your upload_max_filesize ini directive (limit is %d KiB).',
        UPLOAD_ERR_FORM_SIZE  => 'The file %s exceeds the upload limit defined in your form.',
        UPLOAD_ERR_PARTIAL    => 'The file %s was only partially uploaded.',
        UPLOAD_ERR_NO_FILE    => 'No file was uploaded.',
        UPLOAD_ERR_CANT_WRITE => 'The file %s could not be written on disk.',
        UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.',
        UPLOAD_ERR_EXTENSION  => 'File upload was stopped by a PHP extension.',
    ];

    /**
     * 是否为测试.
     *
     * @var bool
     */
    protected $test = false;

    /**
     * 构造函数
     * $_FILES['foo'](tmp_name, name, type, error).
     *
     * @param string      $path
     * @param string      $originalName
     * @param null|string $mimeType
     * @param null|int    $error
     */
    public function __construct(string $path, string $originalName, ?string $mimeType = null, ?int $error = null)
    {
        $this->originalName = $originalName;
        $this->mimeType = $mimeType ?: 'application/octet-stream';
        $this->error = $error ?: UPLOAD_ERR_OK;

        $this->test = 5 === func_num_args();

        parent::__construct($path);
    }

    /**
     * 返回文件原始名字.
     *
     * @return null|string
     */
    public function getOriginalName(): ?string
    {
        return $this->originalName;
    }

    /**
     * 返回文件原始名字扩展.
     *
     * @return string
     */
    public function getOriginalExtension(): string
    {
        return pathinfo($this->originalName, PATHINFO_EXTENSION);
    }

    /**
     * 返回文件类型.
     *
     * @return null|string
     */
    public function getMimeType(): ?string
    {
        return $this->mimeType;
    }

    /**
     * 返回上传错误.
     *
     * @return int
     */
    public function getError(): int
    {
        return $this->error;
    }

    /**
     * 文件是否上传成功.
     *
     * @return bool
     */
    public function isValid(): bool
    {
        return UPLOAD_ERR_OK === $this->error &&
            ($this->test ? true : is_uploaded_file($this->getPathname()));
    }

    /**
     * {@inheritdoc}
     *
     * @codeCoverageIgnore
     */
    public function move(string $directory, ?string $name = null): File
    {
        if ($this->isValid()) {
            if ($this->test) {
                return parent::move($directory, $name);
            }

            $target = $this->getTargetFile($directory, $name);

            $this->moveToTarget($this->getPathname(), $target, true);

            return new File($target);
        }

        throw new FileException($this->getErrorMessage());
    }

    /**
     * 返回文件最大上传字节
     *
     * @return int
     * @codeCoverageIgnoreStart
     */
    public static function getMaxFilesize(): int
    {
        $iniMax = strtolower(ini_get('upload_max_filesize'));

        if ('' === $iniMax) {
            return PHP_INT_MAX;
        }

        $max = ltrim($iniMax, '+');

        if (0 === strpos($max, '0x')) {
            $max = intval($max, 16);
        } elseif (0 === strpos($max, '0')) {
            $max = intval($max, 8);
        } else {
            $max = (int) $max;
        }

        switch (substr($iniMax, -1)) {
            case 't':
                $max *= 1024;
                // no break
            case 'g':
                $max *= 1024;
                // no break
            case 'm':
                $max *= 1024;
                // no break
            case 'k':
                $max *= 1024;
        }

        return $max;
    }

    /**
     * 上传错误.
     *
     * @return string
     */
    public function getErrorMessage(): string
    {
        $errorCode = $this->error;
        $maxFilesize = UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0;

        $message = isset(self::$errors[$errorCode]) ?
            self::$errors[$errorCode] :
            'The file %s was not uploaded due to an unknown error.';

        $message = sprintf($message, $this->getOriginalName(), $maxFilesize);

        return $message;
    }
}


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

/**
 * Redirect 响应请求
 * This class borrows heavily from the Symfony4 Framework and is part of the symfony package.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.02.28
 *
 * @version 1.0
 *
 * @see Symfony\Component\HttpFoundation (https://github.com/symfony/symfony)
 */
class RedirectResponse extends Response
{
    /**
     * 目标 URL 地址
     *
     * @var string
     */
    protected $targetUrl;

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

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

    /**
     * 构造函数.
     *
     * @param string $url
     * @param int    $status
     * @param array  $headers
     */
    public function __construct(?string $url = null, int $status = 302, array $headers = [])
    {
        parent::__construct('', $status, $headers);

        if ($url) {
            $this->setTargetUrl($url);
        }

        if (!$this->isRedirect()) {
            throw new InvalidArgumentException(
                sprintf('The HTTP status code is not a redirect (%s given).', $status)
            );
        }

        if (301 === $status && !array_key_exists('cache-control', $headers)) {
            $this->headers->remove('cache-control');
        }
    }

    /**
     * 创建 URL 跳转响应.
     *
     * @param mixed $url
     * @param int   $status
     * @param array $headers
     *
     * @return static
     */
    public static function create($url = '', int $status = 302, array $headers = []): IResponse
    {
        return new static($url, $status, $headers);
    }

    /**
     * 闪存一个数据片段到 SESSION.
     *
     * @param array|string $key
     * @param mixed        $value
     *
     * @return $this
     */
    public function with($key, $value = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $key = is_array($key) ? $key : [
            $key => $value,
        ];

        foreach ($key as $k => $v) {
            $this->session->flash($k, $v);
        }

        return $this;
    }

    /**
     * 闪存输入信息.
     *
     * @param null|array $input
     *
     * @return $this
     */
    public function withInput(?array $input = null): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $input = $input ?: $this->request->input();

        $inputs = array_merge($this->session->getFlash('inputs', []), $input);

        $this->session->flash('inputs', $inputs);

        return $this;
    }

    /**
     * 闪存输入信息.
     *
     * @return $this
     */
    public function onlyInput(...$args): IResponse
    {
        if (!$args) {
            throw new InvalidArgumentException('Method onlyInput need an args.');
        }

        return $this->withInput($this->request->only($args));
    }

    /**
     * 闪存输入信息.
     *
     * @return $this
     */
    public function exceptInput(...$args): IResponse
    {
        if (!$args) {
            throw new InvalidArgumentException('Method exceptInput need an args.');
        }

        return $this->withInput($this->request->except($args));
    }

    /**
     * 闪存错误信息.
     *
     * @param mixed  $value
     * @param string $key
     *
     * @return $this
     */
    public function withErrors($value, string $key = 'default'): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        $errors = $this->session->getFlash('errors', []);
        $errors[$key] = $value;

        $this->session->flash('errors', $errors);

        return $this;
    }

    /**
     * 获取目标 URL 地址.
     *
     * @return string
     */
    public function getTargetUrl(): string
    {
        return $this->targetUrl;
    }

    /**
     * 设置目标 URL 地址
     *
     * @param string $url
     *
     * @return $this
     */
    public function setTargetUrl(string $url): IResponse
    {
        if ($this->checkFlowControl()) {
            return $this;
        }

        if (empty($url)) {
            throw new InvalidArgumentException('Cannot redirect to an empty URL.');
        }

        $this->targetUrl = $url;

        $this->setContent(
            sprintf('<!DOCTYPE html>

        <meta charset="UTF-8" />
        <meta http-equiv="refresh" content="0;url=%1$s" />
        <title>Redirecting to %1$s</title>

        Redirecting to %1$s.

', htmlspecialchars($url, ENT_QUOTES, 'UTF-8')));

        $this->headers->set('Location', $url);

        return $this;
    }

    /**
     * 获取 HTTP 请求
     *
     * @return null|\Leevel\Http\IRequest
     */
    public function getRequest(): ?IRequest
    {
        return $this->request;
    }

    /**
     * 设置 HTTP 请求
     *
     * @param \Leevel\Http\IRequest $request
     */
    public function setRequest(IRequest $request): void
    {
        $this->request = $request;
    }

    /**
     * 获取 SESSION 仓储.
     *
     * @return null|\Leevel\Session\ISession
     */
    public function getSession(): ?ISession
    {
        return $this->session;
    }

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


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

/**
 * cookie 封装.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.19
 *
 * @version 1.0
 */
class Cookie
{
    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        'expire'   => 86400,
        'domain'   => '',
        'path'     => '/',
        'secure'   => false,
        'httponly' => false,
    ];

    /**
     * Cookie 设置数据.
     *
     * @var array
     */
    protected $cookies = [];

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

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

        return $this;
    }

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

        if (is_array($value)) {
            $value = json_encode($value);
        } elseif (!is_string($value) && null !== $value) {
            throw new Exception('Cookie value must be string,array or null.');
        }

        $option['expire'] = (int) ($option['expire']);

        if ($option['expire'] < 0) {
            throw new Exception('Cookie expire date must greater than or equal 0.');
        }

        if ($option['expire'] > 0) {
            $option['expire'] = time() + $option['expire'];
        }

        // 对应 setcookie 的参数
        $this->cookies[$name] = [
            $name,
            $value,
            $option['expire'],
            $option['path'],
            $option['domain'],
            $option['secure'],
            $option['httponly'],
        ];
    }

    /**
     * 批量插入.
     *
     * @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->set($key, $value, $option);
        }
    }

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

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

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

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

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

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

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

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

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

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

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

        if (isset($this->cookies[$name])) {
            if ($this->isJson($this->cookies[$name][1])) {
                return json_decode($this->cookies[$name][1], true);
            }

            return $this->cookies[$name][1];
        }

        return $defaults;
    }

    /**
     * 删除 cookie.
     *
     * @param string $name
     * @param array  $option
     */
    public function delete(string $name, array $option = []): void
    {
        $this->set($name, null, $option);
    }

    /**
     * 清空 cookie.
     *
     * @param array $option
     */
    public function clear(array $option = []): void
    {
        $option = $this->normalizeOptions($option);

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

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

    /**
     * 格式化 COOKIE 为字符串.
     *
     * @param array $cookie
     *
     * @return string
     *
     * @see \Symfony\Component\HttpFoundation\Cookie::__string()
     */
    public static function format(array $cookie): string
    {
        if (7 !== count($cookie)) {
            throw new Exception('Invalid cookie data.');
        }

        $str = $cookie[0].'=';

        if ('' === (string) $cookie[1]) {
            $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0';
        } else {
            $str .= $cookie[1];

            if (0 !== $cookie[2]) {
                $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $cookie[2]).'; Max-Age='.
                    ($cookie[2] - time() ?: 0);
            }
        }

        if ($cookie[3]) {
            $str .= '; path='.$cookie[3];
        }

        if ($cookie[4]) {
            $str .= '; domain='.$cookie[4];
        }

        if (true === $cookie[5]) {
            $str .= '; secure';
        }

        if (true === $cookie[6]) {
            $str .= '; httponly';
        }

        return $str;
    }

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

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

        json_decode((string) ($data));

        return JSON_ERROR_NONE === json_last_error();
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Database;

use InvalidArgumentException;
use Leevel\Event\IDispatch;
use Leevel\Manager\Manager as Managers;

/**
 * database 入口.
 *
 * @method $this whereNull(...$arr)
 * @method $this whereNotNull(...$arr)
 * @method $this whereBetween(...$arr)
 * @method $this whereNotBetween(...$arr)
 * @method $this whereIn(...$arr)
 * @method $this whereNotIn(...$arr)
 * @method $this whereLike(...$arr)
 * @method $this whereNotLike(...$arr)
 * @method $this havingNull(...$arr)
 * @method $this havingNotNull(...$arr)
 * @method $this havingBetween(...$arr)
 * @method $this havingNotBetween(...$arr)
 * @method $this havingIn(...$arr)
 * @method $this havingNotIn(...$arr)
 * @method $this havingLike(...$arr)
 * @method $this havingNotLike(...$arr)
 * @method $this innerJoin($table, $cols, $cond)
 * @method $this leftJoin($table, $cols, $cond)
 * @method $this rightJoin($table, $cols, $cond)
 * @method $this fullJoin($table, $cols, $cond)
 * @method $this crossJoin($table, $cols)
 * @method $this naturalJoin($table, $cols)
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.02.15
 *
 * @version 1.0
 */
class Manager extends Managers
{
    /**
     * 取得配置命名空间.
     *
     * @return string
     */
    protected function normalizeOptionNamespace(): string
    {
        return 'database';
    }

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

    /**
     * 创建 mysql 连接.
     *
     * @param array $option
     *
     * @return \Leevel\Database\Mysql
     */
    protected function makeConnectMysql(array $option = []): Mysql
    {
        return new Mysql(
            $this->normalizeConnectOption('mysql', $option),
            $this->container->make(IDispatch::class)
        );
    }

    /**
     * 读取默认配置.
     *
     * @param string $connect
     * @param array  $extendOption
     *
     * @return array
     */
    protected function normalizeConnectOption(string $connect, array $extendOption = []): array
    {
        return $this->parseDatabaseOption(
            parent::normalizeConnectOption($connect, $extendOption)
        );
    }

    /**
     * 分析数据库配置参数.
     *
     * @param array $option
     *
     * @return array
     */
    protected function parseDatabaseOption(array $option): array
    {
        $temp = $option;
        $type = ['distributed', 'separate', 'driver', 'master', 'slave'];

        foreach (array_keys($option) as $t) {
            if (in_array($t, $type, true)) {
                if (isset($temp[$t])) {
                    unset($temp[$t]);
                }
            } elseif (isset($option[$t])) {
                unset($option[$t]);
            }
        }

        foreach (['master', 'slave'] as $t) {
            if (!is_array($option[$t])) {
                $e = sprintf('Database option `%s` must be an array.', $t);

                throw new InvalidArgumentException($e);
            }
        }

        $option['master'] = array_merge($option['master'], $temp);

        if (!$option['distributed']) {
            $option['slave'] = [];
        } elseif ($option['slave']) {
            if (count($option['slave']) === count($option['slave'], COUNT_RECURSIVE)) {
                $option['slave'] = [$option['slave']];
            }

            foreach ($option['slave'] as &$slave) {
                $slave = array_merge($slave, $temp);
            }
        }

        return $option;
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Page;

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

/**
 * BootstrapSimple 分页渲染.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.07.14
 *
 * @version 1.0
 */
class BootstrapSimple extends Bootstrap
{
    /**
     * 配置.
     *
     * @var array
     */
    protected $option = [
        // center,justify
        'align'         => 'center',
        'template'      => '{header} {ul} {prev} {next} {endul} {footer}',
        'justify_align' => false,
    ];

    /**
     * 设置两端对齐.
     *
     * @return $this
     */
    public function setJustifyAlign(): IRender
    {
        $this->setOption('align', 'justify');
    }

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

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

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

    /**
     * 返回渲染 prev.
     *
     * @return string
     */
    protected function getPrevRender(): string
    {
        if ($this->page->canPrevRender()) {
            return sprintf(
                '
'.
                    '%s
',
                'justify' === $this->option['align'] ? 'previous' : '',
                $this->replace(
                    $this->page->parsePrevRenderPrev()
                ),
                __('上一页')
            );
        }

        return sprintf(
            '
'.
                '%s
',
            'justify' === $this->option['align'] ? ' previous' : '',
            __('上一页')
        );
    }

    /**
     * 返回渲染 next.
     *
     * @return string
     */
    protected function getNextRender(): string
    {
        if ($this->page->canNextRender()) {
            return sprintf(
                '
'.
                    '%s
',
                'justify' === $this->option['align'] ? 'next' : '',
                $this->replace(
                    $this->page->getCurrentPage() + 1
                ),
                __('下一页')
            );
        }

        return sprintf(
            '
'.
                '%s
',
            'justify' === $this->option['align'] ? ' next' : '',
            __('下一页')
        );
    }

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

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

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


<?php

declare(strict_types=1);

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

namespace Leevel\Collection;

use ArrayAccess;
use ArrayIterator;
use Closure;
use Countable;
use InvalidArgumentException;
use IteratorAggregate;
use JsonSerializable;
use Leevel\Support\IArray;
use Leevel\Support\IJson;
use function Leevel\Support\Type\type_these;
use Leevel\Support\Type\type_these;
use stdClass;

/**
 * 集合.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.21
 *
 * @version 1.0
 */
class Collection implements IArray, IJson, IteratorAggregate, ArrayAccess, Countable, JsonSerializable
{
    /**
     * 元素合集.
     *
     * @var array
     */
    protected $elements = [];

    /**
     * 验证
     *
     * @var bool
     */
    protected $valid = true;

    /**
     * 类型.
     *
     * @var mixed
     */
    protected $type = [];

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

        $elements = $this->getArrayElements($elements);

        if ($this->type) {
            foreach ($elements as $key => $value) {
                $this->offsetSet($key, $value);
            }
        } else {
            $this->elements = $elements;
        }

        unset($elements);
    }

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

    /**
     * __get 魔术方法.
     *
     * @param string $key
     *
     * @return mixed
     */
    public function __get($key)
    {
        return $this->offsetGet($key);
    }

    /**
     * __set 魔术方法.
     *
     * @param string $key
     * @param mixed  $value
     */
    public function __set($key, $value): void
    {
        $this->offsetSet($key, $value);
    }

    /**
     * 创建一个集合.
     *
     * @param mixed $elements
     * @param mixed $type
     */
    public static function make($elements = [], $type = null): self
    {
        return new static($elements, $type);
    }

    /**
     * 当前元素.
     *
     * @return mixed
     */
    public function current()
    {
        return current($this->elements);
    }

    /**
     * 当前 key.
     *
     * @return mixed
     */
    public function key()
    {
        return key($this->elements);
    }

    /**
     * 下一个元素.
     */
    public function next(): void
    {
        $next = next($this->elements);
        $this->valid = false !== $next;
    }

    /**
     * 指针重置.
     */
    public function rewind(): void
    {
        reset($this->elements);
        $this->valid = true;
    }

    /**
     * 验证.
     *
     * @return bool
     */
    public function valid(): bool
    {
        return $this->valid;
    }

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

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

    /**
     * 实现 ArrayAccess::offsetGet.
     *
     * @param string $index
     *
     * @return mixed
     */
    public function offsetGet($index)
    {
        return $this->elements[$index] ?? null;
    }

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

    /**
     * 实现 ArrayAccess::offsetUnset.
     *
     * @param mixed $index
     */
    public function offsetUnset($index): void
    {
        if (isset($this->elements[$index])) {
            unset($this->elements[$index]);
        }
    }

    /**
     * 统计元素数量 count($obj).
     *
     * @return int
     */
    public function count(): int
    {
        return count($this->elements);
    }

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

    /**
     * 对象转数组.
     *
     * @return array
     */
    public function toArray(): array
    {
        $args = func_get_args();

        return array_map(function ($value) use ($args) {
            return $value instanceof IArray ? $value->toArray(...$args) : $value;
        }, $this->elements);
    }

    /**
     * 实现 JsonSerializable::jsonSerialize.
     *
     * @return array
     */
    public function jsonSerialize(): array
    {
        return array_map(function ($value) {
            if ($value instanceof JsonSerializable) {
                return $value->jsonSerialize();
            }

            if ($value instanceof IJson) {
                return json_decode($value->toJson(), true);
            }

            if ($value instanceof IArray) {
                return $value->toArray();
            }

            return $value;
        }, $this->elements);
    }

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

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

    /**
     * each.
     *
     * @param \Closure $callback
     */
    public function each(Closure $callback): void
    {
        foreach ($this->elements as $key => $item) {
            if (false === $callback($item, $key)) {
                break;
            }
        }
    }

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

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

        $e = sprintf('Collection type %s validation failed.', implode(',', $this->type));

        throw new InvalidArgumentException($e);
    }

    /**
     * 转换数据到数组.
     *
     * @param mixed $elements
     *
     * @return array
     */
    protected function getArrayElements($elements): array
    {
        if (is_array($elements)) {
            return $elements;
        }

        if ($elements instanceof self) {
            return $elements->all();
        }

        if ($elements instanceof IArray) {
            return $elements->toArray();
        }

        if ($elements instanceof IJson) {
            return json_decode($elements->toJson(), true);
        }

        if ($elements instanceof JsonSerializable) {
            return $elements->jsonSerialize();
        }

        if ($elements instanceof stdClass) {
            return json_decode(json_encode($elements), true);
        }

        return [$elements];
    }
}

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


<?php

declare(strict_types=1);

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

namespace Leevel\Router;

use Leevel\Http\ApiResponse;
use Leevel\Http\FileResponse;
use Leevel\Http\JsonResponse;
use Leevel\Http\RedirectResponse;
use Leevel\Http\Response;
use Leevel\Http\ResponseHeaderBag;

/**
 * 响应工厂
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.03.03
 *
 * @version 1.0
 */
class ResponseFactory implements IResponseFactory
{
    /**
     * 视图.
     *
     * @var \Leevel\Router\IView
     */
    protected $view;

    /**
     * 跳转实例.
     *
     * @var \Leevel\Router\Redirector
     */
    protected $redirector;

    /**
     * 视图正确模板
     *
     * @var string
     */
    protected $viewSuccessTemplate = 'success';

    /**
     * 视图错误模板
     *
     * @var string
     */
    protected $viewFailTemplate = 'fail';

    /**
     * 构造函数.
     *
     * @param \Leevel\Router\IView    $view
     * @param \Leevel\Router\Redirect $redirector
     */
    public function __construct(IView $view, Redirect $redirector)
    {
        $this->view = $view;
        $this->redirector = $redirector;
    }

    /**
     * 返回一个响应.
     *
     * @param mixed $content
     * @param int   $status
     * @param array $headers
     *
     * @return \Leevel\Http\Response
     */
    public function make($content = '', $status = 200, array $headers = []): Response
    {
        return new Response($content, $status, $headers);
    }

    /**
     * 返回视图响应.
     *
     * @param string $file
     * @param array  $vars
     * @param string $ext
     * @param int    $status
     * @param array  $headers
     *
     * @return \Leevel\Http\Response
     */
    public function view(string $file, array $vars = [], ?string $ext = null, int $status = 200, array $headers = []): Response
    {
        return $this->make($this->view->display($file, $vars, $ext), $status, $headers);
    }

    /**
     * 返回视图成功消息.
     *
     * @param string $message
     * @param string $url
     * @param int    $time
     * @param int    $status
     * @param array  $headers
     *
     * @return \Leevel\Http\Response
     */
    public function viewSuccess(string $message, string $url = '', int $time = 1, int $status = 200, array $headers = []): Response
    {
        $vars = [
            'message' => $message,
            'url'     => $url,
            'time'    => $time,
        ];

        return $this->view($this->viewSuccessTemplate, $vars, null, $status, $headers);
    }

    /**
     * 返回视图失败消息.
     *
     * @param string $message
     * @param string $url
     * @param int    $time
     * @param int    $status
     * @param array  $headers
     *
     * @return \Leevel\Http\Response
     */
    public function viewFail(string $message, string $url = '', int $time = 3, int $status = 404, array $headers = []): Response
    {
        $vars = [
            'message' => $message,
            'url'     => $url,
            'time'    => $time,
        ];

        return $this->view($this->viewFailTemplate, $vars, null, $status, $headers);
    }

    /**
     * 返回 JSON 响应.
     *
     * @param mixed $data
     * @param int   $status
     * @param array $headers
     * @param bool  $json
     *
     * @return \Leevel\Http\JsonResponse
     */
    public function json($data = null, int $status = 200, array $headers = [], bool $json = false): JsonResponse
    {
        return new JsonResponse($data, $status, $headers, $json);
    }

    /**
     * 返回 JSONP 响应.
     *
     * @param string $callback
     * @param mixed  $data
     * @param int    $status
     * @param array  $headers
     * @param bool   $json
     *
     * @return \Leevel\Http\JsonResponse
     */
    public function jsonp(string $callback, $data = null, int $status = 200, array $headers = [], bool $json = false): JsonResponse
    {
        return $this->json($data, $status, $headers, $json)->
        setCallback($callback);
    }

    /**
     * 返回下载响应.
     *
     * @param \SplFileInfo|\SplFileObject|string $file
     * @param string                             $name
     * @param int                                $status
     * @param array                              $headers
     * @param bool                               $autoEtag
     * @param bool                               $autoLastModified
     *
     * @return \Leevel\Http\FileResponse
     */
    public function download($file, string $name = null, int $status = 200, array $headers = [], bool $autoEtag = false, bool $autoLastModified = true): FileResponse
    {
        $response = new FileResponse($file, $status, $headers, ResponseHeaderBag::DISPOSITION_ATTACHMENT, $autoEtag, $autoLastModified);

        if (null !== $name) {
            return $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $name);
        }

        return $response;
    }

    /**
     * 返回文件响应.
     *
     * @param \SplFileInfo|\SplFileObject|string $file
     * @param int                                $status
     * @param array                              $headers
     * @param bool                               $autoEtag
     * @param bool                               $autoLastModified
     *
     * @return \Leevel\Http\FileResponse
     */
    public function file($file, int $status = 200, array $headers = [], bool $autoEtag = false, bool $autoLastModified = true): FileResponse
    {
        return new FileResponse($file, $status, $headers, ResponseHeaderBag::DISPOSITION_INLINE, $autoEtag, $autoLastModified);
    }

    /**
     * 返回一个 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 redirect(string $url, array $params = [], string $subdomain = 'www', $suffix = null, int $status = 302, array $headers = []): RedirectResponse
    {
        return $this->redirector->url($url, $params, $subdomain, $suffix, $status, $headers);
    }

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

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

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

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

    /**
     * 无内容
     * 服务器成功处理，但未返回内容: 204.
     *
     * @return \Leevel\Http\ApiResponse
     */
    public function apiNoContent(): ApiResponse
    {
        return $this->createApiResponse()->noContent();
    }

    /**
     * 错误请求
     *
     * @param string $message
     * @param int    $statusCode
     * @param string $text
     *
     * @return \Leevel\Http\ApiResponse
     */
    public function apiError(string $message, int $statusCode, ?string $text = null): ApiResponse
    {
        return $this->createApiResponse()->error($message, $statusCode, $text);
    }

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

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

    /**
     * 禁止
     * 服务器拒绝请求: 403.
     *
     * @param string $message
     * @param string $text
     *
     * @return \Leevel\Http\ApiResponse
     */
    public function apiForbidden(?string $message = null, ?string $text = null): ApiResponse
    {
        return $this->createApiResponse()->forbidden($message, $text);
    }

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

    /**
     * 方法禁用
     * 禁用请求中指定的方法: 405.
     *
     * @param string $message
     * @param string $text
     *
     * @return \Leevel\Http\ApiResponse
     */
    public function apiMethodNotAllowed(?string $message = null, ?string $text = null): ApiResponse
    {
        return $this->createApiResponse()->methodNotAllowed($message, $text);
    }

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

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

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

    /**
     * 设置视图正确模板
     *
     * @param string $template
     *
     * @return $this
     */
    public function setViewSuccessTemplate(string $template): IResponseFactory
    {
        $this->viewSuccessTemplate = $template;

        return $this;
    }

    /**
     * 设置视图错误模板
     *
     * @param string $template
     *
     * @return $this
     */
    public function setViewFailTemplate(string $template): IResponseFactory
    {
        $this->viewFailTemplate = $template;

        return $this;
    }

    /**
     * 创建基础 API 响应.
     *
     * @return \Leevel\Http\ApiResponse
     */
    protected function createApiResponse(): ApiResponse
    {
        return new ApiResponse();
    }
}