zoukankan      html  css  js  c++  java
  • 【thinkphp6源码分析二】 最最基础的类-APP类

    上篇说到index.php里面的$http = (new App())->http 

    最终就是调了APP类同目录下的  thinkHttp   [tp6vendor opthinkframeworksrc hinkHttp.php]

    调用之前 使用了系统的一个叫APP [tp6vendor opthinkframeworksrc hinkApp.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,
            'thinkDbManager'         => Db::class,
            'thinkLogManager'        => Log::class,
            'thinkCacheManager'      => Cache::class,
    
            // 接口依赖注入
            'PsrLogLoggerInterface' => 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('thinkContainer', $this);
    21 }

     构造函数的代码还是比较好懂的

    它首先定义了很多 路径属性 这个有点像早期TP定义的那些ROOT_PATH PUBLIC_PATH常量

    看中间一段[上面代码13-15行 ] 它引入了apppath目录下面的provider.php文件  并且对这个文件进行了bind操作

    apppath根据上面代码可以看到  是项目的app目录 [tp6app] 

    它下面的provider文件  和bind函数  我们一起看一下

     1 <?php
     2 use appExceptionHandle;
     3 use appRequest;
     4 
     5 // 容器Provider定义文件
     6 return [
     7     'thinkRequest'          => Request::class,
     8     'thinkexceptionHandle' => 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里面的那个数组 

    [
        'thinkRequest'          => Request::class,
        'thinkexceptionHandle' => ExceptionHandle::class,
    ]

    然后将这个数组当做参数 传入到bind函数里面去  数组的话 会将数组的每一个元素都执行一次bind方法

    我们以其中一个为例子  'thinkRequest' => Request::class,

      这个参数传入bind 可以得到 $abstract 为 'thinkRequest'    $concrete 为Request::class

    这里'thinkRequest'只能是个字符串 不属于闭包 也不属于对象 所以会走到最下面的else

    else代码中 首先会对thinkRequest 执行 $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] 里面 是没有thinkRequest 这个别名的[一般别名我们也不会叫这么复杂的 比如thinkRequest叫request还差不多]

    所以这个函数其实就没啥用 直接返回了自身

    然后执行 $this->bind[$abstract] = $concrete;

    也就是给文章一开始的那个$bind插入了一个值  'thinkRequest' => Request::class,

    这里 我们直接把这个$bind打印下就能看到

     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('thinkContainer', $this);
    16         //打印看下这个bind
    17          dd($this->bind);
    18     }    
    View Code
    ^ array:25 [▼
      "app" => "thinkApp"
      "cache" => "thinkCache"
      "config" => "thinkConfig"
      "console" => "thinkConsole"
      "cookie" => "thinkCookie"
      "db" => "thinkDb"
      "env" => "thinkEnv"
      "event" => "thinkEvent"
      "http" => "thinkHttp"
      "lang" => "thinkLang"
      "log" => "thinkLog"
      "middleware" => "thinkMiddleware"
      "request" => "thinkRequest"
      "response" => "thinkResponse"
      "route" => "thinkRoute"
      "session" => "thinkSession"
      "validate" => "thinkValidate"
      "view" => "thinkView"
      "filesystem" => "thinkFilesystem"
      "thinkDbManager" => "thinkDb"
      "thinkLogManager" => "thinkLog"
      "thinkCacheManager" => "thinkCache"
      "PsrLogLoggerInterface" => "thinkLog"
      "thinkRequest" => "appRequest"
      "thinkexceptionHandle" => "appExceptionHandle"
    ]
    View Code

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

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

    直接在index.php试一下

     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 = 'thinkRequest';
    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这个对象了

    当然 这里一用就发现不爽  作为属性 还用'thinkRequest'这种名字也丑陋了吧 实际在$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 里面
    [thinkRequest 这个叫类 new thinkRequest()这个叫类实例 ]

    关于bind函数的第二个作用 请直接看下章

  • 相关阅读:
    Linux/shell: remove adjacent similar patterns
    Calculate difference between consecutive data points in a column from a file
    awk
    自定义Cordova插件(基础篇)
    npm init 命令生成package.json文件
    自定义Cordova插件详解
    Android 回退键监听
    Cordova结合Vue学习Camera
    解决悬浮的<header>、<footer>遮挡内容的处理技巧
    npm 是干什么的
  • 原文地址:https://www.cnblogs.com/dk1988/p/14419368.html
Copyright © 2011-2022 走看看