成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

深入解析 composer 的自動(dòng)加載原理

Darkgel / 2199人閱讀

摘要:從這個(gè)例子中,我們可以看出至少要做三件事情根據(jù)類名確定類文件名確定類文件所在的磁盤路徑將類從磁盤文件中加載到系統(tǒng)中。

深入解析 composer 的自動(dòng)加載原理 前言

PHP 自5.3的版本之后,已經(jīng)重?zé)ㄐ律臻g、性狀(trait)、閉包、接口、PSR 規(guī)范、以及 composer 的出現(xiàn)已經(jīng)讓 PHP 變成了一門現(xiàn)代化的腳本語(yǔ)言。PHP 的生態(tài)系統(tǒng)也一直在演進(jìn),而 composer 的出現(xiàn)更是徹底的改變了以往構(gòu)建 PHP 應(yīng)用的方式,我們可以根據(jù) PHP 的應(yīng)用需求混合搭配最合適的 PHP 組件。當(dāng)然這也得益于 PSR 規(guī)范的提出。


大綱

PHP 自動(dòng)加載功能

PSR 規(guī)范

comoposer 的自動(dòng)加載過程

composer 源碼分析


一、PHP 自動(dòng)加載功能 PHP 自動(dòng)加載功能的由來

在 PHP 開發(fā)過程中,如果希望從外部引入一個(gè) Class ,通常會(huì)使用 includerequire 方法,去把定義這個(gè) Class 的文件包含進(jìn)來。這個(gè)在小規(guī)模開發(fā)的時(shí)候,沒什么大問題。但在大型的開發(fā)項(xiàng)目中,使用這種方式會(huì)帶來一些隱含的問題:如果一個(gè) PHP 文件需要使用很多其它類,那么就需要很多的 require/include 語(yǔ)句,這樣有可能會(huì) 造成遺漏 或者 包含進(jìn)不必要的類文件。如果大量的文件都需要使用其它的類,那么要保證每個(gè)文件都包含正確的類文件肯定是一個(gè)噩夢(mèng), 況且 require或 incloud 的性能代價(jià)很大。

PHP5 為這個(gè)問題提供了一個(gè)解決方案,這就是 類的自動(dòng)加載(autoload)機(jī)制。autoload機(jī)制 可以使得 PHP 程序有可能在使用類時(shí)才自動(dòng)包含類文件,而不是一開始就將所有的類文件include進(jìn)來,這種機(jī)制也稱為 Lazy loading (惰性加載)。

總結(jié)起來,自動(dòng)加載功能帶來了幾處優(yōu)點(diǎn):

使用類之前無需 include / require

使用類的時(shí)候才會(huì) include / require 文件,實(shí)現(xiàn)了 lazy loading ,避免了 include / require 多余文件。

無需考慮引入 類的實(shí)際磁盤地址 ,實(shí)現(xiàn)了邏輯和實(shí)體文件的分離。

PHP 自動(dòng)加載函數(shù) __autoload()

從 PHP5 開始,當(dāng)我們?cè)谑褂靡粋€(gè)類時(shí),如果發(fā)現(xiàn)這個(gè)類沒有加載,就會(huì)自動(dòng)運(yùn)行 __autoload() 函數(shù),這個(gè)函數(shù)是我們?cè)诔绦蛑凶远x的,在這個(gè)函數(shù)中我們可以加載需要使用的類。下面是個(gè)簡(jiǎn)單的示例:


在我們這個(gè)簡(jiǎn)單的例子中,我們直接將類名加上擴(kuò)展名 .class.php 構(gòu)成了類文件名,然后使用 require_once 將其加載。

從這個(gè)例子中,我們可以看出 __autoload 至少要做三件事情:

根據(jù)類名確定類文件名;

確定類文件所在的磁盤路徑;

將類從磁盤文件中加載到系統(tǒng)中。

第三步最簡(jiǎn)單,只需要使用 include / require 即可。要實(shí)現(xiàn)第一步,第二步的功能,必須在開發(fā)時(shí)約定類名與磁盤文件的映射方法,只有這樣我們才能根據(jù)類名找到它對(duì)應(yīng)的磁盤文件。

當(dāng)有大量的類文件要包含的時(shí)候,我們只要確定相應(yīng)的規(guī)則,然后在 __autoload() 函數(shù)中,將類名與實(shí)際的磁盤文件對(duì)應(yīng)起來,就可以實(shí)現(xiàn) lazy loading 的效果

如果想詳細(xì)的了解關(guān)于 autoload 自動(dòng)加載的過程,可以查看手冊(cè)資料:PHP autoload函數(shù)說明

__autoload() 函數(shù)存在的問題

如果在一個(gè)系統(tǒng)的實(shí)現(xiàn)中,如果需要使用很多其它的類庫(kù),這些類庫(kù)可能是由不同的開發(fā)人員編寫的, 其類名與實(shí)際的磁盤文件的映射規(guī)則不盡相同。這時(shí)如果要實(shí)現(xiàn)類庫(kù)文件的自動(dòng)加載,就必須 在 __autoload() 函數(shù)中將所有的映射規(guī)則全部實(shí)現(xiàn),這樣的話 __autoload() 函數(shù)有可能會(huì)非常復(fù)雜,甚至無法實(shí)現(xiàn)。最后可能會(huì)導(dǎo)致 __autoload() 函數(shù)十分臃腫,這時(shí)即便能夠?qū)崿F(xiàn),也會(huì)給將來的維護(hù)和系統(tǒng)效率帶來很大的負(fù)面影響。

