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

/**
 * 编译器列表.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.18
 *
 * @version 1.0
 */
class Compiler implements ICompiler
{
    /**
     * code 支持的特殊别名映射.
     *
     * @var array
     */
    protected $codeMap = [
        'php'      => '~',
        'note'     => '#',
        'variable' => '$',
        'foreach'  => 'list', // foreach 和 for 冲突，foreach 改为 list
        'echo'     => ':',
        'endtag'   => [
            '/list',
            '/for',
            '/while',
            '/if',
            '/script',
            '/style',
        ],
    ];

    /**
     * node 支持的特殊别名映射.
     *
     * @var array
     */
    protected $nodeMap = [];

    /**
     * js 风格支持的特殊别名映射.
     *
     * @var array
     */
    protected $jsMap = [];

    /**
     * js 风格标签.
     *
     * @var array
     */
    protected $jsTag = [
        // required 属性不能为空，single 单标签
        'if' => [
            'attr' => [
                'condition1',
            ],
            'single'   => false,
            'required' => [
                'condition1',
            ],
        ],
        'elseif' => [
            'attr' => [
                'condition1',
            ],
            'single'   => true,
            'required' => [
                'condition1',
            ],
        ],
        'else' => [
            'attr'     => [],
            'single'   => true,
            'required' => [],
        ],
        'let' => [
            'attr' => [
                'condition1',
            ],
            'single'   => true,
            'required' => [
                'condition1',
            ],
        ],
        'for' => [
            'attr' => [
                'condition1',
                'condition2',
                'condition3',
            ],
            'single'   => false,
            'required' => [
                'condition1',
                'condition2',
                'condition3',
            ],
        ],
    ];

    /**
     * Node 标签.
     *
     * @var array
     */
    protected $nodeTag = [
        // required 属性不能为空，single 单标签
        'assign' => [
            'attr' => [
                'name',
                'value',
            ],
            'single'   => true,
            'required' => [
                'name',
            ],
        ],
        'if' => [
            'attr' => [
                'condition',
            ],
            'single'   => false,
            'required' => [
                'condition',
            ],
        ],
        'elseif' => [
            'attr' => [
                'condition',
            ],
            'single'   => true,
            'required' => [
                'condition',
            ],
        ],
        'else' => [
            'attr'     => [],
            'single'   => true,
            'required' => [],
        ],
        'list' => [
            'attr' => [
                'for',
                'key',
                'value',
                'index',
            ],
            'single'   => false,
            'required' => [
                'for',
            ],
        ],
        'lists' => [
            'attr' => [
                'index',
                'key',
                'mod',
                'empty',
                'length',
                'offset',
                'name',
                'id',
            ],
            'single'   => false,
            'required' => [
                'name',
            ],
        ],
        'include' => [
            'attr' => [
                'file',
                'ext',
            ],
            'single'   => true,
            'required' => [
                'file',
            ],
        ],
        'for' => [
            'attr' => [
                'step',
                'start',
                'end',
                'var',
                'type',
            ],
            'single'   => false,
            'required' => [],
        ],
        'while' => [
            'attr' => [
                'condition',
            ],
            'single'   => false,
            'required' => [
                'condition',
            ],
        ],
        'break' => [
            'attr'     => [],
            'single'   => true,
            'required' => [],
        ],
        'continue' => [
            'attr'     => [],
            'single'   => true,
            'required' => [],
        ],
        'php' => [
            'attr'     => [],
            'single'   => false,
            'required' => [],
        ],
    ];

    /**
     * 构造函数.
     */
    public function __construct()
    {
    }

    /**
     * 获取编译器.
     *
     * @return array
     */
    public function getCompilers(): array
    {
        $methods = get_class_methods($this);
        $compilers = [];

        foreach ($methods as $method) {
            if ('Compiler' !== substr($method, -8)) {
                continue;
            }

            $method = substr($method, 0, -8);

            if (!in_array($method, [
                'global',
                'jsvar',
                'globalrevert',
                'revert',
            ], true)) {
                $type = strtolower(substr($method, -4));
                $tag = substr($method, 0, -4);

                if ('code' === $type) {
                    $name = $this->codeMap[$tag] ?? $tag;
                } elseif ('node' === $type) {
                    $name = $this->nodeMap[$tag] ?? $tag;
                } else {
                    $type = strtolower(substr($method, -2));
                    $tag = substr($method, 0, -2);
                    $name = $this->jsMap[$tag] ?? $tag;
                }

                $compilers[] = [
                    $type,
                    $name,
                    $tag,
                ];
            }
        }

        unset($methods);

        return $compilers;
    }

    /**
     * node.tag.
     *
     * @return array
     */
    public function getNodeTagHelp(): array
    {
        return $this->nodeTag;
    }

    /**
     * js.tag.
     *
     * @return array
     */
    public function getJsTagHelp(): array
    {
        return $this->jsTag;
    }

    /**
     * 全局编译器（保护标签）.
     *
     * @param array $theme
     */
    public function globalCompiler(array &$theme): void
    {
        $theme['content'] = $this->encodeContent($theme['content'], 'global');
    }

    /**
     * 全局还原编译器（保护标签还原）.
     *
     * @param array $theme
     */
    public function globalrevertCompiler(array &$theme): void
    {
        $theme['content'] = $this->encodeContent($theme['content'], 'revert');
    }

    /**
     * node.code 还原编译器.
     *
     * @param array $theme
     */
    public function revertCompiler(array &$theme): void
    {
        $theme['content'] = $this->encodeContent($theme['content'], 'revert');
    }

    /**
     * 变量.
     *
     * @param array $theme
     */
    public function variableCodeCompiler(array &$theme): void
    {
        $theme['content'] = !empty($theme['content']) ?
            $this->parseContent($theme['content']) : null;

        if (null !== $theme['content']) {
            $theme['content'] = $this->withPhpTag('echo '.$theme['content'].';');
        }

        $theme['content'] = $this->encodeContent($theme['content']);
    }

    /**
     * if.
     *
     * @param array $theme
     */
    public function ifCodeCompiler(array &$theme): void
    {
        $theme['content'] = $this->parseContentIf($theme['content'], '');
        $theme['content'] = $this->encodeContent(
            $this->withPhpTag($theme['content'].':')
        );
    }

    /**
     * elseif.
     *
     * @param array $theme
     */
    public function elseifCodeCompiler(array &$theme): void
    {
        $theme['content'] = $this->parseContentIf($theme['content'], 'else');
        $theme['content'] = $this->encodeContent(
            $this->withPhpTag($theme['content'].':')
        );
    }

    /**
     * else 标签.
     *
     * @param array $theme
     */
    public function elseCodeCompiler(array &$theme): void
    {
        $theme['content'] = $this->encodeContent(
            $this->withPhpTag('else:')
        );
    }

    /**
     * foreach.
     *
     * @param array $theme
     */
    public function foreachCodeCompiler(array &$theme): void
    {
        $theme['content'] = call_user_func(
            function ($content) {
                preg_match_all('/\\$([\S]+)/', $content, $matches);

                $matches = $matches[1];
                $num = count($matches);

                if ($num > 0) {
                    if (2 === $num) {
                        $result = "\${$matches[1]}";
                    } elseif (3 === $num) {
                        $result = "\${$matches[1]} => \${$matches[2]}";
                    } else {
                        throw new InvalidArgumentException('The parameter of code.foreach tag can be at most three.');
                    }

                    return "if (is_array(\${$matches[0]})): foreach(\${$matches[0]} as {$result})";
                }
            },
            $theme['content']
        );

        $theme['content'] = $this->encodeContent(
            $this->withPhpTag($theme['content'].':')
        );
    }

    /**
     * for.
     *
     * @param array $theme
     */
    public function forCodeCompiler(array &$theme): void
    {
        $theme['content'] = $this->encodeContent(
            $this->withPhpTag('for ('.$theme['content'].'):')
        );
    }

    /**
     * while 头部.
     *
     * @param array $theme
     */
    public function whileCodeCompiler(array &$theme): void
    {
        $theme['content'] = $this->encodeContent(
            $this->withPhpTag('while ('.$theme['content'].'):')
        );
    }

    /**
     * php 脚本.
     *
     * @param array $theme
     */
    public function phpCodeCompiler(array &$theme): void
    {
        $theme['content'] = $this->encodeContent(
            $this->withPhpTag($theme['content'].';')
        );
    }

    /**
     * 注释.
     *
     * @param array $theme
     */
    public function noteCodeCompiler(array &$theme): void
    {
        $theme['content'] = $this->encodeContent(' ');
    }

    /**
     * PHP echo 标签.
     *
     * @param array $theme
     */
    public function echoCodeCompiler(array &$theme): void
    {
        $theme['content'] = $this->encodeContent(
            $this->withPhpTag('echo '.$theme['content'].';')
        );
    }

    /**
     * javascript 初始标签.
     *
     * @param array $theme
     */
    public function scriptCodeCompiler(array &$theme): void
    {
        $theme['content'] = $this->encodeContent(
            '<script type="text/javascript">'
        );
    }

    /**
     * css 初始标签.
     *
     * @param array $theme
     */
    public function styleCodeCompiler(array &$theme): void
    {
        $theme['content'] = $this->encodeContent(
            '<style type="text/css">'
        );
    }

    /**
     * endtag.
     *
     * @param array $theme
     */
    public function endtagCodeCompiler(array &$theme): void
    {
        $theme['content'] = substr(
            $theme['source'],
            strpos($theme['source'], '/'),
            strripos($theme['source'], '}') - 1
        );

        $theme['content'] = call_user_func(
            function ($content) {
                $content = ltrim(trim($content), '/');

                switch ($content) {
                    case 'list':
                        $content = $this->withPhpTag('endforeach; endif;');

                        break;
                    case 'for':
                        $content = $this->withPhpTag('endfor;');

                        break;
                    case 'while':
                        $content = $this->withPhpTag('endwhile;');

                        break;
                    case 'if':
                        $content = $this->withPhpTag('endif;');

                        break;
                    case 'script':
                        $content = '</script>';

                        break;
                    case 'style':
                        $content = '</style>';

                        break;
                }

                return $content;
            }, $theme['content']
        );

        $theme['content'] = $this->encodeContent($theme['content']);
    }

    /**
     * 变量及表达式.
     *
     * @param array $theme
     */
    public function jsvarCompiler(array &$theme): void
    {
        $theme['content'] = $this->withPhpTag(
            'echo '.$this->parseJcontent($theme['content']).';'
        );
    }

