laravel源碼解析之請求周期

入口文件 indx.php

<?php

# 系統(tǒng)定義了一個開始時間
define('LARAVEL_START', microtime(true));

# composer 類自動加載
require __DIR__.'/../vendor/autoload.php';

# 系統(tǒng)初始化
$app = require_once __DIR__.'/../bootstrap/app.php';

# 正式實例化類,在這之前的綁定和別名種種都是存下了抽象類或者別名與其生成類的閉包函數(shù)的數(shù)組,沒有正式實例化,直到執(zhí)行下面這句,make 返回實例化的 Kernel 類,而 kernel 類需要用到路由類,路由類需要傳遞一個實例化的 Event 類,所以到此為止實例化的只有三個類,Kernel、Router 和 Event。打印出來的結(jié)果也證實了這一點。
resolved: array:4 [▼
   "events" => true
   "router" => true
   "App\Http\Kernel" => true
   "Illuminate\Contracts\Http\Kernel" => true
]
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

# 最后,內(nèi)核處理相應的請求
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

# 將請求發(fā)送給客戶端
$response->send();

# 最后處理下結(jié)束中間件,注意如果在request中的terminate方法里面 dd(),或者echo什么的操作你是看不到的,因為響應已經(jīng)在上一個方法中返回了,這里只能用 \Log 來記錄。
$kernel->terminate($request, $response);

進入 bootstrap/app.php

<?php

# 設置了框架的根路徑
$app = new Illuminate\Foundation\Application(
    dirname(__DIR__)
);


$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;

代碼深入

$app = new Illuminate\Foundation\Application(
    dirname(__DIR__)
);