那么問題出現(xiàn)在哪里呢?問題出現(xiàn)在 __autoload() 是全局函數(shù)只能定義一次 ,不夠靈活,所以所有的類名與文件名對(duì)應(yīng)的邏輯規(guī)則都要在一個(gè)函數(shù)里面實(shí)現(xiàn),造成這個(gè)函數(shù)的臃腫。那么如何來解決這個(gè)問題呢?答案就是使用一個(gè) __autoload調(diào)用堆棧 ,不同的映射關(guān)系寫到不同的 __autoload函數(shù) 中去,然后統(tǒng)一注冊(cè)統(tǒng)一管理,這個(gè)就是 PHP5 引入的 SPL Autoload 。

SPL Autoload

SPL是 Standard PHP Library(標(biāo)準(zhǔn)PHP庫(kù))的縮寫。它是 PHP5 引入的一個(gè)擴(kuò)展標(biāo)準(zhǔn)庫(kù),包括 spl autoload 相關(guān)的函數(shù)以及各種數(shù)據(jù)結(jié)構(gòu)和迭代器的接口或類。spl autoload 相關(guān)的函數(shù)具體可見 php中spl_autoload


spl_autoload_register() 就是我們上面所說的__autoload調(diào)用堆棧,我們可以向這個(gè)函數(shù)注冊(cè)多個(gè)我們自己的 autoload() 函數(shù),當(dāng) PHP 找不到類名時(shí),PHP就會(huì)調(diào)用這個(gè)堆棧,然后去調(diào)用自定義的 autoload() 函數(shù),實(shí)現(xiàn)自動(dòng)加載功能。如果我們不向這個(gè)函數(shù)輸入任何參數(shù),那么就會(huì)默認(rèn)注冊(cè) spl_autoload() 函數(shù)。


二、PSR 規(guī)范

與自動(dòng)加載相關(guān)的規(guī)范是 PSR4,在說 PSR4 之前先介紹一下 PSR 標(biāo)準(zhǔn)。PSR 標(biāo)準(zhǔn)的發(fā)明和推出組織是:PHP-FIG,它的網(wǎng)站是:www.php-fig.org。由幾位開源框架的開發(fā)者成立于 2009 年,從那開始也選取了很多其他成員進(jìn)來,雖然不是 “官方” 組織,但也代表了社區(qū)中不小的一塊。組織的目的在于:以最低程度的限制,來統(tǒng)一各個(gè)項(xiàng)目的編碼規(guī)范,避免各家自行發(fā)展的風(fēng)格阻礙了程序員開發(fā)的困擾,于是大伙發(fā)明和總結(jié)了 PSR,PSR 是 PHP Standards Recommendation 的縮寫,截止到目前為止,總共有 14 套 PSR 規(guī)范,其中有 7 套PSR規(guī)范已通過表決并推出使用,分別是:

PSR-0 自動(dòng)加載標(biāo)準(zhǔn)(已廢棄,一些舊的第三方庫(kù)還有在使用)

PSR-1 基礎(chǔ)編碼標(biāo)準(zhǔn)

PSR-2 編碼風(fēng)格向?qū)?/strong>

PSR-3 日志接口

PSR-4 自動(dòng)加載的增強(qiáng)版,替換掉了 PSR-0

PSR-6 緩存接口規(guī)范

PSR-7 HTTP 消息接口規(guī)范

具體詳細(xì)的規(guī)范標(biāo)準(zhǔn)可以查看PHP 標(biāo)準(zhǔn)規(guī)范

PSR4 標(biāo)準(zhǔn)

2013 年底,PHP-FIG 推出了第 5 個(gè)規(guī)范——PSR-4。

PSR-4 規(guī)范了如何指定文件路徑從而自動(dòng)加載類定義,同時(shí)規(guī)范了自動(dòng)加載文件的位置。

1)一個(gè)完整的類名需具有以下結(jié)構(gòu):

<命名空間><子命名空間><類名>

完整的類名必須要有一個(gè)頂級(jí)命名空間,被稱為 "vendor namespace";

完整的類名可以有一個(gè)或多個(gè)子命名空間;

完整的類名必須有一個(gè)最終的類名;

完整的類名中任意一部分中的下滑線都是沒有特殊含義的;

完整的類名可以由任意大小寫字母組成;

所有類名都必須是大小寫敏感的。

2)根據(jù)完整的類名載入相應(yīng)的文件

完整的類名中,去掉最前面的命名空間分隔符,前面連續(xù)的一個(gè)或多個(gè)命名空間和子命名空間,作為「命名空間前綴」,其必須與至少一個(gè)「文件基目錄」相對(duì)應(yīng);

緊接命名空間前綴后的子命名空間 必須 與相應(yīng)的「文件基目錄」相匹配,其中的命名空間分隔符將作為目錄分隔符。

末尾的類名必須與對(duì)應(yīng)的以 .php 為后綴的文件同名。

