PHP?Composer自動加載使用實戰(zhàn)
一、沒有 composer 時 PHP 是怎么做的
PHP 的 autoload 機(jī)制,可以在使用一個未導(dǎo)入的類時動態(tài)加載該類,從而實現(xiàn)延遲加載和管理依賴類文件的目的。
__autoload 自動加載器
PHP 中想要使用一個類,必須通過 require (指代 require_once, include_once 等) 的方式在文件開頭聲明要使用的類。當(dāng)項目中類較多時,一個個聲明加載顯然不可行。
在 PHP5 版本,PHP 支持通過 __autoload 定義一個自動加載器,嘗試加載未定義的類。 如:
// 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ù)缺點比較明顯:他只能定義一次,這樣就會耦合所有依賴的類的自動加載邏輯,統(tǒng)統(tǒng)寫到這個方法里,這時候就需要用到 spl_autoload_register 函數(shù)了。
使用 spl_autoload_register 注冊多個自動加載器
spl 是 standard php library 的縮寫。spl_autoload_register 最大的特點是支持注冊多個自動加載器,這樣就能實現(xiàn)將各個類庫的自動加載邏輯分開,自己處理自己的加載邏輯。
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 是一個社區(qū)組織:https://www.php-fig.org/psr/,聲明一系列規(guī)范來統(tǒng)一開發(fā)風(fēng)格,減少互不兼容的困擾。規(guī)范中的 PSR-4 代表:Autoloading Standard,即自動加載規(guī)范。
PSR-4
其中規(guī)定:一個類的完整類名應(yīng)該遵循一下規(guī)范:
\<命名空間>(\<子命名空間>)*\<類名>
即:
- 完整的類名必須要有一個頂級命名空間,被稱為 “vendor namespace”;
- 完整的類名可以有一個或多個子命名空間;
- 完整的類名必須有一個最終的類名;
- 完整的類名中任意一部分中的下滑線都是沒有特殊含義的;
- 完整的類名可以由任意大小寫字母組成;
- 所有類名都必須是大小寫敏感的。
看看例子:

應(yīng)用的效果簡單來說就是:將命名空間前綴 Namespace Prefix 替換成 Base Directory 目錄,并將 \ 替換成 / 。一句話,命名空間可以表明類具體的存放位置。
三、Composer 自動加載的過程
結(jié)合 spl_auto_register 和 PSR-4 的命名空間規(guī)范,可以想象,我們可以通過類的命名空間,來找到具體類的存放位置,然后通過 require 將其加載進(jìn)來生效,composer 就是這么干的。
接下來我們分兩步看 composer 是怎么做的。
第一步,建立類的命名空間和類存放位置的映射關(guān)系
首先看 vendor 目錄下的 autoload.php 文件,所有項目啟動必然要先 require 這個文件。
// 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方法,注冊為加載器
$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 處,上來先實例化一個 \Composer\Autoload\ClassLoader 類,這個類里面維護(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)通過完整命名空間找不到時,通過在目標(biāo)類名后加上 .php 再次尋找用。
到此,建立命名空間到類存放路徑的關(guān)系已經(jīng)完成了。
第二步,如何找到類并加載
在上面代碼中,將 ClassLoader 的 loadClass 方法注冊成加載器:
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
function includeFile($file)
{
include $file;
}其中 findFile 方法,就是通過類名,去尋找文件實際的位置,如果找到了,就通過 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;
}對于類的加載十分簡單,直接去 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 自動加載的邏輯基本就過了一遍。
composer 的 ClassLoader 中的 classMap 是怎么生成出來的?
答案就在 composer 的源碼中:
掃描所有包中的類,然后生成一個 php 文件,例如:getStaticFile 方法
參考:http://www.dbjr.com.cn/program/297620u9q.htm
PHP類自動加載的說明:http://www.dbjr.com.cn/article/188078.htm
以上就是PHP Composer自動加載使用實戰(zhàn)的詳細(xì)內(nèi)容,更多關(guān)于PHP Composer自動加載的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
PHP環(huán)境搭建(php+Apache+mysql)
這篇文章主要為大家詳細(xì)介紹了PHP環(huán)境搭建,包括php、Apache、mysql環(huán)境安裝,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11
smarty中改進(jìn)truncate使其支持中文的方法
這篇文章主要介紹了smarty中改進(jìn)truncate使其支持中文的方法,涉及針對Smarty源碼中truncate源文件進(jìn)行函數(shù)功能擴(kuò)展的相關(guān)技巧,需要的朋友可以參考下2016-05-05
thinkphp3.2.3框架動態(tài)切換多數(shù)據(jù)庫的方法分析
這篇文章主要介紹了thinkphp3.2.3框架動態(tài)切換多數(shù)據(jù)庫的方法,結(jié)合實例形式分析了thinkPHP3.2.3框架多數(shù)據(jù)庫切換的配置、使用相關(guān)操作技巧與注意事項,需要的朋友可以參考下2020-01-01

