PHP?Composer自動(dòng)加載使用實(shí)戰(zhàn)
一、沒有 composer 時(shí) PHP 是怎么做的
PHP 的 autoload 機(jī)制,可以在使用一個(gè)未導(dǎo)入的類時(shí)動(dòng)態(tài)加載該類,從而實(shí)現(xiàn)延遲加載和管理依賴類文件的目的。
__autoload 自動(dòng)加載器
PHP 中想要使用一個(gè)類,必須通過 require
(指代 require_once, include_once 等) 的方式在文件開頭聲明要使用的類。當(dāng)項(xiàng)目中類較多時(shí),一個(gè)個(gè)聲明加載顯然不可行。
在 PHP5 版本,PHP 支持通過 __autoload
定義一個(gè)自動(dòng)加載器,嘗試加載未定義的類。 如:
// we've writen this code where we need function __autoload($classname) { $filename = "./". $classname .".php"; include_once($filename); } // we've called a class *** $obj = new myClass();
但 __autoload
函數(shù)缺點(diǎn)比較明顯:他只能定義一次,這樣就會(huì)耦合所有依賴的類的自動(dòng)加載邏輯,統(tǒng)統(tǒng)寫到這個(gè)方法里,這時(shí)候就需要用到 spl_autoload_register
函數(shù)了。
使用 spl_autoload_register 注冊(cè)多個(gè)自動(dòng)加載器
spl 是 standard php library 的縮寫。spl_autoload_register
最大的特點(diǎn)是支持注冊(cè)多個(gè)自動(dòng)加載器,這樣就能實(shí)現(xiàn)將各個(gè)類庫的自動(dòng)加載邏輯分開,自己處理自己的加載邏輯。
function my_autoloader($class) { var_dump("my_autoloader", $class); } spl_autoload_register('my_autoloader'); // 靜態(tài)方法 class MyClass1 { public static function autoload($className) { var_dump("MyClass1 autoload", $className); } } spl_autoload_register(array('MyClass1', 'autoload')); // 非靜態(tài)方法 class MyClass2 { public function autoload($className) { var_dump("MyClass2 autoload", $className); } } $instance = new MyClass2(); spl_autoload_register(array($instance, 'autoload')); new \NotDefineClassName(); /* 輸出 string(32) "my_autoloader NotDefineClassName" string(36) "MyClass1 autoload NotDefineClassName" string(36) "MyClass2 autoload NotDefineClassName" */
二、PSR 規(guī)范
PSR 即 PHP Standards Recommendation 是一個(gè)社區(qū)組織:https://www.php-fig.org/psr/,聲明一系列規(guī)范來統(tǒng)一開發(fā)風(fēng)格,減少互不兼容的困擾。規(guī)范中的 PSR-4 代表:Autoloading Standard,即自動(dòng)加載規(guī)范。
PSR-4
其中規(guī)定:一個(gè)類的完整類名應(yīng)該遵循一下規(guī)范:
\<命名空間>(\<子命名空間>)*\<類名>
即:
- 完整的類名必須要有一個(gè)頂級(jí)命名空間,被稱為 “vendor namespace”;
- 完整的類名可以有一個(gè)或多個(gè)子命名空間;
- 完整的類名必須有一個(gè)最終的類名;
- 完整的類名中任意一部分中的下滑線都是沒有特殊含義的;
- 完整的類名可以由任意大小寫字母組成;
- 所有類名都必須是大小寫敏感的。
看看例子:
應(yīng)用的效果簡單來說就是:將命名空間前綴 Namespace Prefix 替換成 Base Directory 目錄,并將 \ 替換成 / 。一句話,命名空間可以表明類具體的存放位置。
三、Composer 自動(dòng)加載的過程
結(jié)合 spl_auto_register
和 PSR-4 的命名空間規(guī)范,可以想象,我們可以通過類的命名空間,來找到具體類的存放位置,然后通過 require 將其加載進(jìn)來生效,composer 就是這么干的。
接下來我們分兩步看 composer 是怎么做的。
第一步,建立類的命名空間和類存放位置的映射關(guān)系
首先看 vendor 目錄下的 autoload.php 文件,所有項(xiàng)目啟動(dòng)必然要先 require 這個(gè)文件。
// autoload.php @generated by Composer // vendor/autoload.php require_once __DIR__ . '/composer/autoload_real.php'; // 返回了autoload_real文件中的類方法 return ComposerAutoloaderInit7e421c277f7e8f810a19524f0d771cdb::getLoader(); /* ------------- */ // vendor/composer/autoload_real.php public static function getLoader() { if (null !== self::$loader) { return self::$loader; } // P0 初始化ClassLoader spl_autoload_register(array('ComposerAutoloaderInit7e421c277f7e8f810a19524f0d771cdb', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInit7e421c277f7e8f810a19524f0d771cdb', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; // P1 向ClassLoader中set命名空間和文件路徑映射關(guān)系 call_user_func(\Composer\Autoload\ComposerStaticInit7e421c277f7e8f810a19524f0d771cdb::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); } } // P2 將ClassLoader中的loadClass方法,注冊(cè)為加載器 $loader->register(true); if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInit7e421c277f7e8f810a19524f0d771cdb::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire7e421c277f7e8f810a19524f0d771cdb($fileIdentifier, $file); } return $loader; }
在代碼 P0 處,上來先實(shí)例化一個(gè) \Composer\Autoload\ClassLoader
類,這個(gè)類里面維護(hù)了所有命名空間到類具體存放位置的映射關(guān)系。
接下來在 P1 處,根據(jù) PHP 版本和運(yùn)行環(huán)境,如是否運(yùn)行在 HHVM 環(huán)境下,來區(qū)分如何向 ClassLoader 中載入映射關(guān)系。
autoload_static.php 文件中定義的映射關(guān)系有三種:
public static $prefixLengthsPsr4 = array ( 'p' => array ( 'phpDocumentor\\Reflection\\' => 25, ), 'W' => array ( 'Webmozart\\Assert\\' => 17, ), 'S' => array ( 'Symfony\\Polyfill\\Ctype\\' => 23, ), 'R' => array ( 'RefactoringGuru\\' => 16, ), 'P' => array ( 'Prophecy\\' => 9, ), 'D' => array ( 'Doctrine\\Instantiator\\' => 22, 'DeepCopy\\' => 9, ), ); public static $prefixDirsPsr4 = array ( 'phpDocumentor\\Reflection\\' => array ( 0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src', 1 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src', 2 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src', ), 'Webmozart\\Assert\\' => array ( 0 => __DIR__ . '/..' . '/webmozart/assert/src', ), 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), 'RefactoringGuru\\' => array ( 0 => __DIR__ . '/../..' . '/', ), 'Prophecy\\' => array ( 0 => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy', ), 'Doctrine\\Instantiator\\' => array ( 0 => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator', ), 'DeepCopy\\' => array ( 0 => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy', ), ); public static $classMap = array ( 'File_Iterator' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Iterator.php', 'File_Iterator_Facade' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Facade.php', 'File_Iterator_Factory' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Factory.php', 'PHPUnit\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Exception.php', ... );
classMap 是完整映射關(guān)系,prefixLengthsPsr4 和 prefixDirsPsr4 是當(dāng)通過完整命名空間找不到時(shí),通過在目標(biāo)類名后加上 .php 再次尋找用。
到此,建立命名空間到類存放路徑的關(guān)系已經(jīng)完成了。
第二步,如何找到類并加載
在上面代碼中,將 ClassLoader 的 loadClass 方法注冊(cè)成加載器:
public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } } function includeFile($file) { include $file; }
其中 findFile 方法,就是通過類名,去尋找文件實(shí)際的位置,如果找到了,就通過 includeFile 將文件加載進(jìn)來。主要看看 findFile 中的邏輯:
public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; }
對(duì)于類的加載十分簡單,直接去 classmap 中取。如果取不到,則將目標(biāo)類名追加 .php 后綴,去$prefixLengthsPsr4 和 $prefixDirsPsr4 中查找。
第三步,如何加載全局函數(shù)
if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInit7e421c277f7e8f810a19524f0d771cdb::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire7e421c277f7e8f810a19524f0d771cdb($fileIdentifier, $file); } return $loader;
還是通過 autoload_static.php 中定義的數(shù)據(jù)去加載:
// autoload_static.php public static $files = array ( '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', '6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php', ); // vendor/symfony/polyfill-ctype/bootstrap.php if (!function_exists('ctype_alnum')) { function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } } if (!function_exists('ctype_alpha')) { function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } }
至此 composer 自動(dòng)加載的邏輯基本就過了一遍。
composer 的 ClassLoader 中的 classMap 是怎么生成出來的?
答案就在 composer 的源碼中:
掃描所有包中的類,然后生成一個(gè) php 文件,例如:getStaticFile 方法
參考:http://chabaoo.cn/program/297620u9q.htm
PHP類自動(dòng)加載的說明:http://chabaoo.cn/article/188078.htm
以上就是PHP Composer自動(dòng)加載使用實(shí)戰(zhàn)的詳細(xì)內(nèi)容,更多關(guān)于PHP Composer自動(dòng)加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
PHP環(huán)境搭建(php+Apache+mysql)
這篇文章主要為大家詳細(xì)介紹了PHP環(huán)境搭建,包括php、Apache、mysql環(huán)境安裝,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11smarty中改進(jìn)truncate使其支持中文的方法
這篇文章主要介紹了smarty中改進(jìn)truncate使其支持中文的方法,涉及針對(duì)Smarty源碼中truncate源文件進(jìn)行函數(shù)功能擴(kuò)展的相關(guān)技巧,需要的朋友可以參考下2016-05-05thinkphp3.2.3框架動(dòng)態(tài)切換多數(shù)據(jù)庫的方法分析
這篇文章主要介紹了thinkphp3.2.3框架動(dòng)態(tài)切換多數(shù)據(jù)庫的方法,結(jié)合實(shí)例形式分析了thinkPHP3.2.3框架多數(shù)據(jù)庫切換的配置、使用相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2020-01-01PHP房貸計(jì)算器實(shí)例代碼,等額本息,等額本金
下面小編就為大家?guī)硪黄狿HP房貸計(jì)算器實(shí)例代碼,等額本息,等額本金。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04