自動(dòng)加載器(autoloader)的實(shí)現(xiàn)一定不可拋出異常、一定不可觸發(fā)任一級(jí)別的錯(cuò)誤信息以及不應(yīng)該有返回值。

3) 例子

PSR-4風(fēng)格

類名:ZendAbc    
命名空間前綴:Zend
文件基目錄:/usr/includes/Zend/
文件路徑:/usr/includes/Zend/Abc.php
類名:SymfonyCoreRequest  
命名空間前綴:SymfonyCore
文件基目錄:./vendor/Symfony/Core/
文件路徑:./vendor/Symfony/Core/Request.php

目錄結(jié)構(gòu)

-vendor/
| -vendor_name/
| | -package_name/
| | | -src/
| | | | -ClassName.php       # Vendor_NamePackage_NameClassName
| | | -tests/
| | | | -ClassNameTest.php   # Vendor_NamePackage_NameClassNameTest
Composer自動(dòng)加載過程 Composer 做了哪些事情

你有一個(gè)項(xiàng)目依賴于若干個(gè)庫(kù)。

其中一些庫(kù)依賴于其他庫(kù)。

你聲明你所依賴的東西。

Composer 會(huì)找出哪個(gè)版本的包需要安裝,并安裝它們(將它們下載到你的項(xiàng)目中)。

例如,你正在創(chuàng)建一個(gè)項(xiàng)目,需要做一些單元測(cè)試。你決定使用 phpunit 。為了將它添加到你的項(xiàng)目中,你所需要做的就是在 composer.json 文件里描述項(xiàng)目的依賴關(guān)系。

 {
   "require": {
     "phpunit/phpunit":"~6.0",
   }
 }

然后在 composer require 之后我們只要在項(xiàng)目里面直接 use phpunit 的類即可使用。

執(zhí)行 composer require 時(shí)發(fā)生了什么

composer 會(huì)找到符合 PR4 規(guī)范的第三方庫(kù)的源

將其加載到 vendor 目錄下

初始化頂級(jí)域名的映射并寫入到指定的文件里

(如:"PHPUnitFrameworkAssert" => __DIR__ . "/.." . "/phpunit/phpunit/src/Framework/Assert.php"

寫好一個(gè) autoload 函數(shù),并且注冊(cè)到 spl_autoload_register()里

題外話:現(xiàn)在很多框架都已經(jīng)幫我們寫好了頂級(jí)域名映射了,我們只需要在框架里面新建文件,在新建的文件中寫好命名空間,就可以在任何地方 use 我們的命名空間了。


Composer 源碼分析

下面我們通過對(duì)源碼的分析來看看 composer 是如何實(shí)現(xiàn) PSR4標(biāo)準(zhǔn) 的自動(dòng)加載功能。

很多框架在初始化的時(shí)候都會(huì)引入 composer 來協(xié)助自動(dòng)加載的,以 Laravel 為例,它入口文件 index.php 第一句就是利用 composer 來實(shí)現(xiàn)自動(dòng)加載功能。

啟動(dòng)

去 vendor 目錄下的 autoload.php


這里就是 Composer 真正開始的地方了

Composer自動(dòng)加載文件

首先,我們先大致了解一下Composer自動(dòng)加載所用到的源文件。

autoload_real.php: 自動(dòng)加載功能的引導(dǎo)類。

composer 加載類的初始化(頂級(jí)命名空間與文件路徑映射初始化)和注冊(cè)(spl_autoload_register())。

ClassLoader.php : composer 加載類。

composer 自動(dòng)加載功能的核心類。

autoload_static.php : 頂級(jí)命名空間初始化類,

用于給核心類初始化頂級(jí)命名空間。

autoload_classmap.php : 自動(dòng)加載的最簡(jiǎn)單形式,

有完整的命名空間和文件目錄的映射;

autoload_files.php : 用于加載全局函數(shù)的文件,

存放各個(gè)全局函數(shù)所在的文件路徑名;

autoload_namespaces.php : 符合 PSR0 標(biāo)準(zhǔn)的自動(dòng)加載文件,

存放著頂級(jí)命名空間與文件的映射;

autoload_psr4.php : 符合 PSR4 標(biāo)準(zhǔn)的自動(dòng)加載文件,

存放著頂級(jí)命名空間與文件的映射;

autoload_real 引導(dǎo)類

在 vendor 目錄下的 autoload.php 文件中我們可以看出,程序主要調(diào)用了引導(dǎo)類的靜態(tài)方法 getLoader() ,我們接著看看這個(gè)函數(shù)。

= 50600 && !defined("HHVM_VERSION");

      if ($useStaticLoader) {
          require_once __DIR__ . "/autoload_static.php";

          call_user_func(
          ComposerAutoloadComposerStaticInit7b790917ce8899df9af8ed53631a1c29::getInitializer($loader)
          );

      } else {
          $map = require __DIR__ . "/autoload_namespaces.php";
          foreach ($map as $namespace => $path) {
              $loader->set($namespace, $path);
          }

          $map = require __DIR__ . "/autoload_psr4.php";
          foreach ($map as $namespace => $path) {
              $loader->setPsr4($namespace, $path);
          }

          $classMap = require __DIR__ . "/autoload_classmap.php";
          if ($classMap) {
              $loader->addClassMap($classMap);
          }
      }

      /***********************注冊(cè)自動(dòng)加載核心類對(duì)象********************/
      $loader->register(true);

      /***********************自動(dòng)加載全局函數(shù)********************/
      if ($useStaticLoader) {
          $includeFiles = ComposerAutoloadComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files;
      } else {
          $includeFiles = require __DIR__ . "/autoload_files.php";
      }

      foreach ($includeFiles as $fileIdentifier => $file) {
          composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
      }

      return $loader;
    }