    /**
     * let 编译器.
     *
     * @param array $theme
     */
    public function letJsCompiler(array &$theme): void
    {
        $this->checkNode($theme, true);
        $attr = $this->getNodeAttribute($theme);

        $name = array_shift($attr);
        $equal = array_shift($attr);

        if ('=' !== $equal) {
            array_unshift($attr, $equal);
        }

        if (!$attr) {
            $value = 'null';
        } else {
            $value = $this->parseExpression(implode(' ', $attr));

            if ('' === $value) {
                $value = 'null';
            }
        }

        $theme['content'] = $this->withPhpTag("\${$name} = ".$value.';');
    }

    /**
     * if 编译器.
     *
     * @param array $theme
     */
    public function ifJsCompiler(array &$theme): void
    {
        $this->checkNode($theme, true);
        $attr = $this->getNodeAttribute($theme);

        $attr = $this->parseExpression(implode(' ', $attr));

        $theme['content'] = $this->withPhpTag("if ({$attr}):");
        $theme['content'] .= $this->getNodeBody($theme);
        $theme['content'] .= $this->withPhpTag('endif;');
    }

    /**
     * elseif 编译器.
     *
     * @param array $theme
     */
    public function elseifJsCompiler(array &$theme): void
    {
        $this->checkNode($theme, true);
        $attr = $this->getNodeAttribute($theme);

        $attr = $this->parseExpression(implode(' ', $attr));

        $theme['content'] = $this->withPhpTag("elseif ({$attr}):");
        $theme['content'] .= $this->getNodeBody($theme);
    }

    /**
     * else.
     *
     * @param array $theme
     */
    public function elseJsCompiler(array &$theme): void
    {
        $theme['content'] = $this->withPhpTag('else:');
    }

    /**
     * for 循环.
     *
     * @param array $theme
     */
    public function forJsCompiler(array &$theme): void
    {
        $this->checkNode($theme, true);
        $attr = $this->getNodeAttribute($theme);
        $attr = array_values($attr);

        if (!in_array('in', $attr, true)) {
            throw new InvalidArgumentException('For tag need “in“ separate.');
        }

        $key = 'key';
        $value = array_shift($attr);

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

        $next = array_shift($attr);

        if ('in' !== $next) {
            $key = $value;
            $value = $next;
            array_shift($attr);
        }

        $attr = $this->parseExpression(implode(' ', $attr));

        $theme['content'] = $this->withPhpTag("foreach ({$attr} as \${$key} => \${$value}):");
        $theme['content'] .= $this->getNodeBody($theme);
        $theme['content'] .= $this->withPhpTag('endforeach;');
    }

    /**
     * assign.
     *
     * @param array $theme
     */
    public function assignNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $attr = $this->getNodeAttribute($theme);

        $attr['name'] = $this->parseContent($attr['name'], false);

        if (null === $attr['value']) {
            $attr['value'] = 'null';
        } else {
            if ('$' === substr($attr['value'], 0, 1)) {
                $attr['value'] = $this->parseContent(substr($attr['value'], 1));
            } else {
                $attr['value'] = '\''.$attr['value'].'\'';
            }
        }

