IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> thinkphp6 框架源码分析 -> 正文阅读

[PHP知识库]thinkphp6 框架源码分析

一、入口概述

public/index.php

// 执行HTTP应用并响应
$http = (new App())->setEnvName('local')->http;

$response = $http->run();

$response->send();

$http->end($response);

1.1 创建容器

1.1.1、创建预加载器

Laminas\ZendFrameworkBridge\Autoloader

createPrependAutoloader(){}		//分析命名空间,查找对应的类

通过 Composer\Autoload\ClassLoader 去加载对应文件

loadClass(){} //根据类名,去识别文件的全路径,然后通过includefile去加载类

1.1.2、绑定容器

think\app 初始化框架基础,加载app目录下的 provider文件,绑定到app容器,并初始化成单里

其中 think\app 类继承了 think\container 容器类

//此处已初始化容器并完成绑定 
class App extends Container
{
  protected $appPath = '';	//应用目录
  protected $runtimePath = '';	//Runtime目录
  protected $routePath = '';		//路由定义目录
  
  protected $initializers = [		//应用初始化器
        Error::class,
        RegisterService::class,
        BootService::class,
    ];
  protected $services = [];		//注册的系统服务
  protected $initialized = false;
  
  protected $bind = [				//容器绑定标识
    'app'                     => App::class,
    'cache'                   => Cache::class,
    'config'                  => Config::class,
    'console'                 => Console::class,
    'cookie'                  => Cookie::class,
    'db'                      => Db::class,
    'env'                     => Env::class,
    'event'                   => Event::class,
    'http'                    => Http::class,
    'lang'                    => Lang::class,
    'log'                     => Log::class,
    'middleware'              => Middleware::class,
    'request'                 => Request::class,
    'response'                => Response::class,
    'route'                   => Route::class,
    'session'                 => Session::class,
    'validate'                => Validate::class,
    'view'                    => View::class,
    'filesystem'              => Filesystem::class,
    'think\DbManager'         => Db::class,
    'think\LogManager'        => Log::class,
    'think\CacheManager'      => Cache::class,

    // 接口依赖注入
    'Psr\Log\LoggerInterface' => Log::class,
  ];
  
	/**
  * 架构方法 框架初始化,仅执行该处的构造方法
  * @param string $rootPath 应用根目录
  */
  public function __construct(string $rootPath = '')
  {
    $this->thinkPath   = dirname(__DIR__) . DIRECTORY_SEPARATOR;
    $this->rootPath    = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
    $this->appPath     = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
    $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;

    if (is_file($this->appPath . 'provider.php')) 
      $this->bind(include $this->appPath . 'provider.php'); //app容器加载并绑定 app\provider.php 中的提供者

    static::setInstance($this);	//setInstance 函数由 think\container 类实现

    $this->instance('app', $this);//实例化单个类
    $this->instance('think\Container', $this);
  }
  
  public function register($service, bool $force = false){		//注册服务
    
  }

    public function instance(string $abstract, $instance)
    {
        $abstract = $this->getAlias($abstract);

        $this->instances[$abstract] = $instance; //是个数组,存储了每个实例化的类

        return $this;
    }
}

1.2 设置环境

think\App.php

public function setEnvName(string $name)
{
  $this->envName = $name;
  return $this;
}

1.3 获取http服务

$http = (new App())->setEnvName()->http;

1.4 执行请求

$response = $http->send();

1.5 执行结束时的工作

$http->end($response);

二、创建容器

2.1 think\Container.php 源码分析

<?php
namespace think;

use ArrayAccess;
use ArrayIterator;
use Closure;
use Countable;
use InvalidArgumentException;
use IteratorAggregate;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use ReflectionMethod;
use think\exception\ClassNotFoundException;
use think\exception\FuncNotFoundException;
use think\helper\Str;

/**
 * 容器管理类 支持PSR-11
 */
class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable {

    protected static $instance;	//容器对象实例
    protected $instances = [];		//容器中的对象实例
    protected $bind = [];		//容器绑定标识
    protected $invokeCallback = [];		//容器回调

    public static function getInstance() {		//获取当前容器的实例(单例)
        if (is_null(static::$instance)) {
            static::$instance = new static;
        }

        if (static::$instance instanceof Closure) {
            return (static::$instance)();
        }

        return static::$instance;
    }

    public static function setInstance($instance): void {		//设置当前容器的实例
        static::$instance = $instance;
    }

    public function resolving($abstract, Closure $callback = null): void {		//注册一个容器对象回调
        if ($abstract instanceof Closure) {
            $this->invokeCallback['*'][] = $abstract;
            return;
        }

        $abstract = $this->getAlias($abstract);

        $this->invokeCallback[$abstract][] = $callback;
    }

    //获取容器中的对象实例 不存在则创建
    public static function pull(string $abstract, array $vars = [], bool $newInstance = false) {
        return static::getInstance()->make($abstract, $vars, $newInstance);
    }

    public function get($abstract) {		//获取容器中的对象实例,$abstract 类名或者标识
        if ($this->has($abstract)) {
            return $this->make($abstract);
        }

        throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
    }

    /**
     * 绑定一个类、闭包、实例、接口实现到容器
     * @param string|array $abstract 类标识、接口
     * @param mixed $concrete 要绑定的类、闭包或者实例
     * @return $this
     */
    public function bind($abstract, $concrete = null) {
        if (is_array($abstract)) {
            foreach ($abstract as $key => $val) {
                $this->bind($key, $val);
            }
        } elseif ($concrete instanceof Closure) {
            $this->bind[$abstract] = $concrete;
        } elseif (is_object($concrete)) {
            $this->instance($abstract, $concrete);
        } else {
            $abstract = $this->getAlias($abstract);
            if ($abstract != $concrete) {
                $this->bind[$abstract] = $concrete;
            }
        }

        return $this;
    }

    public function getAlias(string $abstract): string {		//根据别名获取真实类名
        if (isset($this->bind[$abstract])) {
            $bind = $this->bind[$abstract];

            if (is_string($bind)) {
                return $this->getAlias($bind);
            }
        }

        return $abstract;
    }

    public function instance(string $abstract, $instance) {	//绑定一个类实例到容器,$abstract 类名或者标识,$instance 类的实例
        $abstract = $this->getAlias($abstract);

        $this->instances[$abstract] = $instance;

        return $this;
    }

    public function bound(string $abstract): bool {	//判断容器中是否存在类及标识,$abstract 类名或者标识
        return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
    }

    public function has($name): bool {	//判断容器中是否存在类及标识,$name 类名或者标识
        return $this->bound($name);
    }

    public function exists(string $abstract): bool {	//判断容器中是否存在对象实例,$abstract 类名或者标识
        $abstract = $this->getAlias($abstract);

        return isset($this->instances[$abstract]);
    }

    //创建类的实例 已经存在则直接获取,$abstract 类名或者标识,$vars 变量,$newInstance 是否每次创建新的实例
    public function make(string $abstract, array $vars = [], bool $newInstance = false) {
        $abstract = $this->getAlias($abstract);

        if (isset($this->instances[$abstract]) && !$newInstance) {
            return $this->instances[$abstract];
        }

        if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
            $object = $this->invokeFunction($this->bind[$abstract], $vars);
        } else {
            $object = $this->invokeClass($abstract, $vars);
        }

        if (!$newInstance) {
            $this->instances[$abstract] = $object;
        }