我把自動(dòng)加載引導(dǎo)類分為 5 個(gè)部分。

第一部分——單例

第一部分很簡(jiǎn)單,就是個(gè)最經(jīng)典的單例模式,自動(dòng)加載類只能有一個(gè)。


第二部分——構(gòu)造ClassLoader核心類

第二部分 new 一個(gè)自動(dòng)加載的核心類對(duì)象。


loadClassLoader()函數(shù):


從程序里面我們可以看出,composer 先向 PHP 自動(dòng)加載機(jī)制注冊(cè)了一個(gè)函數(shù),這個(gè)函數(shù) require 了 ClassLoader 文件。成功 new 出該文件中核心類 ClassLoader() 后,又銷毀了該函數(shù)。

第三部分 —— 初始化核心類對(duì)象
= 50600 && !defined("HHVM_VERSION");
  if ($useStaticLoader) {
     require_once __DIR__ . "/autoload_static.php";

     call_user_func(
       ComposerAutoloadComposerStaticInit7b790917ce8899df9af8ed53631a1c29::getInitializer($loader)
     );
  } else {
      $map = require __DIR__ . "/autoload_namespaces.php";
      foreach ($map as $namespace => $path) {
         $loader->set($namespace, $path);
      }

      $map = require __DIR__ . "/autoload_psr4.php";
      foreach ($map as $namespace => $path) {
         $loader->setPsr4($namespace, $path);
      }

      $classMap = require __DIR__ . "/autoload_classmap.php";
      if ($classMap) {
          $loader->addClassMap($classMap);
      }
    }
    

這一部分就是對(duì)自動(dòng)加載類的初始化,主要是給自動(dòng)加載核心類初始化頂級(jí)命名空間映射。

初始化的方法有兩種:

  1. 使用 autoload_static 進(jìn)行靜態(tài)初始化;
  2. 調(diào)用核心類接口初始化。

autoload_static 靜態(tài)初始化 ( PHP >= 5.6 )

靜態(tài)初始化只支持 PHP5.6 以上版本并且不支持 HHVM 虛擬機(jī)。我們深入 autoload_static.php 這個(gè)文件發(fā)現(xiàn)這個(gè)文件定義了一個(gè)用于靜態(tài)初始化的類,名字叫 ComposerStaticInit7b790917ce8899df9af8ed53631a1c29,仍然為了避免沖突而加了 hash 值。這個(gè)類很簡(jiǎn)單:

prefixLengthsPsr4
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixLengthsPsr4;

          $loader->prefixDirsPsr4
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixDirsPsr4;

          $loader->prefixesPsr0
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixesPsr0;

          $loader->classMap
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$classMap;

      }, null, ClassLoader::class);
  }

這個(gè)靜態(tài)初始化類的核心就是 getInitializer() 函數(shù),它將自己類中的頂級(jí)命名空間映射給了 ClassLoader 類。值得注意的是這個(gè)函數(shù)返回的是一個(gè)匿名函數(shù),為什么呢?原因就是 ClassLoader類 中的 prefixLengthsPsr4prefixDirsPsr4等等變量都是 private的。利用匿名函數(shù)的綁定功能就可以將這些 private 變量賦給 ClassLoader 類 里的成員變量。

關(guān)于匿名函數(shù)的綁定功能。

接下來就是命名空間初始化的關(guān)鍵了。

classMap(命名空間映射)
 __DIR__ . "/../.." . "/app/Console/Kernel.php",

      "AppExceptionsHandler"
              => __DIR__ . "/../.." . "/app/Exceptions/Handler.php",

      "AppHttpControllersAuthForgotPasswordController"
              => __DIR__ . "/../.." . "/app/Http/Controllers/Auth/ForgotPasswordController.php",

      "AppHttpControllersAuthLoginController"
              => __DIR__ . "/../.." . "/app/Http/Controllers/Auth/LoginController.php",

      "AppHttpControllersAuthRegisterController"
              => __DIR__ . "/../.." . "/app/Http/Controllers/Auth/RegisterController.php",
  ...)

直接命名空間全名與目錄的映射,簡(jiǎn)單粗暴,也導(dǎo)致這個(gè)數(shù)組相當(dāng)?shù)拇蟆?/p> PSR4 標(biāo)準(zhǔn)頂級(jí)命名空間映射數(shù)組:

 array (
        "phpDocumentorReflection" => 25,
    ),
      "S" => array (
        "SymfonyPolyfillMbstring" => 26,
        "SymfonyComponentYaml" => 23,
        "SymfonyComponentVarDumper" => 28,
        ...
    ),
  ...);

  public static $prefixDirsPsr4 = array (
      "phpDocumentorReflection" => array (
        0 => __DIR__ . "/.." . "/phpdocumentor/reflection-common/src",
        1 => __DIR__ . "/.." . "/phpdocumentor/type-resolver/src",
        2 => __DIR__ . "/.." . "/phpdocumentor/reflection-docblock/src",
    ),
       "SymfonyPolyfillMbstring" => array (
        0 => __DIR__ . "/.." . "/symfony/polyfill-mbstring",
    ),
      "SymfonyComponentYaml" => array (
        0 => __DIR__ . "/.." . "/symfony/yaml",
    ),
  ...)