上面代碼做了以下的幾件事

  1. 設置app的根目錄以及綁定其他文件夾的目錄 setBasePath->bindPathsInContainer

    1. 先來看看 Application 類中內(nèi)置的屬性

           # 保證整個應用單一實例的單例,static 后期靜態(tài)綁定
                protected static $instance;
              
             # 記錄運行到當前這一刻,已經(jīng)實例化的類
             resolved: array:4 [▼
               "events" => true
               "router" => true
               "App\Http\Kernel" => true
               "Illuminate\Contracts\Http\Kernel" => true
             ]
              protected $resolved = [];
              
                # 記錄接口和類的生成閉包,以及是否共享
              protected $bindings = [];
              
              /**
               * The container's method bindings.
               *
               * @var array
               */
              protected $methodBindings = [];
              
                # 存的都是具體的實例化類和系統(tǒng)路徑
              protected $instances = [];
              
              'aliases' => [
                  "Illuminate\Foundation\Application" => "app"
                  "Illuminate\Contracts\Container\Container" => "app"
                  "Illuminate\Contracts\Foundation\Application" => "app"
                  "Psr\Container\ContainerInterface" => "app"
              ]
              protected $aliases = [];
              
              /**
               * The registered aliases keyed by the abstract name.
               *
               * @var array
               */
              protected $abstractAliases = [];
      
             /**
              * The extension closures for services.
              *
              * @var array
              */
             protected $extenders = [];
             
             /**
              * All of the registered tags.
              *
              * @var array
              */
             protected $tags = [];
      
            # 用來臨時儲存當前需要實例化的類,實例化完就清空了
             buildStack: array:1 [▼
               0 => "App\Http\Kernel"
             ]
             protected $buildStack = [];
      
            # 用來儲存當前實例化的類所需要的參數(shù),實例化完就清空了
             protected $with = [];
             
             /**
              * The contextual binding map.
              *
              * @var array
              */
             public $contextual = [];
             
             /**
              * All of the registered rebound callbacks.
              *
              * @var array
              */
             protected $reboundCallbacks = [];
             
             /**
              * All of the global resolving callbacks.
              *
              * @var array
              */
             protected $globalResolvingCallbacks = [];
             
             /**
              * All of the global after resolving callbacks.
              *
              * @var array
              */
             protected $globalAfterResolvingCallbacks = [];
             
             /**
              * All of the resolving callbacks by class type.
              *
              * @var array
              */
             protected $resolvingCallbacks = [];
             
             /**
              * All of the after resolving callbacks by class type.
              *
              * @var array
              */
             protected $afterResolvingCallbacks = [];
      
      
         
      
    2. 調(diào)用了 Application 的 instance 方法

    3. # abstractAliases 中的格式類似這樣
      "app" => array:4 [
          0 => "Illuminate\Foundation\Application"
          1 => "Illuminate\Contracts\Container\Container"
          2 => "Illuminate\Contracts\Foundation\Application"
          3 => "Psr\Container\ContainerInterface"
      ]
      
      public function instance($abstract, $instance)
      {
      #    dd($abstract, $instance); path , /var/www/html/app
          $this->removeAbstractAlias($abstract);
      
          $isBound = $this->bound($abstract);
      
          # 上面刪除了 屬性 abstractAliases 中,$abstract 對應的別名,這里要把 $abstract 鍵值也刪除
          unset($this->aliases[$abstract]);
      
          # 刪除之后重新綁定,即 $instance => ['path'=>'/var/www/html/app']
          $this->instances[$abstract] = $instance;
      
          if ($isBound) {
              # 取消bingings instances 和 aliases中的綁定
              $this->rebound($abstract);
          }
      
          return $instance;
      }
      
      # $searched => path
      # 先去搜索 alias 里面的 path 鍵值,不存在則返回,存在則刪除存在的值
      protected function removeAbstractAlias($searched)
      {
          if (! isset($this->aliases[$searched])) {
              return;
          }
      
          foreach ($this->abstractAliases as $abstract => $aliases) {
              foreach ($aliases as $index => $alias) {
                  if ($alias == $searched) {
                      unset($this->abstractAliases[$abstract][$index]);
                  }
              }
          }
      }
      
      # $abstract => path
      # 去判斷 bingings instances 和 aliases 中是否存在 path 鍵值
      # 顯然這里返回 false
      public function bound($abstract)
      {
          return isset($this->bindings[$abstract]) ||
              isset($this->instances[$abstract]) ||
              $this->isAlias($abstract);
      }
      
      protected function rebound($abstract)
      {
          # 這一步不貼代碼了,它具體做了以下幾個操作
          # 1. 去 alias 中找是否存在鍵值,
          # 2. 去看 buildStack[] 是否創(chuàng)建過這個實例
          $instance = $this->make($abstract);
      
          foreach ($this->getReboundCallbacks($abstract) as $callback) {
              call_user_func($callback, $this, $instance);
          }
      }
      
      # 這個方法的遞歸主要是為了找到根實例
      # 因為 $aliases 中格式可能是這樣的
      # 'aliases' => [
      # 'a' => 'b',
      #     'b' => 'c',
      # ]
      # a 的別名是 b,b 才對應到 c,c才是最終能找到實例的別名類
      public function getAlias($abstract)
      {
          if (! isset($this->aliases[$abstract])) {
              return $abstract;
          }
      
          if ($this->aliases[$abstract] === $abstract) {
              throw new LogicException("[{$abstract}] is aliased to itself.");
          }
      
          return $this->getAlias($this->aliases[$abstract]);
      }
      

      tips

      true || $a = 'a'; // 后半句不會執(zhí)行
      echo $a; //  Undefined variable: a
      
      public function __construct($basePath = null)
      {
          # 這一步綁定了所有目錄
          array:9 [▼
            "path" => "/var/www/html/app"
            "path.base" => "/var/www/html"
            "path.lang" => "/var/www/html/resources/lang"
            "path.config" => "/var/www/html/config"
            "path.public" => "/var/www/html/public"
            "path.storage" => "/var/www/html/storage"
            "path.database" => "/var/www/html/database"
            "path.resources" => "/var/www/html/resources"
            "path.bootstrap" => "/var/www/html/bootstrap"
          ]
          if ($basePath) {
              $this->setBasePath($basePath);
          }
      
          # 這一步做了
          # 設置了static::$instance,保證了運行中的唯一單例
          # 綁定了 $instance['app'] = $this
          # 綁定了 $instance['Illuminate\Container\Container'] = $this
          # 綁定了 $instance['Illuminate\Foundation\PackageManifest'] = PackageManifest 實例(大概是啟動的時候需要給配置文件做緩存,所以優(yōu)先級比較高)
          array:12 [▼
            "path" => "/var/www/html/app"
            "path.base" => "/var/www/html"
            "path.lang" => "/var/www/html/resources/lang"
            "path.config" => "/var/www/html/config"
            "path.public" => "/var/www/html/public"
            "path.storage" => "/var/www/html/storage"
            "path.database" => "/var/www/html/database"
            "path.resources" => "/var/www/html/resources"
            "path.bootstrap" => "/var/www/html/bootstrap"
            "app" => Application {#2 ?}
            "Illuminate\Container\Container" => Application {#2 ?}
            "Illuminate\Foundation\PackageManifest" => PackageManifest {#4 ?}
          ]
          $this->registerBaseBindings();
      
        # 添加系統(tǒng)最基礎最重要的服務
          $this->registerBaseServiceProviders();
                
        # 注冊系統(tǒng)核心服務 $aliases 和 $abstractAliases 屬性改變了
          aliases => [
              "Illuminate\Foundation\Application" => "app"
            "Illuminate\Contracts\Container\Container" => "app"
            "Illuminate\Contracts\Foundation\Application" => "app"
            "Psr\Container\ContainerInterface" => "app"
          ]
          abstractAliases: array:35 [▼
            "app" => array:4 [▼
                0 => "Illuminate\Foundation\Application"
                1 => "Illuminate\Contracts\Container\Container"
                2 => "Illuminate\Contracts\Foundation\Application"
                3 => "Psr\Container\ContainerInterface"
             ]
          $this->registerCoreContainerAliases();
      }
      
      protected function registerBaseServiceProviders()
      {
          $this->register(new EventServiceProvider($this));
      
          $this->register(new LogServiceProvider($this));
      
          $this->register(new RoutingServiceProvider($this));
      }
      
      public function register($provider, $force = false)
      {
          # getProvider 去獲取 serviceProviders 數(shù)組中的 provider
          # 顯然之前并沒有注冊過,return false
          if (($registered = $this->getProvider($provider)) && ! $force) {
              return $registered;
          }
      
        # 這里傳入的是實例,所以 false
          if (is_string($provider)) {
              $provider = $this->resolveProvider($provider);
          }
      
          # 調(diào)用 EventServiceProvider,LogServiceProvider, RoutingServiceProvider 的 register 方法,綁定了系統(tǒng)日志,事件和路由
           bindings: array:9 [▼
              "events" => array:2 [?]
              "log" => array:2 [?]
              "router" => array:2 [?]
              "url" => array:2 [?]
              "redirect" => array:2 [?]
              "Psr\Http\Message\ServerRequestInterface" => array:2 [▼
                "concrete" => Closure {#14 ?}
                "shared" => false
              ]
              "Psr\Http\Message\ResponseInterface" => array:2 [?]
              "Illuminate\Contracts\Routing\ResponseFactory" => array:2 [?]
              "Illuminate\Routing\Contracts\ControllerDispatcher" => array:2 [?]
            ]
          if (method_exists($provider, 'register')) {
              $provider->register();
          }
                    
        ## 下面兩個可以解釋為什么在 serverProvider 中可以使用 $bindings 和 $singletons 綁定屬性
      
        # 解析provider中的 bingings 屬性
          if (property_exists($provider, 'bindings')) {
              foreach ($provider->bindings as $key => $value) {
                  # bind 方法用到了反射,通過反射拿到對應實例的構(gòu)造函數(shù)
                  $this->bind($key, $value);
              }
          }
                    
        # 解析provider中的 singletons 屬性
          if (property_exists($provider, 'singletons')) {
              foreach ($provider->singletons as $key => $value) {
                  $this->singleton($key, $value);
              }
          }
      
        # 把解析過的 serviceProvider 添加到 $serviceProviders 和 $loadedProviders 屬性中
         serviceProviders: array:3 [▼
          0 => EventServiceProvider {#6 ?}
          1 => LogServiceProvider {#8 ?}
          2 => RoutingServiceProvider {#10 ?}
        ]
        loadedProviders: array:3 [▼
          "Illuminate\Events\EventServiceProvider" => true
          "Illuminate\Log\LogServiceProvider" => true
          "Illuminate\Routing\RoutingServiceProvider" => true
        ]
          $this->markAsRegistered($provider);
      
          // If the application has already booted, we will call this boot method on
          // the provider class so it has an opportunity to do its boot logic and
          // will be ready for any usage by this developer's application logic.
          if ($this->booted) {
              $this->bootProvider($provider);
          }
      
          return $provider;
      }
      
      # EventServiceProvider
      public function register()
      {
          # $this->app 在這里把 Application 實例傳進去了 -> $this->register(new EventServiceProvider($this));
          # 然后 在 ServiceProvider 的構(gòu)造函數(shù)中把 實例賦值給了 app屬性
          $this->app->singleton('events', function ($app) {
              return (new Dispatcher($app))->setQueueResolver(function () use ($app) {
                  return $app->make(QueueFactoryContract::class);
              });
          });
      }
      
      # LogServiceProvider
      public function register()
      {
          $this->app->singleton('log', function () {
              return new LogManager($this->app);
          });
      }
      
      public function singleton($abstract, $concrete = null)
      {
          $this->bind($abstract, $concrete, true);
      }
      
      public function bind($abstract, $concrete = null, $shared = false)
      {
          # 如果已經(jīng)綁定過instance,或存過 aliases 則刪除
          $this->dropStaleInstances($abstract);
      
          if (is_null($concrete)) {
              $concrete = $abstract;
          }
      
          # 不是閉包則獲取閉包
          if (! $concrete instanceof Closure) {
              $concrete = $this->getClosure($abstract, $concrete);
          }
      
          # 綁定實例
          bindings: array:1 [▼
           "events" => array:2 [▼
                "concrete" => Closure {#7 ?} # 這里存了生成實例的閉包
                 "shared" => true
               ]
             ]
          $this->bindings[$abstract] = compact('concrete', 'shared');
      
        # 最后還不忘解綁已經(jīng)實例化的抽象屬性              
          if ($this->resolved($abstract)) {
              $this->rebound($abstract);
          }
      }
      
  2. 緊接著綁定了三個單例,高度解耦,把抽象和實例進行綁定

    1. $app->singleton(
          Illuminate\Contracts\Http\Kernel::class,
          App\Http\Kernel::class
      );
      
      $app->singleton(
          Illuminate\Contracts\Console\Kernel::class,
          App\Console\Kernel::class
      );
      
      $app->singleton(
          Illuminate\Contracts\Debug\ExceptionHandler::class,
          App\Exceptions\Handler::class
      );
      
  3. 然后拿出內(nèi)核

    1. # 上面進行了綁定,所以拿出來的就是 App\Http\Kernel::class 實例
      $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
      

      make 方法最終會走到 build,這里注意框架內(nèi)部用反射,拿到構(gòu)造函數(shù)以及參數(shù).

      protected function resolve($abstract, $parameters = [])
          {
              $abstract = $this->getAlias($abstract);
              $needsContextualBuild = ! empty($parameters) || ! is_null(
                  $this->getContextualConcrete($abstract)
              );
      
            # 判斷是否已經(jīng)實例化過類,有的話直接返回該實例
              if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
                  return $this->instances[$abstract];
              }
      
              $this->with[] = $parameters;
      
              $concrete = $this->getConcrete($abstract);
      
            # 實例化
              if ($this->isBuildable($concrete, $abstract)) {
                  $object = $this->build($concrete);
              } else {
                  $object = $this->make($concrete);
              }
      
            # 類似于裝飾器模式,為某個類添加裝飾器,返回裝飾后的類,怎么用請看下面附言
              foreach ($this->getExtenders($abstract) as $extender) {
                  $object = $extender($object, $this);
              }
      
            # 判斷是否是單例
              if ($this->isShared($abstract) && ! $needsContextualBuild) {
                  $this->instances[$abstract] = $object;
              }
      
              $this->fireResolvingCallbacks($abstract, $object);
      
            # 走到這里說明實例化完成,所以把該實例化的抽象類加到 $resolved 屬性中
              $this->resolved[$abstract] = true;
      
              array_pop($this->with);
      
              return $object;
          }
      
      public function build($concrete)
      {
          // If the concrete type is actually a Closure, we will just execute it and
          // hand back the results of the functions, which allows functions to be
          // used as resolvers for more fine-tuned resolution of these objects.
          if ($concrete instanceof Closure) {
              return $concrete($this, $this->getLastParameterOverride());
          }
      
          $reflector = new ReflectionClass($concrete);
      
          // If the type is not instantiable, the developer is attempting to resolve
          // an abstract type such as an Interface of Abstract Class and there is
          // no binding registered for the abstractions so we need to bail out.
          if (! $reflector->isInstantiable()) {
              return $this->notInstantiable($concrete);
          }
      
          $this->buildStack[] = $concrete;
      
          $constructor = $reflector->getConstructor();
      
          // If there are no constructors, that means there are no dependencies then
          // we can just resolve the instances of the objects right away, without
          // resolving any other types or dependencies out of these containers.
          if (is_null($constructor)) {
              array_pop($this->buildStack);
      
              return new $concrete;
          }
      
          $dependencies = $constructor->getParameters();
      
          # 會解析參數(shù),如果之前應用綁定過那么拿到的是綁定過得那個參數(shù),單例
          $instances = $this->resolveDependencies(
              $dependencies
          );
      
          array_pop($this->buildStack);
      
          return $reflector->newInstanceArgs($instances);
      }
      

      反射類知識

      <?php
      namespace Duc;
      
      class B
      {
      }
      
      class A
      {
      
          function __construct(B $duc, $router)
          {
      
          }
      }
      
      $c = (new ReflectionClass(A::class))->getConstructor()->getParameters();
      foreach ($c as $key) {
          var_dump($key->getClass()->name); // Duc\B
      }
      
      # 第二個報錯,所以源碼用來 try catch
      
      # 源碼
      protected function resolveClass(ReflectionParameter $parameter)
      {
          try {
              return $this->make($parameter->getClass()->name);
          }
      
      
          catch (BindingResolutionException $e) {
              if ($parameter->isOptional()) {
                  return $parameter->getDefaultValue();
              }
      
              throw $e;
          }
      }
      
      
    2. 內(nèi)核構(gòu)造函數(shù)

         public function __construct(Application $app, Router $router)
         {
             $this->app = $app;
             $this->router = $router;
         
             $router->middlewarePriority = $this->middlewarePriority;
            
             # 保存系統(tǒng)中間件
             foreach ($this->middlewareGroups as $key => $middleware) {
                 $router->middlewareGroup($key, $middleware);
             }
         
             # 添加中間件別名
             foreach ($this->routeMiddleware as $key => $middleware) {
                 $router->aliasMiddleware($key, $middleware);
             }
         }
      
  4. 最后執(zhí)行

      $response = $kernel->handle(
          $request = Illuminate\Http\Request::capture()
      );
    
    public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride();
    
            $response = $this->sendRequestThroughRouter($request);
        } catch (Exception $e) {
            $this->reportException($e);
    
            $response = $this->renderException($request, $e);
        } catch (Throwable $e) {
            $this->reportException($e = new FatalThrowableError($e));
    
            $response = $this->renderException($request, $e);
        }
    
        $this->app['events']->dispatch(
            new Events\RequestHandled($request, $response)
        );
    
        return $response;
    }
    
    protected function sendRequestThroughRouter($request)
    {
        $this->app->instance('request', $request);
    
        Facade::clearResolvedInstance('request');
    
        # 到這一刻系統(tǒng)才算啟動成功
        $this->bootstrap();
    
        return (new Pipeline($this->app))
            ->send($request)
            # $this->app->shouldSkipMiddleware() 在測試時用來跳過中間件的
            ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
            ->then($this->dispatchToRouter());
    }
    

    其中bootstrap方法:

    加載系統(tǒng)環(huán)境變量,加載配置文件,異常處理,注冊facads,注冊app.php里面的系統(tǒng)的providers,注意這里最后一個 BootProviders,會將啟動屬性改為 true,$this->booted = true;,并且會調(diào)用所有已經(jīng)注冊的providers中的boot方法

    protected $bootstrappers = [    
      \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
      \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
      \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
      \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
      \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
      \Illuminate\Foundation\Bootstrap\BootProviders::class,
    ];
    

附言

關于 extend 的使用

# 我這里給 config 加了一層擴展,輸出一句 'use extend'。
# 這個方法僅限于擴展系統(tǒng)內(nèi)部的服務類,類似于代理模式,或者裝飾模式,代理可能更加好點
Route::get('/', function () {
    app()->extend('config', function ($config, $app) {
        echo 'use extend';

       return $config;
    });
    
    dd(config('app.name'));
}
           
# 輸出:
"Laravel"
use extend
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容