        return $object;
    }

    //删除容器中的对象实例,$name 类名或者标识
    public function delete($name) {
        $name = $this->getAlias($name);

        if (isset($this->instances[$name])) {
            unset($this->instances[$name]);
        }
    }

    //执行函数或者闭包方法 支持参数调用,$function 函数或者闭包,$vars 参数
    public function invokeFunction($function, array $vars = []) {
        try {
            $reflect = new ReflectionFunction($function);
        } catch (ReflectionException $e) {
            throw new FuncNotFoundException("function not exists: {$function}()", $function, $e);
        }

        $args = $this->bindParams($reflect, $vars);

        return $function(...$args);
    }

    //调用反射执行类的方法 支持参数绑定,$method 方法,$vars 参数,$accessible 设置是否可访问
    public function invokeMethod($method, array $vars = [], bool $accessible = false) {
        if (is_array($method)) {
            [$class, $method] = $method;

            $class = is_object($class) ? $class : $this->invokeClass($class);
        } else {
            // 静态方法
            [$class, $method] = explode('::', $method);
        }

        try {
            $reflect = new ReflectionMethod($class, $method);
        } catch (ReflectionException $e) {
            $class = is_object($class) ? get_class($class) : $class;
            throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e);
        }

        $args = $this->bindParams($reflect, $vars);

        if ($accessible) {
            $reflect->setAccessible($accessible);
        }

        return $reflect->invokeArgs(is_object($class) ? $class : null, $args);
    }

    //调用反射执行类的方法 支持参数绑定,$instance 对象实例,$reflect 反射类,$vars 参数
    public function invokeReflectMethod($instance, $reflect, array $vars = []) {
        $args = $this->bindParams($reflect, $vars);

        return $reflect->invokeArgs($instance, $args);
    }

    //调用反射执行callable 支持参数绑定,$vars 参数,$accessible 设置是否可访问
    public function invoke($callable, array $vars = [], bool $accessible = false) {
        if ($callable instanceof Closure) {
            return $this->invokeFunction($callable, $vars);
        } elseif (is_string($callable) && false === strpos($callable, '::')) {
            return $this->invokeFunction($callable, $vars);
        } else {
            return $this->invokeMethod($callable, $vars, $accessible);
        }
    }

    //调用反射执行类的实例化 支持依赖注入,$class 类名,$vars 参数
    public function invokeClass(string $class, array $vars = []) {
        try {
            $reflect = new ReflectionClass($class);
        } catch (ReflectionException $e) {
            throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
        }

        if ($reflect->hasMethod('__make')) {
            $method = $reflect->getMethod('__make');
            if ($method->isPublic() && $method->isStatic()) {
                $args = $this->bindParams($method, $vars);
                $object = $method->invokeArgs(null, $args);
                $this->invokeAfter($class, $object);
                return $object;
            }
        }

        $constructor = $reflect->getConstructor();

        $args = $constructor ? $this->bindParams($constructor, $vars) : [];

        $object = $reflect->newInstanceArgs($args);

        $this->invokeAfter($class, $object);

        return $object;
    }

    //执行invokeClass回调,$class 对象类名,$object 容器对象实例
    protected function invokeAfter(string $class, $object): void {
        if (isset($this->invokeCallback['*'])) {
            foreach ($this->invokeCallback['*'] as $callback) {
                $callback($object, $this);
            }
        }

        if (isset($this->invokeCallback[$class])) {
            foreach ($this->invokeCallback[$class] as $callback) {
                $callback($object, $this);
            }
        }
    }

    //绑定参数,$reflect 反射类,$vars 参数
    protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array {
        if ($reflect->getNumberOfParameters() == 0) {
            return [];
        }

        // 判断数组类型 数字数组时按顺序绑定参数
        reset($vars);
        $type = key($vars) === 0 ? 1 : 0;
        $params = $reflect->getParameters();
        $args = [];

        foreach ($params as $param) {
            $name = $param->getName();
            $lowerName = Str::snake($name);
            $reflectionType = $param->getType();

            if ($reflectionType && $reflectionType->isBuiltin() === false) {
                $args[] = $this->getObjectParam($reflectionType->getName(), $vars);
            } elseif (1 == $type && !empty($vars)) {
                $args[] = array_shift($vars);
            } elseif (0 == $type && array_key_exists($name, $vars)) {
                $args[] = $vars[$name];
            } elseif (0 == $type && array_key_exists($lowerName, $vars)) {
                $args[] = $vars[$lowerName];
            } elseif ($param->isDefaultValueAvailable()) {
                $args[] = $param->getDefaultValue();
            } else {
                throw new InvalidArgumentException('method param miss:' . $name);
            }
        }

        return $args;
    }

    //创建工厂对象实例,$name 工厂类名,$namespace 默认命名空间
    public static function factory(string $name, string $namespace = '', ...$args) {
        $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name);

        return Container::getInstance()->invokeClass($class, $args);
    }

    //获取对象类型的参数值,$className 类名,$vars 参数
    protected function getObjectParam(string $className, array &$vars) {
        $array = $vars;
        $value = array_shift($array);

        if ($value instanceof $className) {
            $result = $value;
            array_shift($vars);
        } else {
            $result = $this->make($className);
        }

        return $result;
    }

    public function __set($name, $value) {
        $this->bind($name, $value);
    }

    public function __get($name) {
        return $this->get($name);
    }

    public function __isset($name): bool {
        return $this->exists($name);
    }

    public function __unset($name) {
        $this->delete($name);
    }

    public function offsetExists($key) {
        return $this->exists($key);
    }

    public function offsetGet($key) {
        return $this->make($key);
    }

    public function offsetSet($key, $value) {
        $this->bind($key, $value);
    }

    public function offsetUnset($key) {
        $this->delete($key);
    }

    //Countable
    public function count() {
        return count($this->instances);
    }

    //IteratorAggregate
    public function getIterator() {
        return new ArrayIterator($this->instances);
    }
}

2.2 think\app.php 源码分析

<?php
namespace think;

use think\event\AppInit;
use think\helper\Str;
use think\initializer\BootService;
use think\initializer\Error;
use think\initializer\RegisterService;

/**
 * App 基础类
 * @property Route $route
 * @property Config $config
 * @property Cache $cache
 * @property Request $request
 * @property Http $http
 * @property Console $console
 * @property Env $env
 * @property Event $event
 * @property Middleware $middleware
 * @property Log $log
 * @property Lang $lang
 * @property Db $db
 * @property Cookie $cookie
 * @property Session $session
 * @property Validate $validate
 * @property Filesystem $filesystem
 */
class App extends Container {
    const VERSION = '6.0.9';
    protected $appDebug = false;	//应用调试模式
    protected $envName = '';	//环境变量标识
    protected $beginTime;		//应用开始时间
    protected $beginMem;		//应用内存初始占用
    protected $namespace = 'app';	//当前应用类库命名空间
    protected $rootPath = '';		//应用根目录
    protected $thinkPath = '';		//框架目录
    protected $appPath = '';		//应用目录
    protected $runtimePath = '';		//Runtime目录
    protected $routePath = '';		//路由定义目录
    protected $configExt = '.php';		//配置后缀
    protected $initializers = [		//应用初始化器
        Error::class,
        RegisterService::class,
        BootService::class,
    ];
    protected $services = [];		//注册的系统服务
    protected $initialized = false;		//初始化
    protected $bind = [
        'app' => App::class,
        'cache' => Cache::class,
        'config' => Config::class,
        'console' => Console::class,
        'cookie' => Cookie::class,
        'db' => Db::class,
        'env' => Env::class,
        'event' => Event::class,
        'http' => Http::class,
        'lang' => Lang::class,
        'log' => Log::class,
        'middleware' => Middleware::class,
        'request' => Request::class,
        'response' => Response::class,
        'route' => Route::class,
        'session' => Session::class,
        'validate' => Validate::class,
        'view' => View::class,
        'filesystem' => Filesystem::class,
        'think\DbManager' => Db::class,
        'think\LogManager' => Log::class,
        'think\CacheManager' => Cache::class,
        'Psr\Log\LoggerInterface' => Log::class,  // 接口依赖注入
    ];

    //架构方法,$rootPath 应用根目录
    public function __construct(string $rootPath = '') {
        $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR;
        $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath();
        $this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
        $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR;

        if (is_file($this->appPath . 'provider.php')) {
            $this->bind(include $this->appPath . 'provider.php');
        }

        static::setInstance($this);

        $this->instance('app', $this);
        $this->instance('think\Container', $this);
    }

    //注册服务,$service 服务,$force 强制重新注册
    public function register($service, bool $force = false) {
        $registered = $this->getService($service);

        if ($registered && !$force) {
            return $registered;
        }

        if (is_string($service)) {
            $service = new $service($this);
        }

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

        if (property_exists($service, 'bind')) {
            $this->bind($service->bind);
        }

        $this->services[] = $service;
    }

    //执行服务,$service 服务
    public function bootService($service) {
        if (method_exists($service, 'boot')) {
            return $this->invoke([$service, 'boot']);
        }
    }

    //获取服务
    public function getService($service) {
        $name = is_string($service) ? $service : get_class($service);
        return array_values(array_filter($this->services, function ($value) use ($name) {
                return $value instanceof $name;
            }, ARRAY_FILTER_USE_BOTH))[0] ?? null;
    }

    //开启应用调试模式,$debug 开启应用调试模式
    public function debug(bool $debug = true) {
        $this->appDebug = $debug;
        return $this;
    }

    //是否为调试模式
    public function isDebug(): bool {
        return $this->appDebug;
    }