PSR4 標(biāo)準(zhǔn)頂級(jí)命名空間映射用了兩個(gè)數(shù)組,第一個(gè)是用命名空間第一個(gè)字母作為前綴索引,然后是 頂級(jí)命名空間,但是最終并不是文件路徑,而是 頂級(jí)命名空間的長(zhǎng)度。為什么呢?

因?yàn)?PSR4 標(biāo)準(zhǔn)是用頂級(jí)命名空間目錄替換頂級(jí)命名空間,所以獲得頂級(jí)命名空間的長(zhǎng)度很重要。

具體說明這些數(shù)組的作用:

假如我們找 SymfonyPolyfillMbstringexample 這個(gè)命名空間,通過前綴索引和字符串匹配我們得到了

 26,

這條記錄,鍵是頂級(jí)命名空間,值是命名空間的長(zhǎng)度。拿到頂級(jí)命名空間后去 $prefixDirsPsr4數(shù)組 獲取它的映射目錄數(shù)組:(注意映射目錄可能不止一條)

 array (
              0 => __DIR__ . "/.." . "/symfony/polyfill-mbstring",
          )

然后我們就可以將命名空間 SymfonyPolyfillMbstringexample 前26個(gè)字符替換成目錄 __DIR__ . "/.." . "/symfony/polyfill-mbstring ,我們就得到了__DIR__ . "/.." . "/symfony/polyfill-mbstring/example.php,先驗(yàn)證磁盤上這個(gè)文件是否存在,如果不存在接著遍歷。如果遍歷后沒有找到,則加載失敗。

ClassLoader 接口初始化( PHP < 5.6 )

如果PHP版本低于 5.6 或者使用 HHVM 虛擬機(jī)環(huán)境,那么就要使用核心類的接口進(jìn)行初始化。

 $path) {
       $loader->set($namespace, $path);
    }

    // PSR4 標(biāo)準(zhǔn)
    $map = require __DIR__ . "/autoload_psr4.php";
    foreach ($map as $namespace => $path) {
       $loader->setPsr4($namespace, $path);
    }

    $classMap = require __DIR__ . "/autoload_classmap.php";
    if ($classMap) {
       $loader->addClassMap($classMap);
    }
PSR4 標(biāo)準(zhǔn)的映射

autoload_psr4.php 的頂級(jí)命名空間映射

 array($vendorDir . "/dnoegel/php-xdg-base-dir/src"),

    "WebmozartAssert"
        => array($vendorDir . "/webmozart/assert/src"),

    "TijsVerkoyenCssToInlineStyles"
        => array($vendorDir . "/tijsverkoyen/css-to-inline-styles/src"),

    "Tests"
        => array($baseDir . "/tests"),

    "SymfonyPolyfillMbstring"
        => array($vendorDir . "/symfony/polyfill-mbstring"),
    ...
    )

PSR4 標(biāo)準(zhǔn)的初始化接口:

fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ("" !== $prefix[$length - 1]) {
                throw new InvalidArgumentException(
                  "A non-empty PSR-4 prefix must end with a namespace separator."
                );
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

總結(jié)下上面的頂級(jí)命名空間映射過程:

( 前綴 -> 頂級(jí)命名空間,頂級(jí)命名空間 -> 頂級(jí)命名空間長(zhǎng)度 )
( 頂級(jí)命名空間 -> 目錄 )

這兩個(gè)映射數(shù)組。具體形式也可以查看下面的 autoload_static 的 $prefixLengthsPsr4 、 $prefixDirsPsr4 。

命名空間映射

autoload_classmap:

 __DIR__ . "/../.." . "/app/Console/Kernel.php",

    "AppExceptionsHandler"
        => __DIR__ . "/../.." . "/app/Exceptions/Handler.php",
    ...
)

addClassMap:

classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

自動(dòng)加載核心類 ClassLoader 的靜態(tài)初始化到這里就完成了!

其實(shí)說是5部分,真正重要的就兩部分——初始化與注冊(cè)。初始化負(fù)責(zé)頂層命名空間的目錄映射,注冊(cè)負(fù)責(zé)實(shí)現(xiàn)頂層以下的命名空間映射規(guī)則。

第四部分 —— 注冊(cè)

講完了 Composer 自動(dòng)加載功能的啟動(dòng)與初始化,經(jīng)過啟動(dòng)與初始化,自動(dòng)加載核心類對(duì)象已經(jīng)獲得了頂級(jí)命名空間與相應(yīng)目錄的映射,也就是說,如果有命名空間 "AppConsoleKernel,我們已經(jīng)可以找到它對(duì)應(yīng)的類文件所在位置。那么,它是什么時(shí)候被觸發(fā)去找的呢?

