【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打印下就能看到

【thinkphp6源码分析二】最最基础的类-APP类
 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     }    
View Code
【thinkphp6源码分析二】最最基础的类-APP类
^ 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"
]
View Code

可以看到  bind数组里面 最终加入了provider里面的两个类

那么这里 其实我们就可以参考(new App)->http 来调用Request这个类了 

直接在index.php试一下

【thinkphp6源码分析二】最最基础的类-APP类
 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);
View Code

可以看到 正常返回Request这个对象了

当然 这里一用就发现不爽  作为属性 还用‘think\Request‘这种名字也丑陋了吧 实际在$bind系统提供好的预设中 我们可以看到 它给这个类提供了一个叫request的别名

可以(new App)->request这样 来很方便的调用

 [实际仔细看 这俩类还是有区别的 一个是app/Request  一个是think/Requst  看源码会发现APP/Requst 继承了think/Request]

 

当然 我们也可以在provider里面

如:‘myRequest‘ => Request::class,

自己对这个类设置一个别名这样就可以通过(new App)->myRequest 来使用这个类了

【thinkphp6源码分析二】最最基础的类-APP类

上一篇:python钉钉打卡脚本


下一篇:Java并发编程(一)——进程和线程、Java对象内存布局、synchronized、wait和notify、park和unpack