    //设置应用命名空间,$namespace 应用命名空间
    public function setNamespace(string $namespace) {
        $this->namespace = $namespace;
        return $this;
    }

    //获取应用类库命名空间
    public function getNamespace(): string {
        return $this->namespace;
    }

    //设置环境变量标识,$name 环境标识
    public function setEnvName(string $name) {
        $this->envName = $name;
        return $this;
    }

    //获取框架版本
    public function version(): string {
        return static::VERSION;
    }

    //获取应用根目录
    public function getRootPath(): string {
        return $this->rootPath;
    }

    //获取应用基础目录
    public function getBasePath(): string {
        return $this->rootPath . 'app' . DIRECTORY_SEPARATOR;
    }

    //获取当前应用目录
    public function getAppPath(): string {
        return $this->appPath;
    }

    //设置应用目录,$path 应用目录
    public function setAppPath(string $path) {
        $this->appPath = $path;
    }

    //获取应用运行时目录
    public function getRuntimePath(): string {
        return $this->runtimePath;
    }

    //设置runtime目录,$path 定义目录
    public function setRuntimePath(string $path): void {
        $this->runtimePath = $path;
    }

    //获取核心框架目录
    public function getThinkPath(): string {
        return $this->thinkPath;
    }

    //获取应用配置目录
    public function getConfigPath(): string {
        return $this->rootPath . 'config' . DIRECTORY_SEPARATOR;
    }

    //获取配置后缀
    public function getConfigExt(): string {
        return $this->configExt;
    }

    //获取应用开启时间
    public function getBeginTime(): float {
        return $this->beginTime;
    }

    //获取应用初始内存占用
    public function getBeginMem(): int {
        return $this->beginMem;
    }

    //加载环境变量定义,$envName 环境标识
    public function loadEnv(string $envName = ''): void {
        // 加载环境变量
        $envFile = $envName ? $this->rootPath . '.env.' . $envName : $this->rootPath . '.env';

        if (is_file($envFile)) {
            $this->env->load($envFile);
        }
    }

    //初始化应用
    public function initialize() {
        $this->initialized = true;
        $this->beginTime = microtime(true);
        $this->beginMem = memory_get_usage();
        $this->loadEnv($this->envName);
        $this->configExt = $this->env->get('config_ext', '.php');
        $this->debugModeInit();

        $this->load();	// 加载全局初始化文件

        $langSet = $this->lang->defaultLangSet();	// 加载框架默认语言包
        $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php');
        $this->loadLangPack($langSet);	// 加载应用默认语言包
        $this->event->trigger(AppInit::class);	// 监听AppInit

        date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai'));

        foreach ($this->initializers as $initializer) {	// 初始化
            $this->make($initializer)->init($this);
        }

        return $this;
    }

    //是否初始化过
    public function initialized() {
        return $this->initialized;
    }

    //加载语言包,$langset 语言
    public function loadLangPack($langset) {
        if (empty($langset)) {
            return;
        }

        // 加载系统语言包
        $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*');
        $this->lang->load($files);

        // 加载扩展(自定义)语言包
        $list = $this->config->get('lang.extend_list', []);

        if (isset($list[$langset])) {
            $this->lang->load($list[$langset]);
        }
    }

    //引导应用
    public function boot(): void {
        array_walk($this->services, function ($service) {
            $this->bootService($service);
        });
    }

    //加载应用文件和配置
    protected function load(): void {
        $appPath = $this->getAppPath();

        if (is_file($appPath . 'common.php')) {
            include_once $appPath . 'common.php';
        }

        include_once $this->thinkPath . 'helper.php';
        $configPath = $this->getConfigPath();

        $files = [];

        if (is_dir($configPath)) {
            $files = glob($configPath . '*' . $this->configExt);
        }

        foreach ($files as $file) {
            $this->config->load($file, pathinfo($file, PATHINFO_FILENAME));
        }

        if (is_file($appPath . 'event.php')) {
            $this->loadEvent(include $appPath . 'event.php');
        }

        if (is_file($appPath . 'service.php')) {
            $services = include $appPath . 'service.php';
            foreach ($services as $service) {
                $this->register($service);
            }
        }
    }

    //调试模式设置
    protected function debugModeInit(): void {
        // 应用调试模式
        if (!$this->appDebug) {
            $this->appDebug = $this->env->get('app_debug') ? true : false;
            ini_set('display_errors', 'Off');
        }

        if (!$this->runningInConsole()) {
            //重新申请一块比较大的buffer
            if (ob_get_level() > 0) {
                $output = ob_get_clean();
            }
            ob_start();
            if (!empty($output)) {
                echo $output;
            }
        }
    }

    //注册应用事件,$event 事件数据
    public function loadEvent(array $event): void {
        if (isset($event['bind'])) {
            $this->event->bind($event['bind']);
        }

        if (isset($event['listen'])) {
            $this->event->listenEvents($event['listen']);
        }

        if (isset($event['subscribe'])) {
            $this->event->subscribe($event['subscribe']);
        }
    }

    // 解析应用类的类名,$layer 层名 controller model ...,$name 类名
    public function parseClass(string $layer, string $name): string {
        $name = str_replace(['/', '.'], '\\', $name);
        $array = explode('\\', $name);
        $class = Str::studly(array_pop($array));
        $path = $array ? implode('\\', $array) . '\\' : '';

        return $this->namespace . '\\' . $layer . '\\' . $path . $class;
    }

    //是否运行在命令行下
     * @return bool
     */
    public function runningInConsole(): bool {
        return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg';
    }

    //获取应用根目录
    protected function getDefaultRootPath(): string {
        return dirname($this->thinkPath, 4) . DIRECTORY_SEPARATOR;
    }
}

三、设全局环境变量

四、获取 http 服务

4.1 think\http.php源码分析

<?php
declare (strict_types=1);

namespace think;

use think\event\HttpEnd;
use think\event\HttpRun;
use think\event\RouteLoaded;
use think\exception\Handle;
use Throwable;

/**
 * Web应用管理类
 */
class Http {

    protected $app;		//App
    protected $name;	//应用名称
    protected $path;	//应用路径
    protected $routePath;	//路由路径
    protected $isBind = false;	//是否绑定应用

    public function __construct(App $app) {
        $this->app = $app;
        $this->routePath = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR;
    }

    //设置应用名称,$name 应用名称
    public function name(string $name) {
        $this->name = $name;
        return $this;
    }

    //获取应用名称
    public function getName(): string {
        return $this->name ?: '';
    }

    //设置应用目录,$path 应用目录
    public function path(string $path) {
        if (substr($path, -1) != DIRECTORY_SEPARATOR) {
            $path .= DIRECTORY_SEPARATOR;
        }

        $this->path = $path;
        return $this;
    }

    //获取应用路径
    public function getPath(): string {
        return $this->path ?: '';
    }

    //获取路由目录
    public function getRoutePath(): string {
        return $this->routePath;
    }

    //设置路由目录,$path 路由定义目录
    public function setRoutePath(string $path): void {
        $this->routePath = $path;
    }

    //设置应用绑定,$bind 是否绑定
    public function setBind(bool $bind = true) {
        $this->isBind = $bind;
        return $this;
    }

    //是否绑定应用
    public function isBind(): bool {
        return $this->isBind;
    }

    //执行应用程序,$request
    public function run(Request $request = null): Response {
        
        $this->initialize();		//初始化

        $request = $request ?? $this->app->make('request', [], true);		//自动创建request对象
        $this->app->instance('request', $request);	//绑定 $request 到容器中的request中

        try {
            $response = $this->runWithRequest($request);
        } catch (Throwable $e) {
            $this->reportException($e);
            $response = $this->renderException($request, $e);
        }

        return $response;
    }

    //初始化
    protected function initialize() {
        if (!$this->app->initialized()) {
            $this->app->initialize();
        }
    }

    //执行应用程序
    protected function runWithRequest(Request $request) {
        
        $this->loadMiddleware();		// 加载全局中间件
        
        $this->app->event->trigger(HttpRun::class);		// 监听HttpRun

        return $this->app->middleware->pipeline()
            ->send($request)
            ->then(function ($request) {
                return $this->dispatchToRoute($request);
            });
    }

    protected function dispatchToRoute($request) {
        $withRoute = $this->app->config->get('app.with_route', true) ? function () {
            $this->loadRoutes();
        } : null;

        return $this->app->route->dispatch($request, $withRoute);
    }

    //加载全局中间件
    protected function loadMiddleware(): void {
        if (is_file($this->app->getBasePath() . 'middleware.php')) {
            $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php');
        }
    }