這就是 composer 自動(dòng)加載的核心了,我們先回顧一下自動(dòng)加載引導(dǎo)類:

 public static function getLoader()
 {
    /***************************經(jīng)典單例模式********************/
    if (null !== self::$loader) {
        return self::$loader;
    }
    
    /***********************獲得自動(dòng)加載核心類對(duì)象********************/
    spl_autoload_register(array("ComposerAutoloaderInit
    7b790917ce8899df9af8ed53631a1c29", "loadClassLoader"), true, true);
    
    self::$loader = $loader = new ComposerAutoloadClassLoader();
    
    spl_autoload_unregister(array("ComposerAutoloaderInit
    7b790917ce8899df9af8ed53631a1c29", "loadClassLoader"));

    /***********************初始化自動(dòng)加載核心類對(duì)象********************/
    $useStaticLoader = PHP_VERSION_ID >= 50600 && 
    !defined("HHVM_VERSION");
    
    if ($useStaticLoader) {
        require_once __DIR__ . "/autoload_static.php";

        call_user_func(ComposerAutoloadComposerStaticInit
        7b790917ce8899df9af8ed53631a1c29::getInitializer($loader));
  
    } else {
        $map = require __DIR__ . "/autoload_namespaces.php";
        foreach ($map as $namespace => $path) {
            $loader->set($namespace, $path);
        }

        $map = require __DIR__ . "/autoload_psr4.php";
        foreach ($map as $namespace => $path) {
            $loader->setPsr4($namespace, $path);
        }

        $classMap = require __DIR__ . "/autoload_classmap.php";
        if ($classMap) {
            $loader->addClassMap($classMap);
        }
    }

    /***********************注冊(cè)自動(dòng)加載核心類對(duì)象********************/
    $loader->register(true);

    /***********************自動(dòng)加載全局函數(shù)********************/
    if ($useStaticLoader) {
        $includeFiles = ComposerAutoloadComposerStaticInit
        7b790917ce8899df9af8ed53631a1c29::$files;
    } else {
        $includeFiles = require __DIR__ . "/autoload_files.php";
    }
    
    foreach ($includeFiles as $fileIdentifier => $file) {
        composerRequire
        7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
    }

    return $loader;
} 

現(xiàn)在我們開始引導(dǎo)類的第四部分:注冊(cè)自動(dòng)加載核心類對(duì)象。我們來看看核心類的 register() 函數(shù):

public function register($prepend = false)
{
    spl_autoload_register(array($this, "loadClass"), true, $prepend);
}

其實(shí)奧秘都在自動(dòng)加載核心類 ClassLoader 的 loadClass() 函數(shù)上:

public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }
    }

這個(gè)函數(shù)負(fù)責(zé)按照 PSR 標(biāo)準(zhǔn)將頂層命名空間以下的內(nèi)容轉(zhuǎn)為對(duì)應(yīng)的目錄,也就是上面所說的將 "AppConsoleKernel 中" ConsoleKernel 這一段轉(zhuǎn)為目錄,至于怎么轉(zhuǎn)的在下面 “運(yùn)行”的部分講。核心類 ClassLoader 將 loadClass() 函數(shù)注冊(cè)到PHP SPL中的 spl_autoload_register() 里面去。這樣,每當(dāng)PHP遇到一個(gè)不認(rèn)識(shí)的命名空間的時(shí)候,PHP會(huì)自動(dòng)調(diào)用注冊(cè)到 spl_autoload_register 里面的 loadClass() 函數(shù),然后找到命名空間對(duì)應(yīng)的文件。

全局函數(shù)的自動(dòng)加載

Composer 不止可以自動(dòng)加載命名空間,還可以加載全局函數(shù)。怎么實(shí)現(xiàn)的呢?把全局函數(shù)寫到特定的文件里面去,在程序運(yùn)行前挨個(gè) require就行了。這個(gè)就是 composer 自動(dòng)加載的第五步,加載全局函數(shù)。

if ($useStaticLoader) {
    $includeFiles = ComposerAutoloadComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files;
} else {
    $includeFiles = require __DIR__ . "/autoload_files.php";
}
foreach ($includeFiles as $fileIdentifier => $file) {
    composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
}

跟核心類的初始化一樣,全局函數(shù)自動(dòng)加載也分為兩種:靜態(tài)初始化和普通初始化,靜態(tài)加載只支持PHP5.6以上并且不支持HHVM。

靜態(tài)初始化:

ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files:

public static $files = array (
"0e6d7bf4a5811bfa5cf40c5ccd6fae6a" => __DIR__ . "/.." . "/symfony/polyfill-mbstring/bootstrap.php",
"667aeda72477189d0494fecd327c3641" => __DIR__ . "/.." . "/symfony/var-dumper/Resources/functions/dump.php",
...
);
普通初始化

autoload_files:

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
    
return array(
"0e6d7bf4a5811bfa5cf40c5ccd6fae6a" => $vendorDir . "/symfony/polyfill-mbstring/bootstrap.php",
"667aeda72477189d0494fecd327c3641" => $vendorDir . "/symfony/var-dumper/Resources/functions/dump.php",
   ....
);

其實(shí)跟靜態(tài)初始化區(qū)別不大。

加載全局函數(shù)
class ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29{
  public static function getLoader(){
      ...
      foreach ($includeFiles as $fileIdentifier => $file) {
        composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);
      }
      ...
  }
}

function composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file)
 {
    if (empty($GLOBALS["__composer_autoload_files"][$fileIdentifier])) {
        require $file;

        $GLOBALS["__composer_autoload_files"][$fileIdentifier] = true;
    }
}
第五部分 —— 運(yùn)行