        // 编译
        $theme['content'] = $this->withPhpTag(
            $attr['name'].' = '.$attr['value'].';'
        );
    }

    /**
     * if.
     *
     * @param array $theme
     */
    public function ifNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $attr = $this->getNodeAttribute($theme);

        $attr['condition'] = $this->parseContentIf($attr['condition']);
        $theme['content'] = $this->withPhpTag('if ('.$attr['condition'].'):').
            $this->getNodeBody($theme).
            $this->withPhpTag('endif;');
    }

    /**
     * elseif.
     *
     * @param array $theme
     */
    public function elseifNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $attr = $this->getNodeAttribute($theme);

        $attr['condition'] = $this->parseContentIf($attr['condition']);
        $theme['content'] = $this->withPhpTag('elseif ('.$attr['condition'].'):');
    }

    /**
     * else.
     *
     * @param array $theme
     */
    public function elseNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $theme['content'] = $this->withPhpTag('else:');
    }

    /**
     * foreach list.
     *
     * @param array $theme
     */
    public function listNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $attr = $this->getNodeAttribute($theme);

        foreach ([
            'key',
            'value',
            'index',
        ] as $key) {
            null === $attr[$key] && $attr[$key] = '$'.$key;
        }

        foreach ([
            'for',
            'key',
            'value',
            'index',
        ] as $key) {
            if ('$'.$key === $attr[$key]) {
                continue;
            }

            $attr[$key] = $this->parseContent($attr[$key]);
        }

        // 编译
        $theme['content'] = $this->withPhpTag($attr['index'].' = 1;').PHP_EOL.
            $this->withPhpTag(
                'if (is_array('.$attr['for'].')): foreach ('.$attr['for'].
                ' as '.$attr['key'].' => '.
                $attr['value'].'):'
            ).
            $this->getNodeBody($theme).
            $this->withPhpTag($attr['index'].'++;').PHP_EOL.
            $this->withPhpTag('endforeach; endif;');
    }

    /**
     * lists 增强版.
     *
     * @param array $theme
     */
    public function listsNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $attr = $this->getNodeAttribute($theme);

        null === $attr['index'] && $attr['index'] = 'index';
        null === $attr['key'] && $attr['key'] = 'key';
        null === $attr['id'] && $attr['id'] = 'id';
        null === $attr['mod'] && $attr['mod'] = 2;

        if (preg_match('/[^\\d\-.,]/', (string) ($attr['mod']))) {
            $attr['mod'] = '$'.$attr['mod'];
        }

        null === $attr['empty'] && $attr['empty'] = '';
        null === $attr['length'] && $attr['length'] = '';
        null === $attr['offset'] && $attr['offset'] = '';
        $attr['name'] = $this->parseContent($attr['name']);

        $compiled = [];

        $tmp = 'if (is_array('.
            $attr['name'].')):'.PHP_EOL.'    $'.
            $attr['index'].' = 0;'.PHP_EOL;

        if ('' !== $attr['length']) {
            $tmp .= '    $tmp = array_slice('.
                $attr['name'].', '.
                $attr['offset'].', '.
                $attr['length'].');';
        } elseif ('' !== $attr['offset']) {
            $tmp .= '    $tmp = array_slice('.
                $attr['name'].', '.
                $attr['offset'].');';
        } else {
            $tmp .= '    $tmp = '.$attr['name'].';';
        }

        $tmp .= PHP_EOL.'    if (0 === count($tmp)):'.PHP_EOL.
            '        echo "'.$attr['empty'].'";';
        $tmp .= PHP_EOL.'    else:';
        $tmp .= PHP_EOL.'        foreach ($tmp as $'.$attr['key'].
            ' => $'.$attr['id'].'):';
        $tmp .= PHP_EOL.'            ++$'.$attr['index'].';'.PHP_EOL;
        $tmp .= '            '.'$mod = $'.$attr['index'].' % '.
            $attr['mod'].';';

        $compiled[] = $this->withPhpTag($tmp);
        $compiled[] = $this->getNodeBody($theme);
        $compiled[] = '        '.$this->withPhpTag(
            'endforeach;'.PHP_EOL.'    endif;'.
            PHP_EOL.'else:'.PHP_EOL.'    echo "'.
            $attr['empty'].'";'.PHP_EOL.'endif;'
        );

        $theme['content'] = implode('', $compiled);
    }

    /**
     * include.
     *
     * @param array $theme
     */
    public function includeNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $attr = $this->getNodeAttribute($theme);

        if (false === strpos($attr['file'], '(')) {
            // 后缀由主模板提供
            if (!$attr['ext'] && false !== strpos($attr['file'], '.')) {
                $temp = explode('.', $attr['file']);
                $attr['ext'] = '.'.array_pop($temp);
                $attr['file'] = implode('.', $temp);
            }

            if (0 !== strpos($attr['file'], '$')) {
                $attr['file'] = (0 === strpos($attr['file'], '$') ? '' : '\'').$attr['file'].'\'';
            }
        }

        $attr['file'] = $this->parseContentIf($attr['file']);

        $theme['content'] = $this->withPhpTag(
            '$this->display('.$attr['file'].
            ', [], \''.($attr['ext'] ?: '').
            '\', true);'
        );
    }

    /**
     * for.
     *
     * @param array $theme
     */
    public function forNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $attr = $this->getNodeAttribute($theme);

        null === $attr['step'] && $attr['step'] = '1';
        null === $attr['start'] && $attr['start'] = '0';
        null === $attr['end'] && $attr['end'] = '0';
        null === $attr['var'] && $attr['var'] = 'var';
        $attr['var'] = '$'.$attr['var'];

        if ('-' === $attr['type']) {
            $comparison = ' >= ';
            $minusPlus = ' -= ';
        } else {
            $comparison = ' <= ';
            $minusPlus = ' += ';
        }

        $compiled = [];
        $compiled[] = $this->withPhpTag(
            'for ('.$attr['var'].
            ' = '.$attr['start'].'; '.
            $attr['var'].$comparison.$attr['end'].'; '.
            $attr['var'].$minusPlus.$attr['step'].'):'
        );
        $compiled[] = $this->getNodeBody($theme);
        $compiled[] = $this->withPhpTag('endfor;');

        $theme['content'] = implode('', $compiled);
    }

    /**
     * while.
     *
     * @param array $theme
     */
    public function whileNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $attr = $this->getNodeAttribute($theme);

        $theme['content'] = $this->withPhpTag('while('.$attr['condition'].'):').
            $this->getNodeBody($theme).
            $this->withPhpTag('endwhile;');
    }

    /**
     * break.
     *
     * @param array $theme
     */
    public function breakNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $theme['content'] = $this->withPhpTag('break;');
    }

    /**
     * continue.
     *
     * @param array $theme
     */
    public function continueNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $theme['content'] = $this->withPhpTag('continue;');
    }

    /**
     * php.
     *
     * @param array $theme
     */
    public function phpNodeCompiler(array &$theme): void
    {
        $this->checkNode($theme);
        $theme['content'] = $this->withPhpTag($this->getNodeBody($theme));
    }

    /**
     * 属性编译.
     *
     * @param array $theme
     */
    public function attributeNodeCompiler(array &$theme): void
    {
        $source = trim($theme['content']);
        $source = $this->escapeRegexCharacter($source);

        if (true === $theme['is_js']) {
            $tag = $this->jsTag;
        } else {
            $tag = $this->nodeTag;
        }

        $allowedAttr = $tag[$theme['parent_name']]['attr'];

        // 正则匹配
        $regexp = [];

        if (!$theme['is_js']) {
            // xxx="yyy" 或 "yyy" 格式
            $regexp[] = '/(([^=\\s]+)=)?"([^"]+)"/';

            // xxx='yyy' 或 'yyy' 格式
            $regexp[] = "/(([^=\\s]+)=)?'([^\\']+)'/";
        }

        // xxx=yyy 或 yyy 格式
        $regexp[] = '/(([^=\\s]+)=)?([^\\s]+)/';

        $nameIdx = 2;
        $valueIdx = 3;
        $defaultIdx = 0;

        foreach ($regexp as $item) {
            if (preg_match_all($item, $source, $res)) {
                foreach ($res[0] as $idx => $attribute) {
                    $source = str_replace($attribute, '', $source);
                    $name = $res[$nameIdx][$idx];

                    if (empty($name)) {
                        $defaultIdx++;
                        $name = 'condition'.$defaultIdx;
                    }

                    $value = $res[$valueIdx][$idx];
                    $value = $this->escapeRegexCharacter($value, false);
                    $theme['attribute_list'][strtolower($name)] = $value;
                }
            }
        }

        // 补全节点其余参数
        foreach ($allowedAttr as $item) {
            if (!isset($theme['attribute_list'][$item])) {
                $theme['attribute_list'][$item] = null;
            }
        }

        $theme['content'] = $source;
    }

    /**
     * 分析if.
     *
     * @param string $content
     * @param string $type
     *
     * @return string
     */
    protected function parseContentIf(string $content, ?string $type = null): string
    {
        $param = [];

        foreach (explode(' ', $content) as $value) {
            if (strpos($value, '.') > 0) {
                $args = explode('.', $value);
                $param[] = $args[0].($this->arrayHandler($args, true, 1));
            } else {
                $param[] = $value;
            }
        }

        $result = implode(' ', $param);

        if (null === $type) {
            return $result;
        }

        return $type."if ({$result})";
    }

    /**
     * 解析 JS 变量内容.
     *
     * @param string $content
     *
     * @return string
     */
    protected function parseJcontent(string $content): string
    {
        $var = explode('|', $content);
        $content = array_shift($var);

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

        if (count($var) > 0) {
            return $this->parseJsFunction($content, $var);
        }

        return $content;
    }

    /**
     * 解析表达式语法.
     *
     * @param string $content
     *
     * @return string
     */
    protected function parseExpression(string $content): string
    {
        $content = trim($content);

        $logic = [
            '+',
            '-',
            '.',
            '(',
            ')',
            '/',
            '%',
            '*',
            '?',
            ':',
            '<',
            '>',
            '=',
            '|',
            '&',
            '~',
            '!',
        ];

        $result = [];

        // 单逻辑
        $findLogic = false;

        for ($i = 0; $i < strlen($content); $i++) {
            $temp = $content[$i];

            if (0 === $i && $this->isVarExpression($temp)) {
                $result[] = '$';
            }

            if (in_array($temp, $logic, true)) {
                // . 语法作为对象连接符
                if ('.' === $temp &&
                    isset($content[$i + 1]) &&
                    $this->isVarExpression($content[$i + 1]) &&
                    !in_array($content[$i + 1], [' ', '$'], true)) {
                    $result[] = '->';
                    $findLogic = false;
                }

                // -> 语法原生对象连接符
                elseif ('>' === $temp &&
                    $i > 0 &&
                    '-' === $content[$i - 1] &&
                    isset($content[$i + 1]) &&
                    !in_array($content[$i + 1], [' ', '$'], true)) {
                    $result[] = '>';
                    $findLogic = false;
                } else {
                    $findLogic = true;
                    $result[] = $temp;
                }

                continue;
            }

            if (true === $findLogic && ' ' !== $temp) {
                if ($this->isVarExpression($temp)) {
                    $result[] = '$';
                }

                $findLogic = false;
            }

            $result[] = $temp;
        }

        $content = implode('', $result);

        // 还原函数去掉开头的美元符号
        $content = preg_replace_callback('/(\$+?[a-z0-9\\_]+?\\s*?)\\(.+?\\)/',
            function ($match) {
                return substr($match[0], 1);
            },
            $content
        );

        return $content;
    }

    /**
     * 是否为字符串表达式字符.
     *
     * @param string $char
     *
     * @return bool
     */
    protected function isVarExpression(string $char): bool
    {
        return !in_array($char, [
            '"',
            '\'',
            '(',
        ], true) && !is_numeric($char);
    }

    /**
     * 解析 JS 风格函数.
     *
     * @param string $name
     * @param array  $var
     *
     * @return string
     */
    protected function parseJsFunction(string $name, array $var): string
    {
        return $this->parseVarFunction($name, $var, true);
    }

    /**
     * 解析变量内容.
     *
     * @param string $content
     * @param bool   $isFunc
     *
     * @return string
     */
    protected function parseContent(string $content, bool $isFunc = true): string
    {
        // 以|分割字符串,数组第一位是变量名字符串,之后的都是函数参数&&函数{$hello|md5}
        $var = explode('|', $content);

        // 弹出第一个元素,也就是变量名
        $tmp = array_shift($var);

        // 访问数组元素或者属性
        if (strpos($tmp, '.')) {
            $vars = explode('.', $tmp);

            // 这里 . 作为字符连接符
            if ("'" === ($firstLetter = substr($vars[1], 0, 1)) or
                '"' === $firstLetter or
                '$' === $firstLetter) {
                $name = '$'.$vars[0].'.'.$vars[1].($this->arrayHandler($vars, false));
            } else {
                $name = '$'.$vars[0].'->'.$vars[1].($this->arrayHandler($vars, true));
            }

            $tmp = $vars[0];
        }

        // $hello['demo'] 方式访问数组
        elseif (strpos($tmp, '[')) {
            $name = '$'.$tmp;
            preg_match('/(.+?)\[(.+?)\]/is', $tmp, $matches);
            $tmp = $matches[1];
        } else {
            $name = "\$${tmp}";
        }

        // 如果有使用函数
        if (true === $isFunc && count($var) > 0) {
            // 传入变量名,和函数参数继续解析,这里的变量名是上面的判断设置的值
            $name = $this->parseVarFunction($name, $var);
        }

        $name = str_replace('^', ':', $name);

        return $name ?: '';
    }

    /**
     * 解析函数.
     *
     * @param string $name
     * @param array  $var
     * @param bool   $isJavascript
     *
     * @return string
     */
    protected function parseVarFunction(string $name, array $var, bool $isJavascript = false): string
    {
        $len = count($var);

        for ($index = 0; $index < $len; $index++) {
            if (0 === stripos($var[$index], 'default=')) {
                $args = explode('=', $var[$index], 2);
            } else {
                $args = explode('=', $var[$index]);
            }

            $args[0] = trim($args[0]);

            if (false === $isJavascript && isset($args[1])) {
                $args[1] = str_replace('->', ':', $args[1]);
            }

            switch (strtolower($args[0])) {
                // 特殊模板函数
                case 'default':
                    $name = $name.' ?: '.$args[1];

                    break;
                // 通用模板函数
                default:
                    if (isset($args[1])) {
                        if (strstr($args[1], '**')) {
                            $args[1] = str_replace('**', $name, $args[1]);
                            $name = "{$args[0]}({$args[1]})";
                        } else {
                            $name = "{$args[0]}(${name}, {$args[1]})";
                        }
                    } elseif (!empty($args[0])) {
                        $name = "{$args[0]}(${name})";
                    }
            }
        }

        return $name;
    }

    /**
     * 数组格式.
     *
     * @param array $vars
     * @param bool  $forObj
     * @param int   $start
     *
     * @return string
     */
    protected function arrayHandler(array &$vars, bool $forObj = true, int $start = 2): string
    {
        $len = count($vars);
        $param = '';

        for ($index = $start; $index < $len; $index++) {
            if (true === $forObj) {
                // 类似 $hello->test1->test2
                $param .= "->{$vars[$index]}";
            } else {
                // 类似 $hello.test1.test2
                $param .= ".{$vars[$index]}";
            }
        }

        return $param;
    }

    /**
     * 编码内容.
     *
     * @param string $content
     * @param string $type
     *
     * @return string
     */
    protected function encodeContent(string $content, string $type = ''): string
    {
        if ('global' === $type) {
            $content = Parser::globalEncode($content);
        } elseif (in_array($type, ['revert', 'include'], true)) {
            $content = base64_decode($content, true);
        } else {
            $content = Parser::revertEncode($content);
        }

        return $content;
    }

    /**
     * 验证节点是否正确.
     *
     * @param array $theme
     * @param bool  $jsNode
     *
     * @return bool
     */
    protected function checkNode(array $theme, bool $jsNode = false): bool
    {
        $attribute = $theme['children'][0];

        // 验证标签的属性值
        if (true !== $attribute['is_attribute']) {
            throw new InvalidArgumentException('Tag attribute type validation failed.');
        }

        // 验证必要属性
        $tag = true === $jsNode ? $this->jsTag : $this->nodeTag;

        if (!isset($tag[$theme['name']])) {
            throw new InvalidArgumentException(
                sprintf('The tag %s is undefined.', $theme['name'])
            );
        }

        foreach ($tag[$theme['name']]['required'] as $name) {
            $name = strtolower($name);

            if (!isset($attribute['attribute_list'][$name])) {
                throw new InvalidArgumentException(sprintf(
                    'The node %s lacks the required property: %s.', $theme['name'], $name)
                );
            }
        }

        return true;
    }

    /**
     * 取得节点的属性列表.
     *
     * @param array $theme
     *
     * @return array
     */
    protected function getNodeAttribute(array $theme): array
    {
        foreach ($theme['children'] as $child) {
            if (isset($child['is_attribute']) && true === $child['is_attribute']) {
                return $child['attribute_list'];
            }
        }

        return [];
    }

    /**
     * 取得body编译内容.
     *
     * @param array $theme
     *
     * @return string|void
     */
    protected function getNodeBody(array $theme)
    {
        foreach ($theme['children'] as $child) {
            if (isset($child['is_body']) && true === $child['is_body']) {
                return $child['content'];
            }
        }
    }

    /**
     * 正则属性转义.
     *
     * @param string $txt
     * @param bool   $esc
     *
     * @return string
     */
    protected function escapeRegexCharacter(string $txt, bool $esc = true): string
    {
        $txt = $this->escapeCharacter($txt, $esc);

        if (!$esc) {
            $txt = str_replace([
                ' band ',
                ' bxor ',
                ' bor ',
                ' bnot ',
                ' bleft ',
                ' bright ',
                ' and ',
                ' or ',
                ' not ',
                ' dot ',
                ' nheq ',
                ' heq ',
                ' neq ',
                ' eq ',
                ' egt ',
                ' gt ',
                ' elt ',
                ' lt ',
            ], [
                ' & ',
                ' ^ ',
                ' | ',
                ' ~ ',
                ' << ',
                ' >> ',
                ' && ',
                ' || ',
                ' != ',
                '->',
                ' !== ',
                ' === ',
                ' != ',
                ' == ',
                ' >= ',
                ' > ',
                ' <= ',
                ' < ',
            ], $txt);
        }

        return $txt;
    }

    /**
     * 正则属性转义.
     *
     * @param string $txt
     * @param bool   $esc
     *
     * @return string
     */
    protected function escapeCharacter(string $txt, bool $esc = true): string
    {
        if ('""' === $txt) {
            $txt = '';
        }

        if ($esc) {
            // 转义
            $txt = str_replace([
                '\\\\',
                "\\'",
                '\\"',
                '\\$',
                '\\.',
            ], [
                '\\',
                '~~{#!`!#}~~',
                '~~{#!``!#}~~',
                '~~{#!S!#}~~',
                '~~{#!dot!#}~~',
            ], $txt);
        } else {
            // 还原
            $txt = str_replace([
                '~~{#!`!#}~~',
                '~~{#!``!#}~~',
                '~~{#!S!#}~~',
                '~~{#!dot!#}~~',
            ], [
                "'",
                '"',
                '$',
                '.',
            ], $txt);
        }

        return $txt;
    }

    /**
     * PHP 标签包裹内容.
     *
     * @param array $content
     *
     * @return string
     */
    protected function withPhpTag(string $content): string
    {
        return $this->phpTagStart().$content.$this->phpTagEnd();
    }

    /**
     * PHP 开始标签.
     *
     * @return string
     */
    protected function phpTagStart(): string
    {
        return '<?php ';
    }

    /**
     * PHP 结束标签.
     *
     * @return string
     */
    protected function phpTagEnd(): string
    {
        return ' ?>';
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Auth;

use Leevel\Manager\Manager as Managers;

/**
 * manager 入口.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.09.07
 *
 * @version 1.0
 */
class Manager extends Managers
{
    /**
     * 返回默认驱动.
     *
     * @return string
     */
    public function getDefaultDriver(): string
    {
        $option = $this->getContainerOption('default');

        return $this->getContainerOption($option.'_default');
    }

    /**
     * 设置默认驱动.
     *
     * @param string $name
     */
    public function setDefaultDriver(string $name): void
    {
        $option = $this->getContainerOption('default');

        $this->setContainerOption($option.'_default', $name);
    }

    /**
     * 取得配置命名空间.
     *
     * @return string
     */
    protected function normalizeOptionNamespace(): string
    {
        return 'auth';
    }

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

    /**
     * 创建 session 连接.
     *
     * @param array $options
     *
     * @return \Leevel\Auth\Session
     */
    protected function makeConnectSession(array $options = []): Session
    {
        $options = array_merge(
            $this->normalizeConnectOption('session', $options)
        );

        return new Session($this->container['session'], $options);
    }

    /**
     * 创建 token 连接.
     *
     * @param array $options
     *
     * @return \Leevel\Auth\Token
     */
    protected function makeConnectToken(array $options = []): Token
    {
        $options = array_merge(
            $this->normalizeConnectOption('token', $options)
        );

        return new Token($this->container['cache'], $this->container['request'], $options);
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Kernel;

use Composer\Autoload\ClassLoader;
use Leevel\Di\Container;
use Leevel\Di\Provider;
use Leevel\Event\Provider\Register as EventProvider;
use Leevel\Log\Provider\Register as LogProvider;
use Leevel\Router\Provider\Register as RouterProvider;
use RuntimeException;

/**
 * 应用管理.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2017.01.14
 *
 * @version 1.0
 */
class App extends Container implements IApp
{
    /**
     * 当前应用实例.
     *
     * @var static
     */
    protected static $app;

    /**
     * 应用基础路径.
     *
     * @var string
     */
    protected $path;

    /**
     * 应用路径.
     *
     * @var string
     */
    protected $appPath;

    /**
     * 公共路径.
     *
     * @var string
     */
    protected $commonPath;

    /**
     * 运行时路径.
     *
     * @var string
     */
    protected $runtimePath;

    /**
     * 存储路径.
     *
     * @var string
     */
    protected $storagePath;

    /**
     * 资源路径.
     *
     * @var string
     */
    protected $publicPath;

    /**
     * 主题路径.
     *
     * @var string
     */
    protected $themesPath;

    /**
     * 配置路径.
     *
     * @var string
     */
    protected $optionPath;

    /**
     * 语言包路径.
     *
     * @var string
     */
    protected $i18nPath;

    /**
     * 环境变量路径.
     *
     * @var string
     */
    protected $envPath;

    /**
     * 环境变量文件.
     *
     * @var string
     */
    protected $envFile;

    /**
     * 语言包缓存路径.
     *
     * @var string
     */
    protected $i18nCachedPath;

    /**
     * 配置缓存路径.
     *
     * @var string
     */
    protected $optionCachedPath;

    /**
     * 路由缓存路径.
     *
     * @var string
     */
    protected $routerCachedPath;

    /**
     * 延迟载入服务提供者.
     *
     * @var array
     */
    protected $deferredProviders = [];

    /**
     * 服务提供者引导
     *
     * @var array
     */
    protected $providerBootstraps = [];

    /**
     * 是否已经初始化引导
     *
     * @var bool
     */
    protected $isBootstrap = false;

    /**
     * Composer 对象
     *
     * @var \Composer\Autoload\ClassLoader
     */
    protected $composer;

    /**
     * 构造函数
     * 应用中通过 singletons 生成单一实例.
     *
     * @param string $path
     */
    public function __construct(?string $path = null)
    {
        if ($path) {
            $this->setPath($path);
        }

        $this->registerBaseServices();

        $this->registerBaseProvider();
    }

    /**
     * 禁止克隆.
     */
    public function __clone()
    {
        throw new RuntimeException('App disallowed clone.');
    }

    /**
     * 返回应用.
     *
     * @param string $path
     *
     * @return static
     * @codeCoverageIgnore
     */
    public static function singletons(?string $path = null): IApp
    {
        if (null !== static::$app) {
            return static::$app;
        }

        return static::$app = new static($path);
    }

    /**
     * 程序版本.
     *
     * @return string
     */
    public function version(): string
    {
        return static::VERSION;
    }

    /**
     * 是否以扩展方式运行.
     *
     * @return bool
     */
    public function runWithExtension(): bool
    {
        return extension_loaded('leevel');
    }

    /**
     * 是否为 Console.
     *
     * @return bool
     */
    public function console(): bool
    {
        if (!is_object($this->make('request'))) {
            return \PHP_SAPI === 'cli';
        }

        return $this['request']->isCli();
    }

    /**
     * {@inheritdoc}
     */
    public function make(string $name, array $args = [])
    {
        $name = $this->getAlias($name);

        if (isset($this->deferredProviders[$name])) {
            $this->registerDeferredProvider($name);
        }

        return parent::make($name, $args);
    }

    /**
     * 设置应用路径.
     *
     * @param string $path
     */
    public function setPath(string $path): void
    {
        $this->path = $path;
    }

    /**
     * 基础路径.
     *
     * @param string $path
     *
     * @return string
     */
    public function path(string $path = ''): string
    {
        return $this->path.$this->normalizePath($path);
    }

    /**
     * 设置应用路径.
     *
     * @param string $path
     */
    public function setAppPath(string $path): void
    {
        $this->appPath = $path;
    }

    /**
     * 应用路径.
     *
     * @param bool|string $app
     * @param string      $path
     *
     * @return string
     */
    public function appPath($app = false, string $path = ''): string
    {
        return ($this->appPath ?? $this->path.\DIRECTORY_SEPARATOR.'application').
            ($app ? \DIRECTORY_SEPARATOR.$this->normalizeApp($app) : $app).
            $this->normalizePath($path);
    }

    /**
     * 取得应用主题目录.
     *
     * @param bool|string $app
     *
     * @return string
     */
    public function themePath($app = false): string
    {
        return $this->appPath($app).'/ui/theme';
    }

    /**
     * 设置公共路径.
     *
     * @param string $path
     */
    public function setCommonPath(string $path): void
    {
        $this->commonPath = $path;
    }

    /**
     * 公共路径.
     *
     * @param string $path
     *
     * @return string
     */
    public function commonPath(string $path = ''): string
    {
        return ($this->commonPath ?? $this->path.\DIRECTORY_SEPARATOR.'common').
            $this->normalizePath($path);
    }

    /**
     * 设置运行时路径.
     *
     * @param string $path
     */
    public function setRuntimePath(string $path): void
    {
        $this->runtimePath = $path;
    }

    /**
     * 运行路径.
     *
     * @param string $path
     *
     * @return string
     */
    public function runtimePath(string $path = ''): string
    {
        return ($this->runtimePath ?? $this->path.\DIRECTORY_SEPARATOR.'runtime').
            $this->normalizePath($path);
    }

    /**
     * 设置存储路径.
     *
     * @param string $path
     */
    public function setStoragePath(string $path): void
    {
        $this->storagePath = $path;
    }

    /**
     * 附件路径.
     *
     * @param string $path
     *
     * @return string
     */
    public function storagePath(string $path = ''): string
    {
        return ($this->storagePath ?? $this->path.\DIRECTORY_SEPARATOR.'storage').
            $this->normalizePath($path);
    }

    /**
     * 设置资源路径.
     *
     * @param string $path
     */
    public function setPublicPath(string $path): void
    {
        $this->publicPath = $path;
    }

    /**
     * 资源路径.
     *
     * @param string $path
     *
     * @return string
     */
    public function publicPath(string $path = ''): string
    {
        return ($this->publicPath ?? $this->path.\DIRECTORY_SEPARATOR.'public').
            $this->normalizePath($path);
    }

    /**
     * 设置主题路径.
     *
     * @param string $path
     */
    public function setThemesPath(string $path): void
    {
        $this->themesPath = $path;
    }

    /**
     * 主题路径.
     *
     * @param string $path
     *
     * @return string
     */
    public function themesPath(string $path = ''): string
    {
        return ($this->themesPath ?? $this->path.\DIRECTORY_SEPARATOR.'themes').
            $this->normalizePath($path);
    }

    /**
     * 设置配置路径.
     *
     * @param string $path
     */
    public function setOptionPath(string $path): void
    {
        $this->optionPath = $path;
    }

    /**
     * 配置路径.
     *
     * @param string $path
     *
     * @return string
     */
    public function optionPath(string $path = ''): string
    {
        return ($this->optionPath ?? $this->path.\DIRECTORY_SEPARATOR.'option').
            $this->normalizePath($path);
    }

    /**
     * 设置语言包路径.
     *
     * @param string $path
     */
    public function setI18nPath(string $path): void
    {
        $this->i18nPath = $path;
    }

    /**
     * 语言包路径.
     *
     * @param string $path
     *
     * @return string
     */
    public function i18nPath(?string $path = null): string
    {
        return ($this->i18nPath ?? $this->path.\DIRECTORY_SEPARATOR.'i18n').
            $this->normalizePath($path ?: '');
    }

    /**
     * 设置环境变量路径.
     *
     * @param string $path
     */
    public function setEnvPath(string $path): void
    {
        $this->envPath = $path;
    }

    /**
     * 环境变量路径.
     *
     * @return string
     */
    public function envPath(): string
    {
        return $this->envPath ?: $this->path;
    }

    /**
     * 设置环境变量文件.
     *
     * @param string $file
     */
    public function setEnvFile(string $file): void
    {
        $this->envFile = $file;
    }

    /**
     * 取得环境变量文件.
     *
     * @return string
     */
    public function envFile(): string
    {
        return $this->envFile ?: static::DEFAULT_ENV;
    }

    /**
     * 取得环境变量完整路径.
     *
     * @return string
     */
    public function fullEnvPath(): string
    {
        return $this->envPath().\DIRECTORY_SEPARATOR.$this->envFile();
    }

    /**
     * 设置语言包缓存路径.
     *
     * @param string $i18nCachedPath
     */
    public function setI18nCachedPath(string $i18nCachedPath): void
    {
        $this->i18nCachedPath = $i18nCachedPath;
    }

    /**
     * 返回语言包缓存路径.
     *
     * @param string $i18n
     *
     * @return string
     */
    public function i18nCachedPath(string $i18n): string
    {
        $basePath = $this->i18nCachedPath ?: $this->path().'/bootstrap/i18n';

        return $basePath.'/'.$i18n.'.php';
    }

    /**
     * 是否存在语言包缓存.
     *
     * @param string $i18n
     *
     * @return bool
     */
    public function isCachedI18n(string $i18n): bool
    {
        return is_file($this->i18nCachedPath($i18n));
    }

    /**
     * 设置配置缓存路径.
     *
     * @param string $optionCachedPath
     */
    public function setOptionCachedPath(string $optionCachedPath): void
    {
        $this->optionCachedPath = $optionCachedPath;
    }

    /**
     * 返回配置缓存路径.
     *
     * @since 2018.11.23 支持不同环境变量的缓存路径
     *
     * @return string
     */
    public function optionCachedPath(): string
    {
        $basePath = $this->optionCachedPath ?: $this->path().'/bootstrap';

        $cache = getenv('RUNTIME_ENVIRONMENT') ?: 'option';

        return $basePath.'/'.$cache.'.php';
    }

    /**
     * 是否存在配置缓存.
     *
     * @return bool
     */
    public function isCachedOption(): bool
    {
        return is_file($this->optionCachedPath());
    }

    /**
     * 设置路由缓存路径.
     *
     * @param string $routerCachedPath
     */
    public function setRouterCachedPath(string $routerCachedPath)
    {
        $this->routerCachedPath = $routerCachedPath;
    }

    /**
     * 返回路由缓存路径.
     *
     * @return string
     */
    public function routerCachedPath(): string
    {
        return $this->routerCachedPath ?: $this->path().'/bootstrap/router.php';
    }

    /**
     * 是否存在路由缓存.
     *
     * @return bool
     */
    public function isCachedRouter(): bool
    {
        return is_file($this->routerCachedPath());
    }

    /**
     * 设置 Composer 对象.
     *
     * @param \Composer\Autoload\ClassLoader $composer
     * @codeCoverageIgnore
     */
    public function setComposer(ClassLoader $composer): void
    {
        $this->composer = $composer;
    }

    /**
     * 取得 Composer 对象.
     *
     * @return \Composer\Autoload\ClassLoader
     * @codeCoverageIgnore
     */
    public function composer(): ClassLoader
    {
        return $this->composer;
    }

    /**
     * 获取命名空间目录真实路径.
     *
     * 一般用于获取文件 PSR4 所在的命名空间，当然如果存在命名空间。
     * 基于某个具体的类查询该类目录的真实路径。
     * 为简化开发和提升性能，必须提供具体的存在的类才能够获取目录的真实路径。
     *
     * @param string $specificClass
     * @param bool   $throwException
     *
     * @return string
     * @codeCoverageIgnore
     */
    public function namespacePath(string $specificClass, bool $throwException = true): string
    {
        if (false === $path = $this->composer()->findFile($specificClass)) {
            if (true === $throwException) {
                $e = sprintf('Specific class `%s` for finding namespaces was not found.', $specificClass);

                throw new RuntimeException($e);
            }

            return '';
        }

        return dirname($path);
    }

    /**
     * 是否开启 debug.
     *
     * @return bool
     */
    public function debug(): bool
    {
        return 'production' !== $this->environment() &&
            $this->make('option')->get('debug');
    }

    /**
     * 是否为开发环境.
     *
     * @return bool
     */
    public function development(): bool
    {
        return 'development' === $this->environment();
    }

    /**
     * 运行环境.
     *
     * @return string
     */
    public function environment(): string
    {
        return $this->make('option')->get('environment');
    }

    /**
     * 创建服务提供者.
     *
     * @param string $provider
     *
     * @return \Leevel\Di\Provider
     */
    public function makeProvider(string $provider): Provider
    {
        return new $provider($this);
    }

    /**
     * 执行 bootstrap.
     *
     * @param \Leevel\Di\Provider $provider
     */
    public function callProviderBootstrap(Provider $provider): void
    {
        if (!method_exists($provider, 'bootstrap')) {
            return;
        }

        $this->call([
            $provider,
            'bootstrap',
        ]);
    }

    /**
     * 初始化应用.
     *
     * @param array $bootstraps
     */
    public function bootstrap(array $bootstraps): void
    {
        if ($this->isBootstrap) {
            return;
        }

        foreach ($bootstraps as $value) {
            (new $value())->handle($this);
        }
    }

    /**
     * 是否已经初始化引导
     *
     * @return bool
     */
    public function isBootstrap(): bool
    {
        return $this->isBootstrap;
    }

    /**
     * 框架基础提供者 register.
     */
    public function registerProviders(): void
    {
        if ($this->isBootstrap) {
            return;
        }

        list($this->deferredProviders, $deferredAlias) = $this->make('option')->get('_deferred_providers', [[], []]);

        foreach ($deferredAlias as $alias) {
            $this->alias($alias);
        }

        $providers = $this->make('option')->get('_composer.providers', []);

        foreach ($providers as $provider) {
            $provider = $this->register($provider);

            if (method_exists($provider, 'bootstrap')) {
                $this->providerBootstraps[] = $provider;
            }
        }
    }

    /**
     * 执行框架基础提供者引导.
     */
    public function bootstrapProviders(): void
    {
        if ($this->isBootstrap) {
            return;
        }

        foreach ($this->providerBootstraps as $item) {
            $this->callProviderBootstrap($item);
        }

        $this->isBootstrap = true;
    }

    /**
     * 注册服务提供者.
     *
     * @param \Leevel\Di\Provider|string $provider
     *
     * @return \Leevel\Di\Provider
     */
    public function register($provider): Provider
    {
        if (is_string($provider)) {
            $provider = $this->makeProvider($provider);
        }

        if (method_exists($provider, 'register')) {
            $provider->register();
        }

        if ($this->isBootstrap()) {
            $this->callProviderBootstrap($provider);
        }

        return $provider;
    }

    /**
     * 注册基础服务.
     */
    protected function registerBaseServices(): void
    {
        $this->instance('app', $this);

        $this->alias([
            'app' => [
                'Leevel\\Leevel\\App',
                'Leevel\\Di\\Container',
                'Leevel\\Di\\IContainer',
                'Leevel\\Kernel\\IApp',
            ],
            'request' => [
                'Leevel\\Http\\IRequest',
                'Leevel\\Http\\Request',
            ],
            'option' => [
                'Leevel\\Option\\IOption',
                'Leevel\\Option\\Option',
            ],
            'i18n' => [
                'Leevel\\I18n\\I18n',
                'Leevel\\I18n\\II18n',
            ],
        ]);
    }

    /**
     * 注册基础服务提供者.
     *
     * @codeCoverageIgnore
     */
    protected function registerBaseProvider(): void
    {
        $this->register(new EventProvider($this));

        $this->register(new LogProvider($this));

        $this->register(new RouterProvider($this));
    }

    /**
     * 注册延迟载入服务提供者.
     *
     * @param string $provider
     */
    protected function registerDeferredProvider(string $provider): void
    {
        if (!isset($this->deferredProviders[$provider])) {
            return;
        }

        $providerInstance = $this->register($this->deferredProviders[$provider]);

        $this->callProviderBootstrap($providerInstance);

        unset($this->deferredProviders[$provider]);
    }

    /**
     * 格式化应用名字.
     *
     * @param bool|string $app
     *
     * @return string
     */
    protected function normalizeApp($app): string
    {
        return strtolower(true === $app ? ($this->make('app_name') ?: 'app') : $app);
    }

    /**
     * 格式化路径.
     *
     * @param string $path
     *
     * @return string
     */
    protected function normalizePath(string $path): string
    {
        return $path ? \DIRECTORY_SEPARATOR.$path : $path;
    }
}


<?php

declare(strict_types=1);

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

namespace Leevel\Validate;

use InvalidArgumentException;
use Leevel\Database\Ddd\IEntity;
use Leevel\Database\Ddd\Select;
use function Leevel\Support\Type\type_array;
use Leevel\Support\Type\type_array;

/**
 * 不能重复值验证规则.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.11.26
 *
 * @version 1.0
 */
class UniqueRule
{
    /**
     * 占位符.
     *
     * @var string
     */
    const PLACEHOLDER = '_';

    /**
     * 隔离符.
     *
     * @var string
     */
    const SEPARATE = ':';

    /**
     * 校验.
     *
     * @param mixed                       $value
     * @param array                       $parameter
     * @param \Leevel\Validate\IValidator $validator
     * @param string                      $field
     *
     * @return bool
     */
    public function validate($value, array $parameter, IValidator $validator, string $field): bool
    {
        if (!array_key_exists(0, $parameter)) {
            throw new InvalidArgumentException('Missing the first element of parameter.');
        }

        if (!is_string($parameter[0]) && !is_object($parameter[0])) {
            return false;
        }

        $select = $this->normalizeSelect($value, $parameter, $field);

        $this->parseExceptId($select, $parameter);
        $this->parseAdditional($select, $parameter);

        return 0 === $select->findCount();
    }

    /**
     * 创建语法规则.
     *
     * @param string $entity
     * @param string $field
     * @param mixed  $exceptId
     * @param string $primaryKey
     * @param array  $additional
     *
     * @return string
     */
    public static function rule(string $entity, ?string $field = null, $exceptId = null, ?string $primaryKey = null, ...$additional): string
    {
        if (!type_array($additional, ['string'])) {
            $e = 'Unique additional conditions must be string.';

            throw new InvalidArgumentException($e);
        }

        $tmp = [];

        $tmp[] = $entity;
        $tmp[] = $field ?: self::PLACEHOLDER;
        $tmp[] = $exceptId ?: self::PLACEHOLDER;
        $tmp[] = $primaryKey ?: self::PLACEHOLDER;

        $tmp = array_merge($tmp, $additional);

        return 'unique:'.implode(',', $tmp);
    }

    /**
     * 取得查询.
     *
     * @param array  $parameter
     * @param mixed  $value
     * @param string $field
     *
     * @return \Leevel\Database\Ddd\Select
     */
    protected function normalizeSelect($value, array $parameter, string $field): Select
    {
        $entity = $this->parseEntity($parameter);

        if (isset($parameter[1]) && self::PLACEHOLDER !== $parameter[1]) {
            $field = $parameter[1];
        }

        if (false !== strpos($field, self::SEPARATE)) {
            $select = $entity->selfDatabaseSelect();

            foreach (explode(self::SEPARATE, $field) as $v) {
                $select->where($v, $value);
            }
        } else {
            $select = $entity->where($field, $value);
        }

        return $select;
    }

    /**
     * 分析实体.
     *
     * @param array $parameter*
     *
     * @return \Leevel\Database\Ddd\IEntity
     */
    protected function parseEntity(array $parameter): IEntity
    {
        $connect = null;

        if (is_string($parameter[0])) {
            if (false !== strpos($parameter[0], self::SEPARATE)) {
                list($connect, $entityClass) = explode(self::SEPARATE, $parameter[0]);
            } else {
                $entityClass = $parameter[0];
            }

            if (!class_exists($entityClass)) {
                $e = sprintf('Validate entity `%s` was not found.', $entityClass);

                throw new InvalidArgumentException($e);
            }

            $entity = new $entityClass();
        } else {
            $entity = $parameter[0];
        }

        if (!($entity instanceof IEntity)) {
            $e = sprintf('Validate entity `%s` must be an entity.', get_class($entity));

            throw new InvalidArgumentException($e);
        }

        if ($connect) {
            $entity->withConnect($connect);
        }

        return $entity;
    }

    /**
     * 排除主键.
     *
     * @param \Leevel\Database\Ddd\Select $select
     * @param array                       $parameter
     */
    protected function parseExceptId(Select $select, array $parameter): void
    {
        if (isset($parameter[2]) && self::PLACEHOLDER !== $parameter[2]) {
            $withoutPrimary = true;

            if (!empty($parameter[3]) && self::PLACEHOLDER !== $parameter[3]) {
                $primaryKey = $parameter[3];
            } else {
                if (is_string($_ = $select->entity()->primaryKey())) {
                    $primaryKey = $_;
                } else {
                    $withoutPrimary = false;
                }
            }

            if ($withoutPrimary) {
                $select->where($primaryKey, '<>', $parameter[2]);
            }
        }
    }

    /**
     * 额外条件.
     *
     * @param \Leevel\Database\Ddd\Select $select
     * @param array                       $parameter
     */
    protected function parseAdditional(Select $select, array $parameter): void
    {
        if (($num = count($parameter)) >= 4) {
            for ($i = 4; $i < $num; $i += 2) {
                if (!isset($parameter[$i + 1])) {
                    $e = 'Unique additional conditions must be paired.';

                    throw new InvalidArgumentException($e);
                }

                if (false !== strpos($parameter[$i], self::SEPARATE)) {
                    list($field, $operator) = explode(self::SEPARATE, $parameter[$i]);
                } else {
                    $field = $parameter[$i];
                    $operator = '=';
                }

                $select->where($field, $operator, $parameter[$i + 1]);
            }
        }
    }
}

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


<?php

declare(strict_types=1);

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

namespace Leevel\Protocol\Process;

use Leevel\Protocol\IPool;

/**
 * Swoole 对象池清理进程.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2018.12.15
 *
 * @version 1.0
 */
class Pool extends Process
{
    /**
     * 进程名字.
     *
     * @var string
     */
    protected $name = 'pool';

    /**
     * 对象池.
     *
     * @var \Leevel\Protocol\IPool
     */
    protected $pool;

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

    /**
     * 响应句柄.
     */
    public function handle(): void
    {
        while (true) {
            sleep(360);

            $this->clear();
        }
    }

    /**
     * 清理.
     */
    public function clear(): void
    {
        foreach ($this->pool->getPools() as $className => &$pool) {
            while ($pool->count()) {
                $pool->shift();
            }
        }

        $this->log('The object pool has cleared.');
    }

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


<?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 ArrayAccess;
use Leevel\Support\IArray;
use SplFileObject;

/**
 * HTTP 请求
 * This class borrows heavily from the Symfony4 Framework and is part of the symfony package.
 *
 * @author Xiangmin Liu <635750556@qq.com>
 *
 * @since 2016.11.19
 *
 * @version 1.0
 *
 * @see Symfony\Component\HttpFoundation (https://github.com/symfony/symfony)
 */
class Request implements IRequest, IArray, ArrayAccess
{
    /**
     * GET Bag.
     *
     * @var \Leevel\Http\Bag
     */
    public $query;

    /**
     * POST Bag.
     *
     * @var \Leevel\Http\Bag
     */
    public $request;

    /**
     * 路由解析后的参数.
     *
     * @var \Leevel\Http\Bag
     */
    public $params;

    /**
     * COOKIE Bag.
     *
     * @var \Leevel\Http\Bag
     */
    public $cookies;

    /**
     * FILE Bag.
     *
     * @var \Leevel\Http\FileBag
     */
    public $files;

    /**
     * SERVER Bag.
     *
     * @var \Leevel\Http\ServerBag
     */
    public $server;

    /**
     * HEADER Bag.
     *
     * @var \Leevel\Http\HeaderBag
     */
    public $headers;

    /**
     * 内容.
     *
     * @var null|false|resource|string
     */
    protected $content;

    /**
     * 基础 url.
     *
     * @var string
     */
    protected $baseUrl;

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

    /**
     * 请求 url.
     *
     * @var string
     */
    protected $requestUri;

    /**
     * 请求类型.
     *
     * @var string
     */
    protected $method;

    /**
     * pathInfo.
     *
     * @var string
     */
    protected $pathInfo;

    /**
     * 当前语言.
     *
     * @var string
     */
    protected $language;

    /**
     * 构造函数.
     *
     * @param array                $query
     * @param array                $request
     * @param array                $params
     * @param array                $cookies
     * @param array                $files
     * @param array                $server
     * @param null|resource|string $content
     */
    public function __construct(array $query = [], array $request = [], array $params = [], array $cookies = [], array $files = [], array $server = [], $content = null)
    {
        $this->reset($query, $request, $params, $cookies, $files, $server, $content);
    }

    /**
     * 是否存在输入值.
     *
     * @param string $key
     *
     * @return bool
     */
    public function __isset(string $key): bool
    {
        return null !== $this->__get($key);
    }

    /**
     * 获取输入值.
     *
     * @param string $key
     *
     * @return mixed
     */
    public function __get(string $key)
    {
        return $this->get($key);
    }

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

    /**
     * 重置或者初始化.
     *
     * @param array                $query
     * @param array                $request
     * @param array                $params
     * @param array                $cookies
     * @param array                $files
     * @param array                $server
     * @param null|resource|string $content
     */
    public function reset(array $query = [], array $request = [], array $params = [], array $cookies = [], array $files = [], array $server = [], $content = null): void
    {
        $this->query = new Bag($query);
        $this->request = new Bag($request);
        $this->params = new Bag($params);
        $this->cookies = new Bag($cookies);
        $this->files = new FileBag($files);
        $this->server = new ServerBag($server);
        $this->headers = new HeaderBag($this->server->getHeaders());

        $this->content = $content;
        $this->baseUrl = null;
        $this->requestUri = null;
        $this->method = null;
        $this->pathInfo = null;
        $this->language = null;
    }

    /**
     * 全局变量创建一个 Request.
     *
     * @return static
     */
    public static function createFromGlobals(): IRequest
    {
        $request = new static($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER, null);

        $request = static::normalizeRequestFromContent($request);

        return $request;
    }

    /**
     * 格式化请求的内容.
     *
     * @param \Leevel\Http\IRequest $request
     *
     * @return \Leevel\Http\IRequest
     */
    public static function normalizeRequestFromContent(IRequest $request): IRequest
    {
        $contentType = $request->headers->get('CONTENT_TYPE');
        $method = strtoupper($request->server->get('REQUEST_METHOD', self::METHOD_GET));

        if ($contentType) {
            if (0 === strpos($contentType, 'application/x-www-form-urlencoded') &&
                in_array($method, [
                    static::METHOD_PUT,
                    static::METHOD_DELETE,
                    static::METHOD_PATCH,
                ], true)
            ) {
                parse_str($request->getContent(), $data);
                $request->request = new Bag($data);
            } elseif (0 === strpos($contentType, 'application/json') &&
                $content = $request->getContent()) {
                $request->request = new Bag(json_decode($content, true));
            }
        }

        return $request;
    }

    /**
     * 获取参数.
     *
     * @param string $key
     * @param mixed  $defaults
     *
     * @return mixed
     */
    public function get(string $key, $defaults = null)
    {
        $all = $this->all();

        if (array_key_exists($key, $all)) {
            return $all[$key];
        }

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

    /**
     * 请求是否包含给定的 keys.
     *
     * @param array $keys
     *
     * @return bool
     */
    public function exists(array $keys): bool
    {
        $input = $this->all();

        foreach ($keys as $value) {
            if (!array_key_exists($value, $input)) {
                return false;
            }
        }

        return true;
    }

    /**
     * 请求是否包含非空.
     *
     * @param array $keys
     *
     * @return bool
     */
    public function has(array $keys): bool
    {
        foreach ($keys as $value) {
            if ($this->isEmptyString($value)) {
                return false;
            }
        }

        return true;
    }

    /**
     * 取得给定的 keys 数据.
     *
     * @param array $keys
     *
     * @return array
     */
    public function only(array $keys): array
    {
        $results = [];

        $input = $this->all();

        foreach ($keys as $key) {
            $results[$key] = $input[$key] ?? null;
        }

        return $results;
    }

    /**
     * 取得排除给定的 keys 数据.
     *
     * @param array $keys
     *
     * @return array
     */
    public function except(array $keys): array
    {
        $results = $this->all();

        foreach ($keys as $key) {
            if (array_key_exists($key, $results)) {
                unset($results[$key]);
            }
        }

        return $results;
    }

    /**
     * 取回输入和文件.
     *
     * @return array
     */
    public function all(): array
    {
        return array_replace_recursive($this->input(), $this->allFiles());
    }

    /**
     * 获取输入数据.
     *
     * @param string            $key
     * @param null|array|string $defaults
     *
     * @return mixed
     */
    public function input(?string $key = null, $defaults = null)
    {
        $input = $this->getInputSource()->all() + $this->query->all();

        if (null === $key) {
            return $input;
        }

        return $input[$key] ?? $defaults;
    }

    /**
     * 取回 query.
     *
     * @param string            $key
     * @param null|array|string $defaults
     *
     * @return array|string
     */
    public function query(?string $key = null, $defaults = null)
    {
        return $this->getItem('query', $key, $defaults);
    }

    /**
     * 请求是否存在 COOKIE.
     *
     * @param string $key
     *
     * @return bool
     */
    public function hasCookie(string $key): bool
    {
        return null !== $this->cookie($key);
    }

    /**
     * 取回 cookie.
     *
     * @param string            $key
     * @param null|array|string $defaults
     *
     * @return array|string
     */
    public function cookie(?string $key = null, $defaults = null)
    {
        return $this->getItem('cookies', $key, $defaults);
    }

    /**
     * 取得所有文件.
     *
     * @return array
     */
    public function allFiles(): array
    {
        return $this->files->all();
    }

    /**
     * 获取文件
     * 数组文件请在末尾加上反斜杆访问.
     *
     * @param string $key
     * @param mixed  $defaults
     *
     * @return null|array|\Leevel\Http\UploadedFile
     */
    public function file(?string $key = null, $defaults = null)
    {
        if (!$key || false === strpos($key, '\\')) {
            return $this->getItem('files', $key, $defaults);
        }

        return $this->files->getArr($key, is_array($defaults) ? $defaults : []);
    }

    /**
     * 文件是否存在已上传的文件
     * 数组文件请在末尾加上反斜杆访问.
     *
     * @param string $key
     *
     * @return bool
     */
    public function hasFile(string $key): bool
    {
        $files = $this->file($key);

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

        foreach ($files as $file) {
            if ($this->isValidFile($file)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 验证是否为文件实例.
     *
     * @param mixed $file
     *
     * @return bool
     */
    public function isValidFile($file): bool
    {
        return $file instanceof SplFileObject && '' !== $file->getPath();
    }

    /**
     * 取回 header.
     *
     * @param string            $key
     * @param null|array|string $defaults
     *
     * @return array|string
     */
    public function header(?string $key = null, $defaults = null)
    {
        return $this->getItem('headers', $key, $defaults);
    }

    /**
     * 取回 server.
     *
     * @param string            $key
     * @param null|array|string $defaults
     *
     * @return array|string
     */
    public function server(?string $key = null, $defaults = null)
    {
        return $this->getItem('server', $key, $defaults);
    }

    /**
     * 取回数据项.
     *
     * @param string            $source
     * @param string            $key
     * @param null|array|string $defaults
     *
     * @return array|string
     */
    public function getItem(string $source, ?string $key, $defaults)
    {
        if (null === $key) {
            return $this->{$source}->all();
        }

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

    /**
     * 合并输入.
     *
     * @param array $input
     */
    public function merge(array $input): void
    {
        $this->getInputSource()->add($input);
    }

    /**
     * 替换输入.
     *
     * @param array $input
     */
    public function replace(array $input): void
    {
        $this->getInputSource()->replace($input);
    }

    /**
     * PHP 运行模式命令行, 兼容 swoole http service
     * Swoole http 服务器也以命令行运行.
     *
     * @see http://php.net/manual/zh/function.php-sapi-name.php
     *
     * @return bool
     */
    public function isCli(): bool
    {
        if ('swoole-http-server' === $this->server->get('SERVER_SOFTWARE')) {
            return false;
        }

        return $this->isRealCli();
    }

    /**
     * PHP 运行模式命令行.
     *
     * @see http://php.net/manual/zh/function.php-sapi-name.php
     *
     * @return bool
     */
    public function isRealCli(): bool
    {
        return \PHP_SAPI === 'cli';
    }

    /**
     * PHP 运行模式 cgi.
     *
     * @see http://php.net/manual/zh/function.php-sapi-name.php
     *
     * @return bool
     */
    public function isCgi(): bool
    {
        return 'cgi' === substr(\PHP_SAPI, 0, 3);
    }

    /**
     * 是否为 Ajax 请求行为.
     *
     * @return bool
     */
    public function isAjax(): bool
    {
        $field = static::VAR_AJAX;

        if ($this->request->has($field) || $this->query->has($field)) {
            return true;
        }

        return $this->isRealAjax();
    }

    /**
     * 是否为 Ajax 请求行为真实.
     *
     * @return bool
     */
    public function isRealAjax(): bool
    {
        return $this->isXmlHttpRequest();
    }

    /**
     * 是否为 Ajax 请求行为真实.
     *
     * @return bool
     */
    public function isXmlHttpRequest(): bool
    {
        return 'XMLHttpRequest' === $this->headers->get('X_REQUESTED_WITH');
    }

    /**
     * 是否为 Pjax 请求行为.
     *
     * @return bool
     */
    public function isPjax(): bool
    {
        $field = static::VAR_PJAX;

        if ($this->request->has($field) || $this->query->has($field)) {
            return true;
        }

        return $this->isRealPjax();
    }

    /**
     * 是否为 Pjax 请求行为真实.
     *
     * @return bool
     */
    public function isRealPjax(): bool
    {
        return null !== $this->headers->get('X_PJAX');
    }

    /**
     * 是否为 json 请求行为.
     *
     * @return bool
     */
    public function isJson(): bool
    {
        $field = static::VAR_JSON;

        if ($this->request->has($field) || $this->query->has($field)) {
            return true;
        }

        return $this->isRealJson();
    }

    /**
     * 是否为 json 请求行为真实.
     *
     * @return bool
     */
    public function isRealJson(): bool
    {
        $contentType = $this->headers->get('CONTENT_TYPE');

        if (!$contentType) {
            return false;
        }

        foreach (['/json', '+json'] as $item) {
            if (false !== strpos($contentType, $item)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 是否为接受 json 请求.
     *
     * @return bool
     */
    public function isAcceptJson(): bool
    {
        $field = static::VAR_ACCEPT_JSON;

        if ($this->request->has($field) || $this->query->has($field)) {
            return true;
        }

        if ($this->isAjax() && !$this->isPjax() && $this->isAcceptAny()) {
            return true;
        }

        return $this->isRealAcceptJson();
    }

    /**
     * 是否为接受 json 请求真实.
     *
     * @return bool
     */
    public function isRealAcceptJson(): bool
    {
        $accept = $this->headers->get('ACCEPT');

        if (!$accept) {
            return false;
        }

        foreach (['/json', '+json'] as $item) {
            if (false !== strpos($accept, $item)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 是否为接受任何请求.
     *
     * @return bool
     */
    public function isAcceptAny(): bool
    {
        $accept = $this->headers->get('ACCEPT');

        if (!$accept) {
            return true;
        }

        if (false !== strpos($accept, '*')) {
            return true;
        }

        return false;
    }

    /**
     * 是否为 HEAD 请求行为.
     *
     * @return bool
     */
    public function isHead(): bool
    {
        return $this->getMethod() === static::METHOD_HEAD;
    }

    /**
     * 是否为 GET 请求行为.
     *
     * @return bool
     */
    public function isGet(): bool
    {
        return $this->getMethod() === static::METHOD_GET;
    }

    /**
     * 是否为 POST 请求行为.
     *
     * @return bool
     */
    public function isPost(): bool
    {
        return $this->getMethod() === static::METHOD_POST;
    }

    /**
     * 是否为 PUT 请求行为.
     *
     * @return bool
     */
    public function isPut(): bool
    {
        return $this->getMethod() === static::METHOD_PUT;
    }

    /**
     * 是否为 PATCH 请求行为.
     *
     * @return bool
     */
    public function isPatch(): bool
    {
        return $this->getMethod() === static::METHOD_PATCH;
    }

    /**
     * 是否为 PURGE 请求行为.
     *
     * @return bool
     */
    public function isPurge(): bool
    {
        return $this->getMethod() === static::METHOD_PURGE;
    }

    /**
     * 是否为 OPTIONS 请求行为.
     *
     * @return bool
     */
    public function isOptions(): bool
    {
        return $this->getMethod() === static::METHOD_OPTIONS;
    }

    /**
     * 是否为 TRACE 请求行为.
     *
     * @return bool
     */
    public function isTrace(): bool
    {
        return $this->getMethod() === static::METHOD_TRACE;
    }

    /**
     * 是否为 CONNECT 请求行为.
     *
     * @return bool
     */
    public function isConnect(): bool
    {
        return $this->getMethod() === static::METHOD_CONNECT;
    }

    /**
     * 获取 IP 地址.
     *
     * @return string
     */
    public function getClientIp(): string
    {
        return $this->headers->get('CLIENT_IP', $this->server->get('REMOTE_ADDR', '0.0.0.0'));
    }

    /**
     * 请求类型.
     *
     * @return string
     */
    public function getMethod(): string
    {
        if (null !== $this->method) {
            return $this->method;
        }

        $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));

        if ('POST' === $this->method) {
            if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
                $this->method = strtoupper($method);
            } else {
                $field = static::VAR_METHOD;

                $this->method = strtoupper($this->request->get($field, $this->query->get($field, 'POST')));
            }
        }

        return $this->method;
    }

    /**
     * 设置请求类型.
     *
     * @param string $method
     *
     * @return $this
     */
    public function setMethod(string $method): IRequest
    {
        $this->method = null;
        $this->server->set('REQUEST_METHOD', $method);

        return $this;
    }

    /**
     * 实际请求类型.
     *
     * @return string
     */
    public function getRealMethod(): string
    {
        return strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
    }

    /**
     * 验证是否为指定的方法.
     *
     * @param string $method
     *
     * @return bool
     */
    public function isMethod(string $method): bool
    {
        return $this->getMethod() === strtoupper($method);
    }

    /**
     * 返回当前的语言.
     *
     * @return null|string
     */
    public function language(): ?string
    {
        return $this->language;
    }

    /**
     * 返回当前的语言
     *
     * @return null|string
     */
    public function getLanguage(): ?string
    {
        return $this->language;
    }

    /**
     * 设置当前的语言
     *
     * @param string $language
     *
     * @return $this
     */
    public function setLanguage(string $language): IRequest
    {
        $this->language = $language;

        return $this;
    }

    /**
     * 取得请求内容.
     *
     * @return string
     */
    public function getContent(): string
    {
        $resources = is_resource($this->content);

        if ($resources) {
            rewind($this->content);

            return stream_get_contents($this->content);
        }

        if (null === $this->content) {
            $this->content = file_get_contents('php://input');
        }

        return $this->content;
    }

    /**
     * 返回 root URL.
     *
     * @return string
     */
    public function getRoot(): string
    {
        return rtrim($this->getSchemeAndHttpHost().$this->getBaseUrl(), '/');
    }

    /**
     * 返回入口文件.
     *
     * @return string
     */
    public function getEnter(): string
    {
        if ($this->isCli()) {
            return '';
        }

        $scriptName = $this->getScriptName();
        $scriptName = dirname($scriptName);

        if ('\\' === $scriptName) {
            $scriptName = '/';
        }

        return $scriptName;
    }

    /**
     * 取得脚本名字.
     *
     * @return string
     */
    public function getScriptName(): string
    {
        return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', ''));
    }

    /**
     * 是否启用 https.
     *
     * @return bool
     */
    public function isSecure(): bool
    {
        if (in_array($this->server->get('HTTPS'), ['1', 'on'], true)) {
            return true;
        }

        if ('443' === $this->server->get('SERVER_PORT')) {
            return true;
        }

        return false;
    }

    /**
     * 取得 http host.
     *
     * @return string
     */
    public function getHttpHost(): string
    {
        $scheme = $this->getScheme();
        $port = $this->getPort();

        if (('http' === $scheme && 80 === $port) || ('https' === $scheme && 443 === $port)) {
            return $this->getHost();
        }

        return $this->getHost().':'.$port;
    }

    /**
     * 获取 host.
     *
     * @return string
     */
    public function getHost(): string
    {
        $host = $this->headers->get('X_FORWARDED_HOST', $this->headers->get('HOST', ''));

        if (!$host) {
            $host = $this->server->get('SERVER_NAME', $this->server->get('SERVER_ADDR', ''));
        }

        if (false !== strpos($host, ':')) {
            list($host) = explode(':', $host);
        }

        return $host;
    }

    /**
     * 取得 Scheme 和 Host.
     *
     * @return string
     */
    public function getSchemeAndHttpHost(): string
    {
        return $this->getScheme().'://'.$this->getHttpHost();
    }

    /**
     * 返回当前 URL 地址.
     *
     * @return string
     */
    public function getUri(): string
    {
        if (null !== $queryString = $this->getQueryString()) {
            $queryString = '?'.$queryString;
        }

        return $this->getSchemeAndHttpHost().rtrim($this->getBaseUrl(), '/').$this->getPathInfo().$queryString;
    }

    /**
     * 服务器端口.
     *
     * @return int
     */
    public function getPort(): int
    {
        $port = (int) $this->server->get('SERVER_PORT');

        if (!$port) {
            $port = 'https' === $this->getScheme() ? 443 : 80;
        }

        return $port;
    }

    /**
     * 返回 scheme.
     *
     * @return string
     */
    public function getScheme(): string
    {
        return $this->isSecure() ? 'https' : 'http';
    }

    /**
     * 取回查询参数.
     *
     * @return null|string
     */
    public function getQueryString(): ?string
    {
        $queryString = $this->normalizeQueryString($this->server->get('QUERY_STRING'));

        return '' === $queryString && '0' !== $queryString ? null : $queryString;
    }

    /**
     * 设置 pathInfo.
     *
     * @param string $pathInfo
     *
     * @return $this
     */
    public function setPathInfo(string $pathInfo): IRequest
    {
        $this->pathInfo = $pathInfo;

        return $this;
    }

    /**
     * pathInfo 兼容性分析.
     *
     * @return string
     */
    public function getPathInfo(): string
    {
        if (null !== $this->pathInfo) {
            return $this->pathInfo;
        }

        $pathInfo = $this->server->get('PATH_INFO');

        if ($pathInfo) {
            return $this->pathInfo = $this->parsePathInfo($pathInfo);
        }

        // 分析基础 url
        $baseUrl = $this->getBaseUrl();

        // 分析请求参数
        if (null === ($requestUri = $this->getRequestUri())) {
            return $this->pathInfo = $this->parsePathInfo('');
        }

        if (($pos = strpos($requestUri, '?')) > -1) {
            $requestUri = substr($requestUri, 0, $pos);
        }

        if (null !== $baseUrl &&
            (false === ($pathInfo = substr($requestUri, strlen($baseUrl))))) {
            $pathInfo = '';
        } elseif (null === $baseUrl || '/' === $baseUrl) {
            $pathInfo = $requestUri;
        }

        return $this->pathInfo = $this->parsePathInfo($pathInfo);
    }

    /**
     * 获取基础路径.
     *
     * @return string
     */
    public function getBasePath(): string
    {
        if (null !== $this->basePath) {
            return $this->basePath;
        }

        $baseUrl = $this->getBaseUrl();

        if (empty($baseUrl)) {
            return '';
        }

        $filename = basename($this->server->get('SCRIPT_FILENAME', ''));

        if (basename($baseUrl) === $filename) {
            $basePath = dirname($baseUrl);
        } else {
            $basePath = $baseUrl;
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            $basePath = str_replace('\\', '/', $basePath);
        }

        $this->basePath = rtrim($basePath, '/');

        return $this->basePath;
    }

    /**
     * 分析基础 url.
     *
     * @return string
     */
    public function getBaseUrl(): string
    {
        if (null !== $this->baseUrl) {
            return $this->baseUrl;
        }

        // 兼容分析
        $fileName = basename($this->server->get('SCRIPT_FILENAME', ''));

        if (basename($this->server->get('SCRIPT_NAME', '')) === $fileName) {
            $url = $this->server->get('SCRIPT_NAME');
        } elseif (basename($this->server->get('PHP_SELF', '')) === $fileName) {
            $url = $this->server->get('PHP_SELF');
        } elseif (basename($this->server->get('ORIG_SCRIPT_NAME', '')) === $fileName) {
            $url = $this->server->get('ORIG_SCRIPT_NAME');
        } else {
            $path = $this->server->get('PHP_SELF');
            $segs = explode('/', trim($fileName, '/'));
            $segs = array_reverse($segs);
            $index = 0;
            $maxCount = count($segs);

            $url = '';
            do {
                $seg = $segs[$index];
                $url = '/'.$seg.$url;
                $index++;
            } while (($maxCount > $index) && (false !== ($pos = strpos($path, $url))) && (0 !== $pos));
        }

        // 比对请求
        $requestUri = $this->getRequestUri();

        $requestUri = (string) $requestUri;
        $url = (string) $url;

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

        if ($url) {
            $prefix = $this->getUrlencodedPrefix($requestUri, $url);

            if (false !== $prefix) {
                return $this->baseUrl = $prefix;
            }

            $prefix = $this->getUrlencodedPrefix($requestUri, dirname($url));

            if (false !== $prefix) {
                return $this->baseUrl = rtrim($prefix, '/').'/';
            }
        }

        $basename = basename($url);

        if (empty($basename) || !strpos(rawurldecode($requestUri), $basename)) {
            return $this->baseUrl = '';
        }

        if ((strlen($requestUri) >= strlen($url)) &&
            ((false !== ($pos = strpos($requestUri, $url))) && (0 !== $pos))) {
            $url = substr($requestUri, 0, $pos + strlen($url));
        }

        return $this->baseUrl = rtrim($url, '/').'/';
    }

    /**
     * 请求参数.
     *
     * @return string
     */
    public function getRequestUri(): ?string
    {
        if (null !== $this->requestUri) {
            return $this->requestUri;
        }

        $requestUri = $this->headers->get('X_REWRITE_URL', $this->server->get('REQUEST_URI', ''));

        if (!$requestUri) {
            $requestUri = $this->server->get('ORIG_PATH_INFO');

            if ($this->server->get('QUERY_STRING')) {
                $requestUri .= '?'.$this->server->get('QUERY_STRING');
            }
        }

        return $this->requestUri = $requestUri;
    }

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

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

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

        return $all[$index] ?? null;
    }

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

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

    /**
     * pathinfo 处理.
     *
     * @param string $pathInfo
     *
     * @return string
     */
    protected function parsePathInfo(string $pathInfo): string
    {
        if ($pathInfo) {
            $ext = pathinfo($pathInfo, PATHINFO_EXTENSION);

            if ($ext) {
                $pathInfo = substr($pathInfo, 0, -(strlen($ext) + 1));
            }
        }

        $pathInfo = empty($pathInfo) ? '/' : '/'.ltrim($pathInfo, '/');

        return $pathInfo;
    }

    /**
     * 格式化查询参数.
     *
     * @param null|string $queryString
     *
     * @return string
     */
    protected function normalizeQueryString(?string $queryString): string
    {
        if (!$queryString && '0' !== $queryString) {
            return '';
        }

        $parts = [];

        foreach (explode('&', $queryString) as $item) {
            if ('' === $item && '0' !== $item) {
                continue;
            }

            $parts[] = $item;
        }

        return implode('&', $parts);
    }

    /**
     * 取得请求输入源.
     *
     * @return \Leevel\Http\Bag
     */
    protected function getInputSource(): Bag
    {
        return $this->getMethod() === static::METHOD_GET ? $this->query : $this->request;
    }

    /**
     * 是否为空字符串.
     *
     * @param string $key
     *
     * @return bool
     */
    protected function isEmptyString(string $key): bool
    {
        $value = $this->input($key);

        return is_string($value) && '' === trim($value) && '0' !== $value;
    }

    /**
     * URL 前缀编码
     *
     * @param string $strings
     * @param string $prefix
     *
     * @return bool|string
     */
    protected function getUrlencodedPrefix(string $strings, string $prefix)
    {
        if (0 !== strpos(rawurldecode($strings), $prefix)) {
            return false;
        }

        $len = strlen($prefix);

        if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $strings, $matches)) {
            return $matches[0];
        }

        return false;
    }
}