【thinkphp6源码分析二】 最最基础的类-APP类
上篇说到index.php里面的$http = (new App())->http
最终就是调了APP类同目录下的 think\Http [tp6\vendor\topthink\framework\src\think\Http.php]
调用之前 使用了系统的一个叫APP [tp6\vendor\topthink\framework\src\think\App.php]的类
这个属于THINKPHP框架最最基础的一个类 那么首先 先研究下这个类
这个类有一大堆方法 核心上 我们先关注三点
(1)它一开始有个$bind 标识 定义了一大堆绑定标识
(2)它的构造函数__construct里面写了一大坨东西
(3)它继承了Container类
第一点,由于一开始我们就知道 这些标识是为了让我们方便的调用某些系统的基础类
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, ];
这些类看名字就很熟悉 它们就是TP框架最常用的一些工具类,比如Cache Config Request DB类等
我们先看下这些类如何调用
查阅官方文档和源码 能看到很多 $app->cache $app->config的写法 实际这个写法也与index.php里面的$app->http相同
至于为什么这里要绕来绕去这样去调动一个类呢?这种写法的好处何在?,了解这个问题,我们可以先回忆下调用类的三个阶段
第一阶段: require XXX require_once XXX 然后new Class() 这个阶段的写法 缺点显而易见 路径不明晰,相互之间容易嵌套来嵌套去容易出错,每个使用的地方都需要requred 编程效率不高
为了解决这个问题 php后面就引入了命名空间 也就是第二个阶段
第二阶段: namespace XXX use XXX 然后new Class() 这个阶段比第一阶段好多了,不用考虑对类路径引用的问题,让项目更容易工程化
但这样写还是有缺点 每次使用需要use对应的类 然后去new相关对象,代码还是有很大的重复度,且当很多类有重名时,会很容易出错
于是这里在TP框架就有了第三个阶段
第三阶段: 建立一个容器,将所有常用的类放到这个容器里面去,需要使用的时候,根据一个标识就能直接调用了
比如缓存 直接$app->cache 就能调用了(当然$app 这个对象是需要提前去生成好的)
这样编码的时候 逻辑会很清晰 而且这种方式下,容器里面的对象使用了单例模式(只会new一次) 也极大的提高了程序的运行效率
[框架中的实例对象基本都是这种方式去调用]
这第三种也就是我们熟知的设计模式里面的注册树模式
[创建一棵树(容器Container) 把苹果都放到树上 cache苹果 config苹果 db苹果等等 需要使用的时候 把这个苹果拿过来用就行了]
接下来看第二点 关于__construct构造函数
1 /** 2 * 架构方法 3 * @access public 4 * @param string $rootPath 应用根目录 5 */ 6 public function __construct(string $rootPath = '') 7 { 8 $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR; 9 $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath(); 10 $this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR; 11 $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; 12 13 if (is_file($this->appPath . 'provider.php')) { 14 $this->bind(include $this->appPath . 'provider.php'); 15 } 16 17 static::setInstance($this); 18 19 $this->instance('app', $this); 20 $this->instance('think\Container', $this); 21 }
构造函数的代码还是比较好懂的
它首先定义了很多 路径属性 这个有点像早期TP定义的那些ROOT_PATH PUBLIC_PATH常量
看中间一段[上面代码13-15行 ] 它引入了apppath目录下面的provider.php文件 并且对这个文件进行了bind操作
apppath根据上面代码可以看到 是项目的app目录 [tp6\app]
它下面的provider文件 和bind函数 我们一起看一下
1 <?php 2 use app\ExceptionHandle; 3 use app\Request; 4 5 // 容器Provider定义文件 6 return [ 7 'think\Request' => Request::class, 8 'think\exception\Handle' => ExceptionHandle::class, 9 ]; 10
1 11 /** 2 12 * 绑定一个类、闭包、实例、接口实现到容器 3 13 * @access public 4 14 * @param string|array $abstract 类标识、接口 5 15 * @param mixed $concrete 要绑定的类、闭包或者实例 6 16 * @return $this 7 17 */ 8 18 public function bind($abstract, $concrete = null) 9 19 { 10 20 if (is_array($abstract)) { 11 21 foreach ($abstract as $key => $val) { 12 22 $this->bind($key, $val); 13 23 } 14 24 } elseif ($concrete instanceof Closure) { 15 25 $this->bind[$abstract] = $concrete; 16 26 } elseif (is_object($concrete)) { 17 27 $this->instance($abstract, $concrete); 18 28 } else { 19 29 $abstract = $this->getAlias($abstract); 20 30 if ($abstract != $concrete) { 21 31 $this->bind[$abstract] = $concrete; 22 32 } 23 33 } 24 34 25 35 return $this; 26 36 }
provider文件可以看到是一个类似config之类的配置文件,配置的目的是通过bind函数
将文件中的类 绑定实现到容器
我们直接以provider为例子 来看下bind然后
首先 构造函数的第14行 include $this->appPath . 'provider.php' 获得了这个文件 实际这里就等同于provider.php里面的那个数组
[
'think\Request' => Request::class,
'think\exception\Handle' => ExceptionHandle::class,
]
然后将这个数组当做参数 传入到bind函数里面去 数组的话 会将数组的每一个元素都执行一次bind方法
我们以其中一个为例子 'think\Request' => Request::class,
这个参数传入bind 可以得到 $abstract 为 'think\Request' $concrete 为Request::class
这里'think\Request'只能是个字符串 不属于闭包 也不属于对象 所以会走到最下面的else
else代码中 首先会对think\Request 执行 $this->getAlias($abstract); 方法
1 /** 2 * 根据别名获取真实类名 3 * @param string $abstract 4 * @return string 5 */ 6 public function getAlias(string $abstract): string 7 { 8 if (isset($this->bind[$abstract])) { 9 $bind = $this->bind[$abstract]; 10 11 if (is_string($bind)) { 12 return $this->getAlias($bind); 13 } 14 } 15 16 return $abstract; 17 }
根据别名获取真实类名 这里我们可以看到 bind这个数组[就是文章一开始的那个protected $bind] 里面 是没有think\Request 这个别名的[一般别名我们也不会叫这么复杂的 比如think\Request叫request还差不多]
所以这个函数其实就没啥用 直接返回了自身
然后执行 $this->bind[$abstract] = $concrete;
也就是给文章一开始的那个$bind插入了一个值 'think\Request' => Request::class,
这里 我们直接把这个$bind打印下就能看到
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public function __construct(string $rootPath = '') 2 { 3 $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR; 4 $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath(); 5 $this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR; 6 $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; 7 8 if (is_file($this->appPath . 'provider.php')) { 9 $this->bind(include $this->appPath . 'provider.php'); 10 } 11 12 static::setInstance($this); 13 14 $this->instance('app', $this); 15 $this->instance('think\Container', $this); 16 //打印看下这个bind 17 dd($this->bind); 18 }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
^ array:25 [▼ "app" => "think\App" "cache" => "think\Cache" "config" => "think\Config" "console" => "think\Console" "cookie" => "think\Cookie" "db" => "think\Db" "env" => "think\Env" "event" => "think\Event" "http" => "think\Http" "lang" => "think\Lang" "log" => "think\Log" "middleware" => "think\Middleware" "request" => "think\Request" "response" => "think\Response" "route" => "think\Route" "session" => "think\Session" "validate" => "think\Validate" "view" => "think\View" "filesystem" => "think\Filesystem" "think\DbManager" => "think\Db" "think\LogManager" => "think\Log" "think\CacheManager" => "think\Cache" "Psr\Log\LoggerInterface" => "think\Log" "think\Request" => "app\Request" "think\exception\Handle" => "app\ExceptionHandle" ]
可以看到 bind数组里面 最终加入了provider里面的两个类
那么这里 其实我们就可以参考(new App)->http 来调用Request这个类了
直接在index.php试一下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <?php 2 // +---------------------------------------------------------------------- 3 // | ThinkPHP [ WE CAN DO IT JUST THINK ] 4 // +---------------------------------------------------------------------- 5 // | Copyright (c) 2006-2019 http://thinkphp.cn All rights reserved. 6 // +---------------------------------------------------------------------- 7 // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) 8 // +---------------------------------------------------------------------- 9 // | Author: liu21st <liu21st@gmail.com> 10 // +---------------------------------------------------------------------- 11 12 // [ 应用入口文件 ] 13 namespace think; 14 15 require __DIR__ . '/../vendor/autoload.php'; 16 17 $method = 'think\Request'; 18 19 $res = (new App)->$method; 20 21 dd($res); 22 23 // 执行HTTP应用并响应 24 $http = (new App())->http; 25 26 27 $response = $http->run(); 28 29 $response->send(); 30 31 $http->end($response);
可以看到 正常返回Request这个对象了
当然 这里一用就发现不爽 作为属性 还用'think\Request'这种名字也丑陋了吧 实际在$bind系统提供好的预设中 我们可以看到 它给这个类提供了一个叫request的别名
可以(new App)->request这样 来很方便的调用
[实际仔细看 这俩类还是有区别的 一个是app/Request 一个是think/Requst 看源码会发现APP/Requst 继承了think/Request]
当然 我们也可以在provider里面
如:'myRequest' => Request::class,
自己对这个类设置一个别名这样就可以通过(new App)->myRequest 来使用这个类了
这里总结一下 可以得到bind函数的第一个作用
给类设置一些别名 然后放到一个叫$bind的属性里面去
bind函数其实还有第二个作用 就是对应代码
elseif (is_object($concrete)) {
$this->instance($abstract, $concrete);
这个意思是 bind也可以给 给类实例 设置一个别名标识 最终放到一个叫 $instances 里面
[think\Request 这个叫类 new think\Request()这个叫类实例 ]
关于bind函数的第二个作用 请直接看下章