到這里,終于來到了核心的核心—— composer 自動(dòng)加載的真相,命名空間如何通過 composer 轉(zhuǎn)為對(duì)應(yīng)目錄文件的奧秘就在這一章。
前面說過,ClassLoader 的 register() 函數(shù)將 loadClass() 函數(shù)注冊(cè)到 PHP 的 SPL 函數(shù)堆棧中,每當(dāng) PHP 遇到不認(rèn)識(shí)的命名空間時(shí)就會(huì)調(diào)用函數(shù)堆棧的每個(gè)函數(shù),直到加載命名空間成功。所以 loadClass() 函數(shù)就是自動(dòng)加載的關(guān)鍵了。

看下 loadClass() 函數(shù):

public function loadClass($class)
{
    if ($file = $this->findFile($class)) {
        includeFile($file);

        return true;
    }
}

public function findFile($class)
{
    // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
    if ("" == $class[0]) {
        $class = substr($class, 1);
    }

    // class map lookup
    if (isset($this->classMap[$class])) {
        return $this->classMap[$class];
    }
    if ($this->classMapAuthoritative) {
        return false;
    }

    $file = $this->findFileWithExtension($class, ".php");

    // Search for Hack files if we are running on HHVM
    if ($file === null && defined("HHVM_VERSION")) {
        $file = $this->findFileWithExtension($class, ".hh");
    }

    if ($file === null) {
        // Remember that this class does not exist.
        return $this->classMap[$class] = false;
    }

    return $file;
}

我們看到 loadClass() ,主要調(diào)用 findFile() 函數(shù)。findFile() 在解析命名空間的時(shí)候主要分為兩部分:classMap 和 findFileWithExtension() 函數(shù)。classMap 很簡(jiǎn)單,直接看命名空間是否在映射數(shù)組中即可。麻煩的是 findFileWithExtension() 函數(shù),這個(gè)函數(shù)包含了 PSR0 和 PSR4 標(biāo)準(zhǔn)的實(shí)現(xiàn)。還有個(gè)值得我們注意的是查找路徑成功后 includeFile() 仍然是外面的函數(shù),并不是 ClassLoader 的成員函數(shù),原理跟上面一樣,防止有用戶寫 $this 或 self。還有就是如果命名空間是以開頭的,要去掉然后再匹配。

看下 findFileWithExtension 函數(shù):

private function findFileWithExtension($class, $ext)
{
    // PSR-4 lookup
    $logicalPathPsr4 = strtr($class, "", DIRECTORY_SEPARATOR) . $ext;
    
    $first = $class[0];
    if (isset($this->prefixLengthsPsr4[$first])) {
        foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
            if (0 === strpos($class, $prefix)) {
                foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
                    if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
                        return $file;
                    }
                }
            }
        }
    }

    // PSR-4 fallback dirs
    foreach ($this->fallbackDirsPsr4 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
            return $file;
        }
    }
    
    // PSR-0 lookup
    if (false !== $pos = strrpos($class, "")) {
        // namespaced class name
        $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
            . strtr(substr($logicalPathPsr4, $pos + 1), "_", DIRECTORY_SEPARATOR);
    } else {
        // PEAR-like class name
        $logicalPathPsr0 = strtr($class, "_", DIRECTORY_SEPARATOR) . $ext;
    }
    
    if (isset($this->prefixesPsr0[$first])) {
        foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
            if (0 === strpos($class, $prefix)) {
                foreach ($dirs as $dir) {
                    if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                        return $file;
                    }
                }
            }
        }
    }
    
    // PSR-0 fallback dirs
    foreach ($this->fallbackDirsPsr0 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
            return $file;
        }
    }
    
    // PSR-0 include paths.
    if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
        return $file;
    }
}
最后小結(jié)

我們通過舉例來說下上面代碼的流程:

如果我們?cè)诖a中寫下 new phpDocumentorReflectionElement(),PHP 會(huì)通過 SPL_autoload_register 調(diào)用 loadClass -> findFile -> findFileWithExtension。步驟如下:

將 轉(zhuǎn)為文件分隔符/,加上后綴php,變成 $logicalPathPsr4, 即 phpDocumentor/Reflection//Element.php;

利用命名空間第一個(gè)字母p作為前綴索引搜索 prefixLengthsPsr4 數(shù)組,查到下面這個(gè)數(shù)組:

        p" => 
            array (
                "phpDocumentorReflection" => 25,
                "phpDocumentorFake" => 19,
          )

遍歷這個(gè)數(shù)組,得到兩個(gè)頂層命名空間 phpDocumentorReflection 和 phpDocumentorFake

在這個(gè)數(shù)組中查找 phpDocumentorReflectionElement,找出 phpDocumentorReflection 這個(gè)頂層命名空間并且長(zhǎng)度為25。