    //加载路由
    protected function loadRoutes(): void {
        // 加载路由定义
        $routePath = $this->getRoutePath();

        if (is_dir($routePath)) {
            $files = glob($routePath . '*.php');
            foreach ($files as $file) {
                include $file;
            }
        }

        $this->app->event->trigger(RouteLoaded::class);
    }

    //Report the exception to the exception handler.
    protected function reportException(Throwable $e) {
        $this->app->make(Handle::class)->report($e);
    }

    //Render the exception to a response.
    protected function renderException($request, Throwable $e) {
        return $this->app->make(Handle::class)->render($request, $e);
    }

    //HttpEnd
    public function end(Response $response): void {
        $this->app->event->trigger(HttpEnd::class, $response);
        //执行中间件
        $this->app->middleware->end($response);
        // 写入日志
        $this->app->log->save();
    }

}

http 执行流程

1, 执行http的run() 方法, run里执行三件事:http初始化,通过 $this->app->make(‘request’, [], true) 函数创建 r e q u e s t 对 象 , 将 request 对象,将 requestrequest对象与app() 容器当中的request实例绑定

2,在try……catch 中执行应用程序 runWithRequest(),里面执行三件事:加载全局中间件,通过 $this->app->event->trigger 来监听 httpRun 类,将 t h i s ? > a p p ? > m i d d l e w a r e ? > p i p e l i n e ( ) ? > s e n d ( this->app->middleware->pipeline()->send( this?>app?>middleware?>pipeline()?>send(request)->then(function($requeszt){return t h i s ? > d i s p a t c h T o R o u t e ( this->dispatchToRoute( this?>dispatchToRoute(request)}) 结果集返回

3, 响应结果集,返回 $response;

在第2步的匿名方法中,dispatchToRoute() 会通过app->config->get() 方法,加载 app.with_route 路由开启配置,如果路由开启了,则去执行匿名方法,匿名方法内通过 $this->loadRoutes() 来加载路由,反之则不加载

4.2 think\request.php源码分析

<?php
namespace think;

use ArrayAccess;
use think\file\UploadedFile;
use think\route\Rule;

/**
* 请求管理类
* @package think
*/
class Request implements ArrayAccess {
   //兼容PATH_INFO获取
   protected $pathinfoFetch = ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'];
   protected $varPathinfo = 's';		//PATHINFO变量名 用于兼容模式
   protected $varMethod = '_method';		//请求类型
   protected $varAjax = '_ajax';		//表单ajax伪装变量
   protected $varPjax = '_pjax';		//表单pjax伪装变量
   protected $rootDomain = '';		//域名根
   protected $httpsAgentName = '';		//HTTPS代理标识
   protected $proxyServerIp = [];		//前端代理服务器IP
   //前端代理服务器真实IP头
   protected $proxyServerIpHeader = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP'];

   protected $method;		//请求类型
   protected $domain;		//域名(含协议及端口)
   protected $host;		//HOST(含端口)
   protected $subDomain;		//子域名
   protected $panDomain;		//泛域名
   protected $url;		//当前URL地址
   protected $baseUrl;		//基础URL
   protected $baseFile;		//当前执行的文件
   protected $root;		//访问的ROOT地址
   protected $pathinfo;		//pathinfo
   protected $path;	//pathinfo(不含后缀)
   protected $realIP;	//当前请求的IP地址
   protected $controller;		//当前控制器名
   protected $action;		//当前操作名
   protected $param = [];	 //当前请求参数
   protected $get = [];	//当前GET参数
   protected $post = [];    //当前POST参数
   protected $request = [];    //当前REQUEST参数
   protected $rule;    //当前路由对象
   protected $route = [];    //当前ROUTE参数
   protected $middleware = [];    //中间件传递的参数
   protected $put;    //当前PUT参数
   protected $session;    //SESSION对象
   protected $cookie = [];    //COOKIE数据
   protected $env;    //ENV对象
   protected $server = [];    //当前SERVER参数
   protected $file = [];    //当前FILE参数
   protected $header = [];    //当前HEADER参数
   //资源类型定义
   protected $mimeType = [
       'xml' => 'application/xml,text/xml,application/x-xml',
       'json' => 'application/json,text/x-json,application/jsonrequest,text/json',
       'js' => 'text/javascript,application/javascript,application/x-javascript',
       'css' => 'text/css',
       'rss' => 'application/rss+xml',
       'yaml' => 'application/x-yaml,text/yaml',
       'atom' => 'application/atom+xml',
       'pdf' => 'application/pdf',
       'text' => 'text/plain',
       'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*',
       'csv' => 'text/csv',
       'html' => 'text/html,application/xhtml+xml,*/*',
   ];
   protected $content;    //当前请求内容
   protected $filter;    //全局过滤规则
   protected $input;    //php://input内容
   protected $secureKey;    //请求安全Key
   protected $mergeParam = false;    //是否合并Param

   //架构函数
   public function __construct() {
       // 保存 php://input
       $this->input = file_get_contents('php://input');
   }

   public static function __make(App $app) {
       $request = new static();

       if (function_exists('apache_request_headers') && $result = apache_request_headers()) {
           $header = $result;
       } else {
           $header = [];
           $server = $_SERVER;
           foreach ($server as $key => $val) {
               if (0 === strpos($key, 'HTTP_')) {
                   $key = str_replace('_', '-', strtolower(substr($key, 5)));
                   $header[$key] = $val;
               }
           }
           if (isset($server['CONTENT_TYPE'])) {
               $header['content-type'] = $server['CONTENT_TYPE'];
           }
           if (isset($server['CONTENT_LENGTH'])) {
               $header['content-length'] = $server['CONTENT_LENGTH'];
           }
       }

       $request->header = array_change_key_case($header);
       $request->server = $_SERVER;
       $request->env = $app->env;

       $inputData = $request->getInputData($request->input);

       $request->get = $_GET;
       $request->post = $_POST ?: $inputData;
       $request->put = $inputData;
       $request->request = $_REQUEST;
       $request->cookie = $_COOKIE;
       $request->file = $_FILES ?? [];

       return $request;
   }

   //设置当前包含协议的域名,$domain 域名
   public function setDomain(string $domain) {
       $this->domain = $domain;
       return $this;
   }

   //获取当前包含协议的域名,$port 是否需要去除端口号
   public function domain(bool $port = false): string {
       return $this->scheme() . '://' . $this->host($port);
   }

   //获取当前根域名
   public function rootDomain(): string {
       $root = $this->rootDomain;

       if (!$root) {
           $item = explode('.', $this->host());
           $count = count($item);
           $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0];
       }

       return $root;
   }

   //设置当前泛域名的值,$domain 域名
   public function setSubDomain(string $domain) {
       $this->subDomain = $domain;
       return $this;
   }

   //获取当前子域名
   public function subDomain(): string {
       if (is_null($this->subDomain)) {
           // 获取当前主域名
           $rootDomain = $this->rootDomain();

           if ($rootDomain) {
               $sub = stristr($this->host(), $rootDomain, true);
               $this->subDomain = $sub ? rtrim($sub, '.') : '';
           } else {
               $this->subDomain = '';
           }
       }

       return $this->subDomain;
   }

   //设置当前泛域名的值,$domain 域名
   public function setPanDomain(string $domain) {
       $this->panDomain = $domain;
       return $this;
   }

   //获取当前泛域名的值
   public function panDomain(): string {
       return $this->panDomain ?: '';
   }

   //设置当前完整URL 包括QUERY_STRING,$url URL地址
   public function setUrl(string $url) {
       $this->url = $url;
       return $this;
   }

   //获取当前完整URL 包括QUERY_STRING,$complete 是否包含完整域名
   public function url(bool $complete = false): string {
       if ($this->url) {
           $url = $this->url;
       } elseif ($this->server('HTTP_X_REWRITE_URL')) {
           $url = $this->server('HTTP_X_REWRITE_URL');
       } elseif ($this->server('REQUEST_URI')) {
           $url = $this->server('REQUEST_URI');
       } elseif ($this->server('ORIG_PATH_INFO')) {
           $url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : '');
       } elseif (isset($_SERVER['argv'][1])) {
           $url = $_SERVER['argv'][1];
       } else {
           $url = '';
       }

       return $complete ? $this->domain() . $url : $url;
   }

   //设置当前URL 不含QUERY_STRING,$url URL地址
   public function setBaseUrl(string $url) {
       $this->baseUrl = $url;
       return $this;
   }

   //获取当前URL 不含QUERY_STRING,$complete 是否包含完整域名
   public function baseUrl(bool $complete = false): string {
       if (!$this->baseUrl) {
           $str = $this->url();
           $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str;
       }

       return $complete ? $this->domain() . $this->baseUrl : $this->baseUrl;
   }

   //获取当前执行的文件 SCRIPT_NAME,$complete 是否包含完整域名
   public function baseFile(bool $complete = false): string {
       if (!$this->baseFile) {
           $url = '';
           if (!$this->isCli()) {
               $script_name = basename($this->server('SCRIPT_FILENAME'));
               if (basename($this->server('SCRIPT_NAME')) === $script_name) {
                   $url = $this->server('SCRIPT_NAME');
               } elseif (basename($this->server('PHP_SELF')) === $script_name) {
                   $url = $this->server('PHP_SELF');
               } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) {
                   $url = $this->server('ORIG_SCRIPT_NAME');
               } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) {
                   $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name;
               } elseif ($this->server('DOCUMENT_ROOT') && strpos($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT')) === 0) {
                   $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME')));
               }
           }
           $this->baseFile = $url;
       }

       return $complete ? $this->domain() . $this->baseFile : $this->baseFile;
   }

   //设置URL访问根地址,$url URL地址
   public function setRoot(string $url) {
       $this->root = $url;
       return $this;
   }

   //获取URL访问根地址,$complete 是否包含完整域名
   public function root(bool $complete = false): string {
       if (!$this->root) {
           $file = $this->baseFile();
           if ($file && 0 !== strpos($this->url(), $file)) {
               $file = str_replace('\\', '/', dirname($file));
           }
           $this->root = rtrim($file, '/');
       }

       return $complete ? $this->domain() . $this->root : $this->root;
   }

   //获取URL访问根目录
   public function rootUrl(): string {
       $base = $this->root();
       $root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base;

       if ('' != $root) {
           $root = '/' . ltrim($root, '/');
       }

       return $root;
   }

   //设置当前请求的pathinfo,$pathinfo
   public function setPathinfo(string $pathinfo) {
       $this->pathinfo = $pathinfo;
       return $this;
   }

   //获取当前请求URL的pathinfo信息(含URL后缀)
   public function pathinfo(): string {
       if (is_null($this->pathinfo)) {
           if (isset($_GET[$this->varPathinfo])) {
               // 判断URL里面是否有兼容模式参数
               $pathinfo = $_GET[$this->varPathinfo];
               unset($_GET[$this->varPathinfo]);
               unset($this->get[$this->varPathinfo]);
           } elseif ($this->server('PATH_INFO')) {
               $pathinfo = $this->server('PATH_INFO');
           } elseif (false !== strpos(PHP_SAPI, 'cli')) {
               $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI');
           }

           // 分析PATHINFO信息
           if (!isset($pathinfo)) {
               foreach ($this->pathinfoFetch as $type) {
                   if ($this->server($type)) {
                       $pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ?
                           substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type);
                       break;
                   }
               }
           }

           if (!empty($pathinfo)) {
               unset($this->get[$pathinfo], $this->request[$pathinfo]);
           }

           $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/');
       }

       return $this->pathinfo;
   }

   //当前URL的访问后缀
   public function ext(): string {
       return pathinfo($this->pathinfo(), PATHINFO_EXTENSION);
   }

   //获取当前请求的时间,$float 是否使用浮点类型
   public function time(bool $float = false) {
       return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME');
   }

   //当前请求的资源类型
   public function type(): string {
       $accept = $this->server('HTTP_ACCEPT');

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

       foreach ($this->mimeType as $key => $val) {
           $array = explode(',', $val);
           foreach ($array as $k => $v) {
               if (stristr($accept, $v)) {
                   return $key;
               }
           }
       }

       return '';
   }

   //设置资源类型,$type 资源类型名,$val 资源类型
   public function mimeType($type, $val = ''): void {
       if (is_array($type)) {
           $this->mimeType = array_merge($this->mimeType, $type);
       } else {
           $this->mimeType[$type] = $val;
       }
   }

   //设置请求类型,$method 请求类型
   public function setMethod(string $method) {
       $this->method = strtoupper($method);
       return $this;
   }

   //当前的请求类型,$origin 是否获取原始请求类型
   public function method(bool $origin = false): string {
       if ($origin) {
           // 获取原始请求类型
           return $this->server('REQUEST_METHOD') ?: 'GET';
       } elseif (!$this->method) {
           if (isset($this->post[$this->varMethod])) {
               $method = strtolower($this->post[$this->varMethod]);
               if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) {
                   $this->method = strtoupper($method);
                   $this->{$method} = $this->post;
               } else {
                   $this->method = 'POST';
               }
               unset($this->post[$this->varMethod]);
           } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) {
               $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE'));
           } else {
               $this->method = $this->server('REQUEST_METHOD') ?: 'GET';
           }
       }

       return $this->method;
   }

   //是否为GET请求
   public function isGet(): bool {
       return $this->method() == 'GET';
   }

   //是否为POST请求
   public function isPost(): bool {
       return $this->method() == 'POST';
   }

   //是否为PUT请求
   public function isPut(): bool {
       return $this->method() == 'PUT';
   }

   //是否为DELTE请求
   public function isDelete(): bool {
       return $this->method() == 'DELETE';
   }

   //是否为HEAD请求
   public function isHead(): bool {
       return $this->method() == 'HEAD';
   }

   //是否为PATCH请求
   public function isPatch(): bool {
       return $this->method() == 'PATCH';
   }

   //是否为OPTIONS请求
   public function isOptions(): bool {
       return $this->method() == 'OPTIONS';
   }

   //是否为cli
   public function isCli(): bool {
       return PHP_SAPI == 'cli';
   }

   //是否为cgi
   public function isCgi(): bool {
       return strpos(PHP_SAPI, 'cgi') === 0;
   }

   //获取当前请求的参数,$name 变量名,$default 默认值,$filter 过滤方法
   public function param($name = '', $default = null, $filter = '') {
       if (empty($this->mergeParam)) {
           $method = $this->method(true);

           // 自动获取请求变量
           switch ($method) {
               case 'POST':
                   $vars = $this->post(false);
                   break;
               case 'PUT':
               case 'DELETE':
               case 'PATCH':
                   $vars = $this->put(false);
                   break;
               default:
                   $vars = [];
           }

           // 当前请求参数和URL地址中的参数合并
           $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));

           $this->mergeParam = true;
       }

       if (is_array($name)) {
           return $this->only($name, $this->param, $filter);
       }

       return $this->input($this->param, $name, $default, $filter);
   }

   //获取包含文件在内的请求参数,$name 变量名,$filter 过滤方法
   public function all($name = '', $filter = '') {
       $data = array_merge($this->param(), $this->file() ?: []);

       if (is_array($name)) {
           $data = $this->only($name, $data, $filter);
       } elseif ($name) {
           $data = $data[$name] ?? null;
       }

       return $data;
   }

   //设置路由变量,$rule 路由对象
   public function setRule(Rule $rule) {
       $this->rule = $rule;
       return $this;
   }

   //获取当前路由对象
   public function rule() {
       return $this->rule;
   }

   //设置路由变量,$route 路由变量
   public function setRoute(array $route) {
       $this->route = array_merge($this->route, $route);
       $this->mergeParam = false;
       return $this;
   }

   //获取路由参数,$name 变量名,$default 默认值,$filter 过滤方法
   public function route($name = '', $default = null, $filter = '') {
       if (is_array($name)) {
           return $this->only($name, $this->route, $filter);
       }

       return $this->input($this->route, $name, $default, $filter);
   }

   //获取GET参数,$name 变量名,$default 默认值,$filter 过滤方法
   public function get($name = '', $default = null, $filter = '') {
       if (is_array($name)) {
           return $this->only($name, $this->get, $filter);
       }

       return $this->input($this->get, $name, $default, $filter);
   }

   //获取中间件传递的参数,$name 变量名,$default 默认值
   public function middleware($name, $default = null) {
       return $this->middleware[$name] ?? $default;
   }

   //获取POST参数,$name 变量名,$default 默认值,$filter 过滤方法
   public function post($name = '', $default = null, $filter = '') {
       if (is_array($name)) {
           return $this->only($name, $this->post, $filter);
       }

       return $this->input($this->post, $name, $default, $filter);
   }

   //获取PUT参数,$name 变量名,$default 默认值,$filter 过滤方法
   public function put($name = '', $default = null, $filter = '') {
       if (is_array($name)) {
           return $this->only($name, $this->put, $filter);
       }

       return $this->input($this->put, $name, $default, $filter);
   }

   protected function getInputData($content): array {
       $contentType = $this->contentType();
       if ('application/x-www-form-urlencoded' == $contentType) {
           parse_str($content, $data);
           return $data;
       } elseif (false !== strpos($contentType, 'json')) {
           return (array)json_decode($content, true);
       }

       return [];
   }

   //设置获取DELETE参数,$name 变量名,$default 默认值,$filter 过滤方法
   public function delete($name = '', $default = null, $filter = '') {
       return $this->put($name, $default, $filter);
   }

   //设置获取PATCH参数,$name 变量名,$default 默认值,$filter 过滤方法
   public function patch($name = '', $default = null, $filter = '') {
       return $this->put($name, $default, $filter);
   }

   //获取request变量,$name 数据名称,$default 默认值,$filter 过滤方法
   public function request($name = '', $default = null, $filter = '') {
       if (is_array($name)) {
           return $this->only($name, $this->request, $filter);
       }

       return $this->input($this->request, $name, $default, $filter);
   }

   //获取环境变量,$name 数据名称,$default 默认值
   public function env(string $name = '', string $default = null) {
       if (empty($name)) {
           return $this->env->get();
       } else {
           $name = strtoupper($name);
       }

       return $this->env->get($name, $default);
   }

   //获取session数据,$name 数据名称,$default 默认值
   public function session(string $name = '', $default = null) {
       if ('' === $name) {
           return $this->session->all();
       }
       return $this->session->get($name, $default);
   }

   //获取cookie参数,$name 数据名称,$default 默认值,$filter 过滤方法
   public function cookie(string $name = '', $default = null, $filter = '') {
       if (!empty($name)) {
           $data = $this->getData($this->cookie, $name, $default);
       } else {
           $data = $this->cookie;
       }

       // 解析过滤器
       $filter = $this->getFilter($filter, $default);

       if (is_array($data)) {
           array_walk_recursive($data, [$this, 'filterValue'], $filter);
       } else {
           $this->filterValue($data, $name, $filter);
       }

       return $data;
   }

   //获取server参数,$name 数据名称,$default 默认值
   public function server(string $name = '', string $default = '') {
       if (empty($name)) {
           return $this->server;
       } else {
           $name = strtoupper($name);
       }

       return $this->server[$name] ?? $default;
   }

   //获取上传的文件信息,$name 名称,@return null|array|UploadedFile
   public function file(string $name = '') {
       $files = $this->file;
       if (!empty($files)) {
           if (strpos($name, '.')) {
               [$name, $sub] = explode('.', $name);
           }

           // 处理上传文件
           $array = $this->dealUploadFile($files, $name);

           if ('' === $name) {
               // 获取全部文件
               return $array;
           } elseif (isset($sub) && isset($array[$name][$sub])) {
               return $array[$name][$sub];
           } elseif (isset($array[$name])) {
               return $array[$name];
           }
       }
   }

   protected function dealUploadFile(array $files, string $name): array {
       $array = [];
       foreach ($files as $key => $file) {
           if (is_array($file['name'])) {
               $item = [];
               $keys = array_keys($file);
               $count = count($file['name']);

               for ($i = 0; $i < $count; $i++) {
                   if ($file['error'][$i] > 0) {
                       if ($name == $key) {
                           $this->throwUploadFileError($file['error'][$i]);
                       } else {
                           continue;
                       }
                   }

                   $temp['key'] = $key;

                   foreach ($keys as $_key) {
                       $temp[$_key] = $file[$_key][$i];
                   }

                   $item[] = new UploadedFile($temp['tmp_name'], $temp['name'], $temp['type'], $temp['error']);
               }

               $array[$key] = $item;
           } else {
               if ($file instanceof File) {
                   $array[$key] = $file;
               } else {
                   if ($file['error'] > 0) {
                       if ($key == $name) {
                           $this->throwUploadFileError($file['error']);
                       } else {
                           continue;
                       }
                   }

                   $array[$key] = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error']);
               }
           }
       }

       return $array;
   }

   protected function throwUploadFileError($error) {
       static $fileUploadErrors = [
           1 => 'upload File size exceeds the maximum value',
           2 => 'upload File size exceeds the maximum value',
           3 => 'only the portion of file is uploaded',
           4 => 'no file to uploaded',
           6 => 'upload temp dir not found',
           7 => 'file write error',
       ];

       $msg = $fileUploadErrors[$error];
       throw new Exception($msg, $error);
   }

   //设置或者获取当前的Header,$name header名称,$default 默认值
   public function header(string $name = '', string $default = null) {
       if ('' === $name) {
           return $this->header;
       }

       $name = str_replace('_', '-', strtolower($name));

       return $this->header[$name] ?? $default;
   }

   //获取变量 支持过滤和默认值,$data 数据源,$name 字段名,$default 默认值,$filter 过滤函数
   public function input(array $data = [], $name = '', $default = null, $filter = '') {
       if (false === $name) {
           // 获取原始数据
           return $data;
       }

       $name = (string)$name;
       if ('' != $name) {
           // 解析name
           if (strpos($name, '/')) {
               [$name, $type] = explode('/', $name);
           }

           $data = $this->getData($data, $name);

           if (is_null($data)) {
               return $default;
           }

           if (is_object($data)) {
               return $data;
           }
       }

       $data = $this->filterData($data, $filter, $name, $default);

       if (isset($type) && $data !== $default) {
           // 强制类型转换
           $this->typeCast($data, $type);
       }

       return $data;
   }

   protected function filterData($data, $filter, $name, $default) {
       // 解析过滤器
       $filter = $this->getFilter($filter, $default);

       if (is_array($data)) {
           array_walk_recursive($data, [$this, 'filterValue'], $filter);
       } else {
           $this->filterValue($data, $name, $filter);
       }

       return $data;
   }

   /**
    * 强制类型转换,$data,$type
   protected function typeCast(&$data, string $type) {
       switch (strtolower($type)) {
           // 数组
           case 'a':
               $data = (array)$data;
               break;
           // 数字
           case 'd':
               $data = (int)$data;
               break;
           // 浮点
           case 'f':
               $data = (float)$data;
               break;
           // 布尔
           case 'b':
               $data = (boolean)$data;
               break;
           // 字符串
           case 's':
               if (is_scalar($data)) {
                   $data = (string)$data;
               } else {
                   throw new \InvalidArgumentException('variable type error:' . gettype($data));
               }
               break;
       }
   }

   //获取数据,$data 数据源,$name 字段名,$default 默认值
   protected function getData(array $data, string $name, $default = null) {
       foreach (explode('.', $name) as $val) {
           if (isset($data[$val])) {
               $data = $data[$val];
           } else {
               return $default;
           }
       }

       return $data;
   }

   //设置或获取当前的过滤规则,$filter 过滤规则
   public function filter($filter = null) {
       if (is_null($filter)) {
           return $this->filter;
       }

       $this->filter = $filter;

       return $this;
   }

   protected function getFilter($filter, $default): array {
       if (is_null($filter)) {
           $filter = [];
       } else {
           $filter = $filter ?: $this->filter;
           if (is_string($filter) && false === strpos($filter, '/')) {
               $filter = explode(',', $filter);
           } else {
               $filter = (array)$filter;
           }
       }

       $filter[] = $default;

       return $filter;
   }

   //递归过滤给定的值,$value 键值,$key 键名,$filters 过滤方法+默认值
   public function filterValue(&$value, $key, $filters) {
       $default = array_pop($filters);

       foreach ($filters as $filter) {
           if (is_callable($filter)) {
               // 调用函数或者方法过滤
               $value = call_user_func($filter, $value);
           } elseif (is_scalar($value)) {
               if (is_string($filter) && false !== strpos($filter, '/')) {
                   // 正则过滤
                   if (!preg_match($filter, $value)) {
                       // 匹配不成功返回默认值
                       $value = $default;
                       break;
                   }
               } elseif (!empty($filter)) {
                   // filter函数不存在时, 则使用filter_var进行过滤
                   // filter为非整形值时, 调用filter_id取得过滤id
                   $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter));
                   if (false === $value) {
                       $value = $default;
                       break;
                   }
               }
           }
       }

       return $value;
   }

   //是否存在某个请求参数,$name 变量名,$type 变量类型,$checkEmpty 是否检测空值
   public function has(string $name, string $type = 'param', bool $checkEmpty = false): bool {
       if (!in_array($type, ['param', 'get', 'post', 'put', 'patch', 'route', 'delete', 'cookie', 'session', 'env', 'request', 'server', 'header', 'file'])) {
           return false;
       }

       $param = empty($this->$type) ? $this->$type() : $this->$type;

       if (is_object($param)) {
           return $param->has($name);
       }

       // 按.拆分成多维数组进行判断
       foreach (explode('.', $name) as $val) {
           if (isset($param[$val])) {
               $param = $param[$val];
           } else {
               return false;
           }
       }

       return ($checkEmpty && '' === $param) ? false : true;
   }

   //获取指定的参数,$name 变量名,$data 数据或者变量类型,$filter 过滤方法
   public function only(array $name, $data = 'param', $filter = ''): array {
       $data = is_array($data) ? $data : $this->$data();

       $item = [];
       foreach ($name as $key => $val) {

           if (is_int($key)) {
               $default = null;
               $key = $val;
               if (!isset($data[$key])) {
                   continue;
               }
           } else {
               $default = $val;
           }

           $item[$key] = $this->filterData($data[$key] ?? $default, $filter, $key, $default);
       }

       return $item;
   }

   //排除指定参数获取,$name 变量名,$type 变量类型
   public function except(array $name, string $type = 'param'): array {
       $param = $this->$type();

       foreach ($name as $key) {
           if (isset($param[$key])) {
               unset($param[$key]);
           }
       }

       return $param;
   }

   //当前是否ssl
   public function isSsl(): bool {
       if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) {
           return true;
       } elseif ('https' == $this->server('REQUEST_SCHEME')) {
           return true;
       } elseif ('443' == $this->server('SERVER_PORT')) {
           return true;
       } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) {
           return true;
       } elseif ($this->httpsAgentName && $this->server($this->httpsAgentName)) {
           return true;
       }

       return false;
   }

   //当前是否JSON请求
   public function isJson(): bool {
       $acceptType = $this->type();

       return false !== strpos($acceptType, 'json');
   }

   //当前是否Ajax请求,$ajax true 获取原始ajax请求
   public function isAjax(bool $ajax = false): bool {
       $value = $this->server('HTTP_X_REQUESTED_WITH');
       $result = $value && 'xmlhttprequest' == strtolower($value) ? true : false;

       if (true === $ajax) {
           return $result;
       }

       return $this->param($this->varAjax) ? true : $result;
   }

   //当前是否Pjax请求,$pjax true 获取原始pjax请求
   public function isPjax(bool $pjax = false): bool {
       $result = !empty($this->server('HTTP_X_PJAX')) ? true : false;

       if (true === $pjax) {
           return $result;
       }

       return $this->param($this->varPjax) ? true : $result;
   }

   //获取客户端IP地址
   public function ip(): string {
       if (!empty($this->realIP)) {
           return $this->realIP;
       }

       $this->realIP = $this->server('REMOTE_ADDR', '');

       // 如果指定了前端代理服务器IP以及其会发送的IP头
       // 则尝试获取前端代理服务器发送过来的真实IP
       $proxyIp = $this->proxyServerIp;
       $proxyIpHeader = $this->proxyServerIpHeader;

       if (count($proxyIp) > 0 && count($proxyIpHeader) > 0) {
           // 从指定的HTTP头中依次尝试获取IP地址
           // 直到获取到一个合法的IP地址
           foreach ($proxyIpHeader as $header) {
               $tempIP = $this->server($header);

               if (empty($tempIP)) {
                   continue;
               }

               $tempIP = trim(explode(',', $tempIP)[0]);

               if (!$this->isValidIP($tempIP)) {
                   $tempIP = null;
               } else {
                   break;
               }
           }

           // tempIP不为空,说明获取到了一个IP地址
           // 这时我们检查 REMOTE_ADDR 是不是指定的前端代理服务器之一
           // 如果是的话说明该 IP头 是由前端代理服务器设置的
           // 否则则是伪装的
           if (!empty($tempIP)) {
               $realIPBin = $this->ip2bin($this->realIP);

               foreach ($proxyIp as $ip) {
                   $serverIPElements = explode('/', $ip);
                   $serverIP = $serverIPElements[0];
                   $serverIPPrefix = $serverIPElements[1] ?? 128;
                   $serverIPBin = $this->ip2bin($serverIP);

                   // IP类型不符
                   if (strlen($realIPBin) !== strlen($serverIPBin)) {
                       continue;
                   }

                   if (strncmp($realIPBin, $serverIPBin, (int)$serverIPPrefix) === 0) {
                       $this->realIP = $tempIP;
                       break;
                   }
               }
           }
       }

       if (!$this->isValidIP($this->realIP)) {
           $this->realIP = '0.0.0.0';
       }

       return $this->realIP;
   }

   //检测是否是合法的IP地址,$ip IP地址,$type IP地址类型 (ipv4, ipv6)
   public function isValidIP(string $ip, string $type = ''): bool {
       switch (strtolower($type)) {
           case 'ipv4':
               $flag = FILTER_FLAG_IPV4;
               break;
           case 'ipv6':
               $flag = FILTER_FLAG_IPV6;
               break;
           default:
               $flag = 0;
               break;
       }

       return boolval(filter_var($ip, FILTER_VALIDATE_IP, $flag));
   }

   //将IP地址转换为二进制字符串,string $ip
   public function ip2bin(string $ip): string {
       if ($this->isValidIP($ip, 'ipv6')) {
           $IPHex = str_split(bin2hex(inet_pton($ip)), 4);
           foreach ($IPHex as $key => $value) {
               $IPHex[$key] = intval($value, 16);
           }
           $IPBin = vsprintf('%016b%016b%016b%016b%016b%016b%016b%016b', $IPHex);
       } else {
           $IPHex = str_split(bin2hex(inet_pton($ip)), 2);
           foreach ($IPHex as $key => $value) {
               $IPHex[$key] = intval($value, 16);
           }
           $IPBin = vsprintf('%08b%08b%08b%08b', $IPHex);
       }

       return $IPBin;
   }

   // 检测是否使用手机访问
    * @access public
    * @return bool
    */
   public function isMobile(): bool {
       if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) {
           return true;
       } elseif ($this->server('HTTP_ACCEPT') && strpos(strtoupper($this->server('HTTP_ACCEPT')), "VND.WAP.WML")) {
           return true;
       } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) {
           return true;
       } elseif ($this->server('HTTP_USER_AGENT') && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $this->server('HTTP_USER_AGENT'))) {
           return true;
       }

       return false;
   }

   //当前URL地址中的scheme参数
   public function scheme(): string {
       return $this->isSsl() ? 'https' : 'http';
   }

   //当前请求URL地址中的query参数
   public function query(): string {
       return $this->server('QUERY_STRING', '');
   }

   //设置当前请求的host(包含端口),$host 主机名(含端口)
   public function setHost(string $host) {
       $this->host = $host;

       return $this;
   }

   //当前请求的host,$strict true 仅仅获取HOST
   public function host(bool $strict = false): string {
       if ($this->host) {
           $host = $this->host;
       } else {
           $host = strval($this->server('HTTP_X_FORWARDED_HOST') ?: $this->server('HTTP_HOST'));
       }

       return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host;
   }

   //当前请求URL地址中的port参数
   public function port(): int {
       return (int)($this->server('HTTP_X_FORWARDED_PORT') ?: $this->server('SERVER_PORT', ''));
   }

   //SERVER_PROTOCOL
   public function protocol(): string {
       return $this->server('SERVER_PROTOCOL', '');
   }

   //当前请求 REMOTE_PORT
   public function remotePort(): int {
       return (int)$this->server('REMOTE_PORT', '');
   }

   //当前请求 HTTP_CONTENT_TYPE
   public function contentType(): string {
       $contentType = $this->header('Content-Type');

       if ($contentType) {
           if (strpos($contentType, ';')) {
               [$type] = explode(';', $contentType);
           } else {
               $type = $contentType;
           }
           return trim($type);
       }

       return '';
   }

   //获取当前请求的安全Key
   public function secureKey(): string {
       if (is_null($this->secureKey)) {
           $this->secureKey = uniqid('', true);
       }

       return $this->secureKey;
   }

   //设置当前的控制器名,$controller 控制器名
   public function setController(string $controller) {
       $this->controller = $controller;
       return $this;
   }

   //设置当前的操作名,$action 操作名
   public function setAction(string $action) {
       $this->action = $action;
       return $this;
   }

   //获取当前的控制器名,$convert 转换为小写
   public function controller(bool $convert = false): string {
       $name = $this->controller ?: '';
       return $convert ? strtolower($name) : $name;
   }

   //获取当前的操作名,$convert 转换为小写
   public function action(bool $convert = false): string {
       $name = $this->action ?: '';
       return $convert ? strtolower($name) : $name;
   }

   //设置或者获取当前请求的content
   public function getContent(): string {
       if (is_null($this->content)) {
           $this->content = $this->input;
       }

       return $this->content;
   }

   //获取当前请求的php://input
   public function getInput(): string {
       return $this->input;
   }

   //生成请求令牌,$name 令牌名称,$type 令牌生成方法
   public function buildToken(string $name = '__token__', $type = 'md5'): string {
       $type = is_callable($type) ? $type : 'md5';
       $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT'));

       $this->session->set($name, $token);

       return $token;
   }

   //检查请求令牌,$token 令牌名称,$data 表单数据
   public function checkToken(string $token = '__token__', array $data = []): bool {
       if (in_array($this->method(), ['GET', 'HEAD', 'OPTIONS'], true)) {
           return true;
       }

       if (!$this->session->has($token)) {
           // 令牌数据无效
           return false;
       }

       // Header验证
       if ($this->header('X-CSRF-TOKEN') && $this->session->get($token) === $this->header('X-CSRF-TOKEN')) {
           // 防止重复提交
           $this->session->delete($token); // 验证完成销毁session
           return true;
       }

       if (empty($data)) {
           $data = $this->post();
       }

       // 令牌验证
       if (isset($data[$token]) && $this->session->get($token) === $data[$token]) {
           // 防止重复提交
           $this->session->delete($token); // 验证完成销毁session
           return true;
       }

       // 开启TOKEN重置
       $this->session->delete($token);
       return false;
   }

   //设置在中间件传递的数据,$middleware 数据
   public function withMiddleware(array $middleware) {
       $this->middleware = array_merge($this->middleware, $middleware);
       return $this;
   }

   //设置GET数据,$get 数据
   public function withGet(array $get) {
       $this->get = $get;
       return $this;
   }

   //设置POST数据,$post 数据
   public function withPost(array $post) {
       $this->post = $post;
       return $this;
   }

   //设置COOKIE数据,$cookie 数据
   public function withCookie(array $cookie) {
       $this->cookie = $cookie;
       return $this;
   }

   //设置SESSION数据,$session 数据
   public function withSession(Session $session) {
       $this->session = $session;
       return $this;
   }

   //设置SERVER数据,$server 数据
   public function withServer(array $server) {
       $this->server = array_change_key_case($server, CASE_UPPER);
       return $this;
   }

   //设置HEADER数据,$header 数据
   public function withHeader(array $header) {
       $this->header = array_change_key_case($header);
       return $this;
   }

   //设置ENV数据,Env $env 数据
   public function withEnv(Env $env) {
       $this->env = $env;
       return $this;
   }

   //设置php://input数据,$input RAW数据
   public function withInput(string $input) {
       $this->input = $input;
       if (!empty($input)) {
           $inputData = $this->getInputData($input);
           if (!empty($inputData)) {
               $this->post = $inputData;
               $this->put = $inputData;
           }
       }
       return $this;
   }

   //设置文件上传数据,$files 上传信息
   public function withFiles(array $files) {
       $this->file = $files;
       return $this;
   }

   //设置ROUTE变量,$route 数据
   public function withRoute(array $route) {
       $this->route = $route;
       return $this;
   }

   //设置中间传递数据,$name 参数名,$value 值
   public function __set(string $name, $value) {
       $this->middleware[$name] = $value;
   }

   //获取中间传递数据的值,$name 名称
   public function __get(string $name) {
       return $this->middleware($name);
   }

   //检测中间传递数据的值,$name 名称
   public function __isset(string $name): bool {
       return isset($this->middleware[$name]);
   }

   // ArrayAccess
   public function offsetExists($name): bool {
       return $this->has($name);
   }

   public function offsetGet($name) {
       return $this->param($name);
   }

   public function offsetSet($name, $value) {}

   public function offsetUnset($name) {}

}

