這篇文章主要介紹了composer中自動加載原理的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
創(chuàng)新互聯(lián)公司一直通過網(wǎng)站建設(shè)和網(wǎng)站營銷幫助企業(yè)獲得更多客戶資源。 以"深度挖掘,量身打造,注重實效"的一站式服務(wù),以成都做網(wǎng)站、網(wǎng)站建設(shè)、移動互聯(lián)產(chǎn)品、成都全網(wǎng)營銷推廣服務(wù)為核心業(yè)務(wù)。10余年網(wǎng)站制作的經(jīng)驗,使用新網(wǎng)站建設(shè)技術(shù),全新開發(fā)出的標(biāo)準(zhǔn)網(wǎng)站,不但價格便宜而且實用、靈活,特別適合中小公司網(wǎng)站制作。網(wǎng)站管理系統(tǒng)簡單易用,維護(hù)方便,您可以完全操作網(wǎng)站資料,是中小公司快速網(wǎng)站建設(shè)的選擇。
PHP 自5.3的版本之后,已經(jīng)重?zé)ㄐ律?,命名空間、性狀(trait)、閉包、接口、PSR 規(guī)范、以及 composer 的出現(xiàn)已經(jīng)讓 PHP 變成了一門現(xiàn)代化的腳本語言。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 自動加載功能
PSR 規(guī)范
comoposer 的自動加載過程
composer 源碼分析
在 PHP 開發(fā)過程中,如果希望從外部引入一個 Class ,通常會使用 include
和 require
方法,去把定義這個 Class 的文件包含進(jìn)來。這個在小規(guī)模開發(fā)的時候,沒什么大問題。但在大型的開發(fā)項目中,使用這種方式會帶來一些隱含的問題:如果一個 PHP 文件需要使用很多其它類,那么就需要很多的 require/include
語句,這樣有可能會 造成遺漏或者 包含進(jìn)不必要的類文件。如果大量的文件都需要使用其它的類,那么要保證每個文件都包含正確的類文件肯定是一個噩夢, 況且 require或 incloud 的性能代價很大。
PHP5 為這個問題提供了一個解決方案,這就是 類的自動加載(autoload)機(jī)制
。autoload機(jī)制
可以使得 PHP 程序有可能在使用類時才自動包含類文件,而不是一開始就將所有的類文件include
進(jìn)來,這種機(jī)制也稱為 Lazy loading (惰性加載)
。
總結(jié)起來,自動加載功能帶來了幾處優(yōu)點(diǎn):
使用類之前無需 include / require
使用類的時候才會 include / require
文件,實現(xiàn)了 lazy loading
,避免了 include / require
多余文件。
無需考慮引入 類的實際磁盤地址,實現(xiàn)了邏輯和實體文件的分離。
從 PHP5 開始,當(dāng)我們在使用一個類時,如果發(fā)現(xiàn)這個類沒有加載,就會自動運(yùn)行 __autoload() 函數(shù),這個函數(shù)是我們在程序中自定義的,在這個函數(shù)中我們可以加載需要使用的類。下面是個簡單的示例:
在我們這個簡單的例子中,我們直接將類名加上擴(kuò)展名 .class.php
構(gòu)成了類文件名,然后使用 require_once
將其加載。
從這個例子中,我們可以看出 __autoload 至少要做三件事情:
根據(jù)類名確定類文件名;
確定類文件所在的磁盤路徑;
將類從磁盤文件中加載到系統(tǒng)中。
第三步最簡單,只需要使用 include / require
即可。要實現(xiàn)第一步,第二步的功能,必須在開發(fā)時約定類名與磁盤文件的映射方法,只有這樣我們才能根據(jù)類名找到它對應(yīng)的磁盤文件。
當(dāng)有大量的類文件要包含的時候,我們只要確定相應(yīng)的規(guī)則,然后在 __autoload()
函數(shù)中,將類名與實際的磁盤文件對應(yīng)起來,就可以實現(xiàn) lazy loading
的效果。
如果想詳細(xì)的了解關(guān)于 autoload 自動加載的過程,可以查看手冊資料:PHP autoload函數(shù)說明
如果在一個系統(tǒng)的實現(xiàn)中,如果需要使用很多其它的類庫,這些類庫可能是由不同的開發(fā)人員編寫的, 其類名與實際的磁盤文件的映射規(guī)則不盡相同。這時如果要實現(xiàn)類庫文件的自動加載,就必須 在 __autoload() 函數(shù)中將所有的映射規(guī)則全部實現(xiàn),這樣的話 __autoload()
函數(shù)有可能會非常復(fù)雜,甚至無法實現(xiàn)。最后可能會導(dǎo)致 __autoload()
函數(shù)十分臃腫,這時即便能夠?qū)崿F(xiàn),也會給將來的維護(hù)和系統(tǒng)效率帶來很大的負(fù)面影響。
那么問題出現(xiàn)在哪里呢?問題出現(xiàn)在 __autoload() 是全局函數(shù)只能定義一次,不夠靈活,所以所有的類名與文件名對應(yīng)的邏輯規(guī)則都要在一個函數(shù)里面實現(xiàn),造成這個函數(shù)的臃腫。那么如何來解決這個問題呢?答案就是使用一個 __autoload調(diào)用堆棧,不同的映射關(guān)系寫到不同的 __autoload函數(shù)
中去,然后統(tǒng)一注冊統(tǒng)一管理,這個就是 PHP5 引入的 SPL Autoload
。
SPL是 Standard PHP Library(標(biāo)準(zhǔn)PHP庫)的縮寫。它是 PHP5 引入的一個擴(kuò)展標(biāo)準(zhǔn)庫,包括 spl autoload 相關(guān)的函數(shù)以及各種數(shù)據(jù)結(jié)構(gòu)和迭代器的接口或類。spl autoload 相關(guān)的函數(shù)具體可見 php中spl_autoload
spl_autoload_register() 就是我們上面所說的__autoload調(diào)用堆棧,我們可以向這個函數(shù)注冊多個我們自己的 autoload() 函數(shù),當(dāng) PHP 找不到類名時,PHP就會調(diào)用這個堆棧,然后去調(diào)用自定義的 autoload() 函數(shù),實現(xiàn)自動加載功能。如果我們不向這個函數(shù)輸入任何參數(shù),那么就會默認(rèn)注冊 spl_autoload() 函數(shù)。
二、PSR 規(guī)范
與自動加載相關(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)一各個項目的編碼規(guī)范,避免各家自行發(fā)展的風(fēng)格阻礙了程序員開發(fā)的困擾,于是大伙發(fā)明和總結(jié)了 PSR,PSR 是 PHP Standards Recommendation 的縮寫,截止到目前為止,總共有 14 套 PSR 規(guī)范,其中有 7 套PSR規(guī)范已通過表決并推出使用,分別是:
PSR-0 自動加載標(biāo)準(zhǔn)(已廢棄,一些舊的第三方庫還有在使用)PSR-1 基礎(chǔ)編碼標(biāo)準(zhǔn)
PSR-2 編碼風(fēng)格向?qū)?/strong>
PSR-3 日志接口
PSR-4 自動加載的增強(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 個規(guī)范——PSR-4。
PSR-4 規(guī)范了如何指定文件路徑從而自動加載類定義,同時規(guī)范了自動加載文件的位置。
1)一個完整的類名需具有以下結(jié)構(gòu):
\<命名空間>\<子命名空間>\<類名>
完整的類名必須要有一個頂級命名空間,被稱為 "vendor namespace";
完整的類名可以有一個或多個子命名空間;
完整的類名必須有一個最終的類名;
完整的類名中任意一部分中的下滑線都是沒有特殊含義的;
完整的類名可以由任意大小寫字母組成;
所有類名都必須是大小寫敏感的。
完整的類名中,去掉最前面的命名空間分隔符,前面連續(xù)的一個或多個命名空間和子命名空間,作為「命名空間前綴」,其必須與至少一個「文件基目錄」相對應(yīng);
緊接命名空間前綴后的子命名空間 必須 與相應(yīng)的「文件基目錄」相匹配,其中的命名空間分隔符將作為目錄分隔符。
末尾的類名必須與對應(yīng)的以 .php 為后綴的文件同名。
自動加載器(autoloader)的實現(xiàn)一定不可拋出異常、一定不可觸發(fā)任一級別的錯誤信息以及不應(yīng)該有返回值。
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_Name\Package_Name\ClassName | | | -tests/ | | | | -ClassNameTest.php # Vendor_Name\Package_Name\ClassNameTest
你有一個項目依賴于若干個庫。
其中一些庫依賴于其他庫。
你聲明你所依賴的東西。
Composer 會找出哪個版本的包需要安裝,并安裝它們(將它們下載到你的項目中)。
例如,你正在創(chuàng)建一個項目,需要做一些單元測試。你決定使用 phpunit
。為了將它添加到你的項目中,你所需要做的就是在 composer.json
文件里描述項目的依賴關(guān)系。
{ "require": { "phpunit/phpunit":"~6.0", } }
然后在 composer require
之后我們只要在項目里面直接 use
phpunit 的類即可使用。
composer 會找到符合 PR4 規(guī)范的第三方庫的源
將其加載到 vendor 目錄下
初始化頂級域名的映射并寫入到指定的文件里
(如:'PHPUnit\\Framework\\Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php'
)
寫好一個 autoload 函數(shù),并且注冊到 spl_autoload_register()里
題外話:現(xiàn)在很多框架都已經(jīng)幫我們寫好了頂級域名映射了,我們只需要在框架里面新建文件,在新建的文件中寫好命名空間,就可以在任何地方 use 我們的命名空間了。
下面我們通過對源碼的分析來看看 composer 是如何實現(xiàn) PSR4標(biāo)準(zhǔn)
的自動加載功能。
很多框架在初始化的時候都會引入 composer 來協(xié)助自動加載的,以 Laravel 為例,它入口文件 index.php 第一句就是利用 composer 來實現(xiàn)自動加載功能。
去 vendor 目錄下的
autoload.php
:這里就是 Composer 真正開始的地方了
Composer自動加載文件
首先,我們先大致了解一下Composer自動加載所用到的源文件。
autoload_real.php: 自動加載功能的引導(dǎo)類。
composer 加載類的初始化
(頂級命名空間與文件路徑映射初始化)
和注冊(spl_autoload_register())。ClassLoader.php : composer 加載類。
composer 自動加載功能的核心類。
autoload_static.php : 頂級命名空間初始化類,
用于給核心類初始化頂級命名空間。
autoload_classmap.php : 自動加載的最簡單形式,
有完整的命名空間和文件目錄的映射;
autoload_files.php : 用于加載全局函數(shù)的文件,
存放各個全局函數(shù)所在的文件路徑名;
autoload_namespaces.php : 符合 PSR0 標(biāo)準(zhǔn)的自動加載文件,
存放著頂級命名空間與文件的映射;
autoload_psr4.php : 符合 PSR4 標(biāo)準(zhǔn)的自動加載文件,
存放著頂級命名空間與文件的映射;
autoload_real 引導(dǎo)類
在 vendor 目錄下的
autoload.php
文件中我們可以看出,程序主要調(diào)用了引導(dǎo)類的靜態(tài)方法getLoader()
,我們接著看看這個函數(shù)。= 50600 && !defined('HHVM_VERSION'); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func( \Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::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); } } /***********************注冊自動加載核心類對象********************/ $loader->register(true); /***********************自動加載全局函數(shù)********************/ if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file); } return $loader; }我把自動加載引導(dǎo)類分為 5 個部分。
第一部分——單例
第一部分很簡單,就是個最經(jīng)典的單例模式,自動加載類只能有一個。
第二部分——構(gòu)造ClassLoader核心類
第二部分 new 一個自動加載的核心類對象。
loadClassLoader()
函數(shù):從程序里面我們可以看出,composer 先向 PHP 自動加載機(jī)制注冊了一個函數(shù),這個函數(shù) require 了 ClassLoader 文件。成功 new 出該文件中核心類 ClassLoader() 后,又銷毀了該函數(shù)。
第三部分 —— 初始化核心類對象
= 50600 && !defined('HHVM_VERSION'); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func( \Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::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); } }這一部分就是對自動加載類的初始化,主要是給自動加載核心類初始化頂級命名空間映射。
初始化的方法有兩種:
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
這個文件發(fā)現(xiàn)這個文件定義了一個用于靜態(tài)初始化的類,名字叫ComposerStaticInit7b790917ce8899df9af8ed53631a1c29
,仍然為了避免沖突而加了 hash 值。這個類很簡單:prefixLengthsPsr4 = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixesPsr0; $loader->classMap = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$classMap; }, null, ClassLoader::class); }這個靜態(tài)初始化類的核心就是
getInitializer()
函數(shù),它將自己類中的頂級命名空間映射給了 ClassLoader 類。值得注意的是這個函數(shù)返回的是一個匿名函數(shù),為什么呢?原因就是ClassLoader類
中的prefixLengthsPsr4
、prefixDirsPsr4
等等變量都是 private的。利用匿名函數(shù)的綁定功能就可以將這些 private 變量賦給 ClassLoader 類 里的成員變量。關(guān)于匿名函數(shù)的綁定功能。
接下來就是命名空間初始化的關(guān)鍵了。
classMap(命名空間映射)
__DIR__ . '/../..' . '/app/Console/Kernel.php', 'App\\Exceptions\\Handler' => __DIR__ . '/../..' . '/app/Exceptions/Handler.php', 'App\\Http\\Controllers\\Auth\\ForgotPasswordController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/ForgotPasswordController.php', 'App\\Http\\Controllers\\Auth\\LoginController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/LoginController.php', 'App\\Http\\Controllers\\Auth\\RegisterController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/RegisterController.php', ...)直接命名空間全名與目錄的映射,簡單粗暴,也導(dǎo)致這個數(shù)組相當(dāng)?shù)拇蟆?/p>
PSR4 標(biāo)準(zhǔn)頂級命名空間映射數(shù)組:
array ( 'phpDocumentor\\Reflection\\' => 25, ), 'S' => array ( 'Symfony\\Polyfill\\Mbstring\\' => 26, 'Symfony\\Component\\Yaml\\' => 23, 'Symfony\\Component\\VarDumper\\' => 28, ... ), ...); public static $prefixDirsPsr4 = array ( 'phpDocumentor\\Reflection\\' => array ( 0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src', 1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src', 2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src', ), 'Symfony\\Polyfill\\Mbstring\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', ), 'Symfony\\Component\\Yaml\\' => array ( 0 => __DIR__ . '/..' . '/symfony/yaml', ), ...)PSR4 標(biāo)準(zhǔn)頂級命名空間映射用了兩個數(shù)組,第一個是用命名空間第一個字母作為前綴索引,然后是 頂級命名空間,但是最終并不是文件路徑,而是 頂級命名空間的長度。為什么呢?
因為 PSR4 標(biāo)準(zhǔn)是用頂級命名空間目錄替換頂級命名空間,所以獲得頂級命名空間的長度很重要。
具體說明這些數(shù)組的作用:
假如我們找
Symfony\Polyfill\Mbstring\example
這個命名空間,通過前綴索引和字符串匹配我們得到了26,這條記錄,鍵是頂級命名空間,值是命名空間的長度。拿到頂級命名空間后去
$prefixDirsPsr4數(shù)組
獲取它的映射目錄數(shù)組:(注意映射目錄可能不止一條)array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', )然后我們就可以將命名空間
Symfony\\Polyfill\\Mbstring\\example
前26個字符替換成目錄__DIR__ . '/..' . '/symfony/polyfill-mbstring
,我們就得到了__DIR__ . '/..' . '/symfony/polyfill-mbstring/example.php
,先驗證磁盤上這個文件是否存在,如果不存在接著遍歷。如果遍歷后沒有找到,則加載失敗。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 的頂級命名空間映射
array($vendorDir . '/dnoegel/php-xdg-base-dir/src'), 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), 'TijsVerkoyen\\CssToInlineStyles\\' => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'), 'Tests\\' => array($baseDir . '/tests'), 'Symfony\\Polyfill\\Mbstring\\' => 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é)下上面的頂級命名空間映射過程:
( 前綴 -> 頂級命名空間,頂級命名空間 -> 頂級命名空間長度 ) ( 頂級命名空間 -> 目錄 )這兩個映射數(shù)組。具體形式也可以查看下面的
autoload_static
的 $prefixLengthsPsr4 、 $prefixDirsPsr4 。命名空間映射
autoload_classmap:
__DIR__ . '/../..' . '/app/Console/Kernel.php', 'App\\Exceptions\\Handler' => __DIR__ . '/../..' . '/app/Exceptions/Handler.php', ... )addClassMap:
classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } }自動加載核心類 ClassLoader 的靜態(tài)初始化到這里就完成了!
其實說是5部分,真正重要的就兩部分——初始化與注冊。初始化負(fù)責(zé)頂層命名空間的目錄映射,注冊負(fù)責(zé)實現(xiàn)頂層以下的命名空間映射規(guī)則。
第四部分 —— 注冊
講完了 Composer 自動加載功能的啟動與初始化,經(jīng)過啟動與初始化,自動加載核心類對象已經(jīng)獲得了頂級命名空間與相應(yīng)目錄的映射,也就是說,如果有命名空間 'App\Console\Kernel,我們已經(jīng)可以找到它對應(yīng)的類文件所在位置。那么,它是什么時候被觸發(fā)去找的呢?
這就是 composer 自動加載的核心了,我們先回顧一下自動加載引導(dǎo)類:
public static function getLoader() { /***************************經(jīng)典單例模式********************/ if (null !== self::$loader) { return self::$loader; } /***********************獲得自動加載核心類對象********************/ spl_autoload_register(array('ComposerAutoloaderInit 7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInit 7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader')); /***********************初始化自動加載核心類對象********************/ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION'); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit 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); } } /***********************注冊自動加載核心類對象********************/ $loader->register(true); /***********************自動加載全局函數(shù)********************/ if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInit 7b790917ce8899df9af8ed53631a1c29::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire 7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file); } return $loader; }現(xiàn)在我們開始引導(dǎo)類的第四部分:注冊自動加載核心類對象。我們來看看核心類的 register() 函數(shù):
public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); }其實奧秘都在自動加載核心類 ClassLoader 的 loadClass() 函數(shù)上:
public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } }這個函數(shù)負(fù)責(zé)按照 PSR 標(biāo)準(zhǔn)將頂層命名空間以下的內(nèi)容轉(zhuǎn)為對應(yīng)的目錄,也就是上面所說的將 'App\Console\Kernel 中' Console\Kernel 這一段轉(zhuǎn)為目錄,至于怎么轉(zhuǎn)的在下面 “運(yùn)行”的部分講。核心類 ClassLoader 將 loadClass() 函數(shù)注冊到PHP SPL中的 spl_autoload_register() 里面去。這樣,每當(dāng)PHP遇到一個不認(rèn)識的命名空間的時候,PHP會自動調(diào)用注冊到 spl_autoload_register 里面的 loadClass() 函數(shù),然后找到命名空間對應(yīng)的文件。
全局函數(shù)的自動加載
Composer 不止可以自動加載命名空間,還可以加載全局函數(shù)。怎么實現(xiàn)的呢?把全局函數(shù)寫到特定的文件里面去,在程序運(yùn)行前挨個 require就行了。這個就是 composer 自動加載的第五步,加載全局函數(shù)。
if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file); }跟核心類的初始化一樣,全局函數(shù)自動加載也分為兩種:靜態(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', .... );其實跟靜態(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 自動加載的真相,命名空間如何通過 composer 轉(zhuǎn)為對應(yīng)目錄文件的奧秘就在這一章。
前面說過,ClassLoader 的 register() 函數(shù)將 loadClass() 函數(shù)注冊到 PHP 的 SPL 函數(shù)堆棧中,每當(dāng) PHP 遇到不認(rèn)識的命名空間時就會調(diào)用函數(shù)堆棧的每個函數(shù),直到加載命名空間成功。所以 loadClass() 函數(shù)就是自動加載的關(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() 在解析命名空間的時候主要分為兩部分:classMap 和 findFileWithExtension() 函數(shù)。classMap 很簡單,直接看命名空間是否在映射數(shù)組中即可。麻煩的是 findFileWithExtension() 函數(shù),這個函數(shù)包含了 PSR0 和 PSR4 標(biāo)準(zhǔn)的實現(xiàn)。還有個值得我們注意的是查找路徑成功后 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é)
我們通過舉例來說下上面代碼的流程:
如果我們在代碼中寫下
new phpDocumentor\Reflection\Element()
,PHP 會通過 SPL_autoload_register 調(diào)用 loadClass -> findFile -> findFileWithExtension。步驟如下:
將 \ 轉(zhuǎn)為文件分隔符/,加上后綴php,變成 $logicalPathPsr4, 即 phpDocumentor/Reflection//Element.php;
利用命名空間第一個字母p作為前綴索引搜索 prefixLengthsPsr4 數(shù)組,查到下面這個數(shù)組:
p' => array ( 'phpDocumentor\\Reflection\\' => 25, 'phpDocumentor\\Fake\\' => 19, )
遍歷這個數(shù)組,得到兩個頂層命名空間 phpDocumentor\Reflection\ 和 phpDocumentor\Fake\
在這個數(shù)組中查找 phpDocumentor\Reflection\Element,找出 phpDocumentor\Reflection\ 這個頂層命名空間并且長度為25。
在prefixDirsPsr4 映射數(shù)組中得到phpDocumentor\Reflection\ 的目錄映射為:
'phpDocumentor\\Reflection\\' => array ( 0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src', 1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src', 2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src', ),
遍歷這個映射數(shù)組,得到三個目錄映射;
查看 “目錄+文件分隔符//+substr($logicalPathPsr4, $length)”文件是否存在,存在即返回。這里就是
'__DIR__/../phpdocumentor/reflection-common/src + substr(phpDocumentor/Reflection/Element.php,25)'
如果失敗,則利用 fallbackDirsPsr4 數(shù)組里面的目錄繼續(xù)判斷是否存在文件
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“composer中自動加載原理的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!
文章名稱:composer中自動加載原理的示例分析
標(biāo)題來源:http://weahome.cn/article/jshsoj.html