在prefixDirsPsr4 映射數(shù)組中得到phpDocumentorReflection 的目錄映射為:

    "phpDocumentorReflection" => 
        array (
            0 => __DIR__ . "/.." . "/phpdocumentor/reflection-common/src",
            1 => __DIR__ . "/.." . "/phpdocumentor/type-resolver/src",
            2 => __DIR__ . "/.." . "/phpdocumentor/reflection-docblock/src",
        ),

遍歷這個(gè)映射數(shù)組,得到三個(gè)目錄映射;

查看 “目錄+文件分隔符//+substr($logicalPathPsr4, $length)”文件是否存在,存在即返回。這里就是
"__DIR__/../phpdocumentor/reflection-common/src + substr(phpDocumentor/Reflection/Element.php,25)"

如果失敗,則利用 fallbackDirsPsr4 數(shù)組里面的目錄繼續(xù)判斷是否存在文件


以上就是 composer 自動(dòng)加載的原理解析!

The end . Thanks!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/30797.html

相關(guān)文章

  • PHP小知識(shí)點(diǎn)

    摘要:那些瑣碎的知識(shí)點(diǎn)作者記錄的的很奇特很難記的知識(shí)點(diǎn)。易錯(cuò)知識(shí)點(diǎn)整理注意和的區(qū)別中和都是輸出的作用,但是兩者之間還是有細(xì)微的差別。今天手頭不忙,總結(jié)一下,分享過程中掌握的知識(shí)點(diǎn)。 深入理解 PHP 之:Nginx 與 FPM 的工作機(jī)制 這篇文章從 Nginx 與 FPM 的工作機(jī)制出發(fā),探討配置背后的原理,讓我們真正理解 Nginx 與 PHP 是如何協(xié)同工作的。 PHP 那些瑣碎的知識(shí)...

    hover_lew 評(píng)論0 收藏0
  • React高階組件

    摘要:高階組件高階函數(shù)接收函數(shù)作為輸入,或者輸出另一個(gè)函數(shù)的一類函數(shù)高階組件接收組件作為輸入,輸出一個(gè)新的組件的組件。包含了一系列實(shí)用的高階組件庫(kù)拖動(dòng)庫(kù)深入理解高階組件其中詳細(xì)介紹了屬性代理和反向繼承的區(qū)別。 React高階組件 高階函數(shù): 接收函數(shù)作為輸入,或者輸出另一個(gè)函數(shù)的一類函數(shù); 高階組件: 接收React組件作為輸入,輸出一個(gè)新的React組件的組件。 高階組件通過包裹一個(gè)新傳入...

    cncoder 評(píng)論0 收藏0
  • 【Laravel】Laravel 框架關(guān)鍵技術(shù)解析·讀書筆記(一)

    摘要:判斷是否存在構(gòu)造函數(shù),不存在直接實(shí)例化,存在則通過來獲取輸入函數(shù),并有相應(yīng)的方法解決依賴參數(shù)問題,實(shí)現(xiàn)依賴注入。 Laravel 框架關(guān)鍵技術(shù)解析·讀書筆記(一) 第一章 入口文件 請(qǐng)求訪問的入口文件,主要完成幾部分工作,分別是: 自動(dòng)加載函數(shù)的添加 服務(wù)器實(shí)例化與服務(wù)注冊(cè) 路由加載 請(qǐng)求實(shí)例化與路由分發(fā) 相應(yīng)生成與發(fā)送 其中,自動(dòng)加載函數(shù)用于包含引用文件,改文件是composer...

    CocoaChina 評(píng)論0 收藏0
  • ComposerAutoload源碼實(shí)現(xiàn)——注冊(cè)與運(yùn)行

    前言 在開始之前,歡迎關(guān)注我自己的博客:www.leoyang90.cn上一篇 文章我們講到了 Composer 自動(dòng)加載功能的啟動(dòng)與初始化,經(jīng)過啟動(dòng)與初始化,自動(dòng)加載核心類對(duì)象已經(jīng)獲得了頂級(jí)命名空間與相應(yīng)目錄的映射,換句話說,如果有命名空間 AppConsoleKernel,我們已經(jīng)知道了 App 對(duì)應(yīng)的目錄,接下來我們就要解決下面的就是 ConsoleKernel這一段。 注冊(cè) 我們先回顧...

    wanghui 評(píng)論0 收藏0
  • Yii2中代碼自動(dòng)加載機(jī)制

    摘要:中是如何實(shí)現(xiàn)代碼的自動(dòng)加載的入口腳本的以下兩行代碼其中的作用注冊(cè)為自動(dòng)加載函數(shù)。這個(gè)負(fù)責(zé)引入了一個(gè)類中的,隨后立即解除注冊(cè)。注冊(cè)中的為自動(dòng)加載函數(shù),并利用配置文件即目錄下的文件對(duì)這個(gè)自動(dòng)加載函數(shù)進(jìn)行了初始化。 1.基本知識(shí) Include與require 的作用: 當(dāng)一個(gè)文件被包含時(shí),其中所包含的代碼繼承了 include 所在行的變量范圍。從該處開始,調(diào)用文件在該行處可用的任何...

    Jaden 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<