五、执行请求

think\response.php 源码分析

<?php
namespace think;

/**
 * 响应输出基础类
 * @package think
 */
abstract class Response {
    
    protected $data;		//原始数据
    protected $contentType = 'text/html';		//当前contentType
    protected $charset = 'utf-8';		//字符集
    protected $code = 200;		//状态码
    protected $allowCache = true;	//是否允许请求缓存
    protected $options = [];	//输出参数
    protected $header = [];	//header参数
    protected $content = null;	//输出内容
    protected $cookie;		//Cookie对象
    protected $session;		//Session对象

    //初始化,$data 输出数据,$code 状态码
    protected function init($data = '', int $code = 200) {
        $this->data($data);
        $this->code = $code;

        $this->contentType($this->contentType, $this->charset);
    }

    //创建Response对象,$data 输出数据,$type 输出类型,$code 状态码
    public static function create($data = '', string $type = 'html', int $code = 200): Response {
        $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type));

        return Container::getInstance()->invokeClass($class, [$data, $code]);
    }

    //设置Session对象,$session Session对象
    public function setSession(Session $session) {
        $this->session = $session;
        return $this;
    }

    //发送数据到客户端
    public function send(): void {
        // 处理输出数据
        $data = $this->getContent();

        if (!headers_sent() && !empty($this->header)) {
            // 发送状态码
            http_response_code($this->code);
            // 发送头部信息
            foreach ($this->header as $name => $val) {
                header($name . (!is_null($val) ? ':' . $val : ''));
            }
        }
        if ($this->cookie) {
            $this->cookie->save();
        }

        $this->sendData($data);

        if (function_exists('fastcgi_finish_request')) {
            // 提高页面响应
            fastcgi_finish_request();
        }
    }

    //处理数据,$data 要处理的数据
    protected function output($data) {
        return $data;
    }

    //输出数据,$data 要处理的数据
    protected function sendData(string $data): void {
        echo $data;
    }

    //输出的参数,$options 输出参数
    public function options(array $options = []) {
        $this->options = array_merge($this->options, $options);

        return $this;
    }

    //输出数据设置是,$data 输出数据
    public function data($data) {
        $this->data = $data;

        return $this;
    }

    //是否允许请求缓存,$cache 允许请求缓存
    public function allowCache(bool $cache) {
        $this->allowCache = $cache;

        return $this;
    }

    //是否允许请求缓存
    public function isAllowCache() {
        return $this->allowCache;
    }

    //设置Cookie,$name cookie名称,$value cookie值,$option 可选参数
    public function cookie(string $name, string $value, $option = null) {
        $this->cookie->set($name, $value, $option);

        return $this;
    }

    //设置响应头,$header 参数
    public function header(array $header = []) {
        $this->header = array_merge($this->header, $header);

        return $this;
    }

    //设置页面输出内容,mixed $content
    public function content($content) {
        if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
                $content,
                '__toString',
            ])
        ) {
            throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content)));
        }

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

        return $this;
    }

    //发送HTTP状态,$code 状态码
    public function code(int $code) {
        $this->code = $code;

        return $this;
    }

    //LastModified,string $time
    public function lastModified(string $time) {
        $this->header['Last-Modified'] = $time;

        return $this;
    }

    //Expires,string $time
    public function expires(string $time) {
        $this->header['Expires'] = $time;

        return $this;
    }

    //ETag,string $eTag
    public function eTag(string $eTag) {
        $this->header['ETag'] = $eTag;

        return $this;
    }

    //页面缓存控制,string $cache 状态码
    public function cacheControl(string $cache) {
        $this->header['Cache-control'] = $cache;

        return $this;
    }

    //页面输出类型,$contentType 输出类型,$charset 输出编码
    public function contentType(string $contentType, string $charset = 'utf-8') {
        $this->header['Content-Type'] = $contentType . '; charset=' . $charset;

        return $this;
    }

    //获取头部信息,string $name 头部名称
    public function getHeader(string $name = '') {
        if (!empty($name)) {
            return $this->header[$name] ?? null;
        }

        return $this->header;
    }

    //获取原始数据
    public function getData() {
        return $this->data;
    }

    //获取输出数据
    public function getContent(): string {
        if (null == $this->content) {
            $content = $this->output($this->data);

            if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([
                    $content,
                    '__toString',
                ])
            ) {
                throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content)));
            }

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

        return $this->content;
    }

    //获取状态码
    public function getCode(): int {
        return $this->code;
    }
}

六、执行结束时的工作

$http->end($response);
  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-08-24 15:19:43  更:2021-08-24 15:21:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 0:00:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码