前言
在開(kāi)始之前,歡迎關(guān)注我自己的博客:www.leoyang90.cn
上一篇 文章我們講到了 Composer 自動(dòng)加載功能的啟動(dòng)與初始化,經(jīng)過(guò)啟動(dòng)與初始化,自動(dòng)加載核心類對(duì)象已經(jīng)獲得了頂級(jí)命名空間與相應(yīng)目錄的映射,換句話說(shuō),如果有命名空間 "AppConsoleKernel,我們已經(jīng)知道了 App 對(duì)應(yīng)的目錄,接下來(lái)我們就要解決下面的就是 ConsoleKernel這一段。
我們先回顧一下自動(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 832ea71bfb9a4128da8660baedaac82e", "loadClassLoader"), true, true); self::$loader = $loader = new ComposerAutoloadClassLoader(); spl_autoload_unregister(array("ComposerAutoloaderInit 832ea71bfb9a4128da8660baedaac82e", "loadClassLoader")); /***********************初始化自動(dòng)加載核心類對(duì)象********************/ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined("HHVM_VERSION"); if ($useStaticLoader) { require_once __DIR__ . "/autoload_static.php"; call_user_func(ComposerAutoloadComposerStaticInit 832ea71bfb9a4128da8660baedaac82e::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 832ea71bfb9a4128da8660baedaac82e::$files; } else { $includeFiles = require __DIR__ . "/autoload_files.php"; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire 832ea71bfb9a4128da8660baedaac82e($fileIdentifier, $file); } return $loader; }
現(xiàn)在我們開(kāi)始引導(dǎo)類的第四部分:注冊(cè)自動(dòng)加載核心類對(duì)象。我們來(lái)看看核心類的 register() 函數(shù):
public function register($prepend = false) { spl_autoload_register(array($this, "loadClass"), true, $prepend); }
簡(jiǎn)單到爆炸??!一行代碼實(shí)現(xiàn)自動(dòng)加載有木有!其實(shí)奧秘都在自動(dòng)加載核心類 ClassLoader 的 loadClass() 函數(shù)上,這個(gè)函數(shù)負(fù)責(zé)按照 PSR 標(biāo)準(zhǔn)將頂層命名空間以下的內(nèi)容轉(zhuǎn)為對(duì)應(yīng)的目錄,也就是上面所說(shuō)的將 "AppConsoleKernel中"ConsoleKernel 這一段轉(zhuǎn)為目錄,至于怎么轉(zhuǎn)的我們?cè)谙旅妗癈omposer 自動(dòng)加載源碼分析——運(yùn)行”講。核心類 ClassLoader 將 loadClass() 函數(shù)注冊(cè)到 PHP SPL 中的spl_autoload_register() 里面去,這個(gè)函數(shù)的來(lái)龍去脈我們之前 文章 講過(guò)。這樣,每當(dāng) PHP 遇到一個(gè)不認(rèn)識(shí)的命名空間的時(shí)候,PHP 會(huì)自動(dòng)調(diào)用注冊(cè)到 spl_autoload_register 里面的函數(shù)堆棧,運(yùn)行其中的每個(gè)函數(shù),直到找到命名空間對(duì)應(yīng)的文件。
全局函數(shù)的自動(dòng)加載Composer 不止可以自動(dòng)加載命名空間,還可以加載全局函數(shù)。怎么實(shí)現(xiàn)的呢?很簡(jiǎn)單,把全局函數(shù)寫(xiě)到特定的文件里面去,在程序運(yùn)行前挨個(gè) require 就行了。這個(gè)就是 composer 自動(dòng)加載的第五步,加載全局函數(shù)。
if ($useStaticLoader) { $includeFiles = ComposerAutoloadComposerStaticInit832ea71bfb9a4128da8660baedaac82e::$files; } else { $includeFiles = require __DIR__ . "/autoload_files.php"; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire832ea71bfb9a4128da8660baedaac82e($fileIdentifier, $file); }
跟核心類的初始化一樣,全局函數(shù)自動(dòng)加載也分為兩種:靜態(tài)初始化和普通初始化,靜態(tài)加載只支持 PHP5.6 以上并且不支持 HHVM。
靜態(tài)初始化:ComposerStaticInit832ea71bfb9a4128da8660baedaac82e::$files:
public static $files = array ( "0e6d7bf4a5811bfa5cf40c5ccd6fae6a" => __DIR__ . "/.." . "/symfony/polyfill-mbstring/bootstrap.php", "667aeda72477189d0494fecd327c3641" => __DIR__ . "/.." . "/symfony/var-dumper/Resources/functions/dump.php", ... );
看到這里我們可能又要有疑問(wèn)了,為什么不直接放文件路徑名,還要一個(gè) hash 干什么呢?這個(gè)我們一會(huì)兒講,我們這里先了解一下這個(gè)數(shù)組的結(jié)構(gòu)。
普通初始化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 ComposerAutoloaderInit832ea71bfb9a4128da8660baedaac82e{ public static function getLoader(){ ... foreach ($includeFiles as $fileIdentifier => $file) { composerRequire832ea71bfb9a4128da8660baedaac82e($fileIdentifier, $file); } ... } } function composerRequire832ea71bfb9a4128da8660baedaac82e($fileIdentifier, $file) { if (empty($GLOBALS["__composer_autoload_files"][$fileIdentifier])) { require $file; $GLOBALS["__composer_autoload_files"][$fileIdentifier] = true; } }
這一段很有講究,
第一個(gè)問(wèn)題:為什么自動(dòng)加載引導(dǎo)類的 getLoader() 函數(shù)不直接 require includeFiles 里面的每個(gè)文件名,而要用類外面的函數(shù)composerRequire832ea71bfb9a4128da8660baedaac82e0?(順便說(shuō)下這個(gè)函數(shù)名 hash 仍然為了避免和用戶定義函數(shù)沖突)因?yàn)榕掠腥嗽谌趾瘮?shù)所在的文件寫(xiě) this 或者self。
假如 includeFiles 有個(gè) app/helper.php 文件,這個(gè) helper.php 文件的函數(shù)外有一行代碼:this->foo(),如果引導(dǎo)類在 getLoader() 函數(shù)直接 require(file),那么引導(dǎo)類就會(huì)運(yùn)行這句代碼,調(diào)用自己的 foo() 函數(shù),這顯然是錯(cuò)的。事實(shí)上 helper.php 就不應(yīng)該出現(xiàn) this 或 self 這樣的代碼,這樣寫(xiě)一般都是用戶寫(xiě)錯(cuò)了的,一旦這樣的事情發(fā)生,第一種情況:引導(dǎo)類恰好有 foo() 函數(shù),那么就會(huì)莫名其妙執(zhí)行了引導(dǎo)類的 foo();第二種情況:引導(dǎo)類沒(méi)有 foo() 函數(shù),但是卻甩出來(lái)引導(dǎo)類沒(méi)有 foo() 方法這樣的錯(cuò)誤提示,用戶不知道自己哪里錯(cuò)了。把 require 語(yǔ)句放到引導(dǎo)類的外面,遇到 this 或者 self,程序就會(huì)告訴用戶根本沒(méi)有類,this 或 self 無(wú)效,錯(cuò)誤信息更加明朗。
第二個(gè)問(wèn)題,為什么要用 hash 作為 fileIdentifier,上面的代碼明顯可以看出來(lái)這個(gè)變量是用來(lái)控制全局函數(shù)只被 require 一次的,那為什么不用 require_once 呢?事實(shí)上require_once 比 require 效率低很多,使用全局變量 GLOBALS 這樣控制加載會(huì)更快。
但是其實(shí)也帶來(lái)了一些問(wèn)題,如果存在兩個(gè)自動(dòng)加載,而且全局函數(shù)的相對(duì)路徑不一致,很容易造成 hash 不相同,但是文件相同的情況,導(dǎo)致重復(fù)定義函數(shù)。所以在使用 composer 的時(shí)候最好要統(tǒng)一自動(dòng)加載和依賴機(jī)制,最好不要多重自動(dòng)加載。
運(yùn)行我們終于來(lái)到了核心的核心——composer 自動(dòng)加載的真相,命名空間如何通過(guò) composer 轉(zhuǎn)為對(duì)應(yīng)目錄文件的奧秘就在這一章。
前面說(shuō)過(guò),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():
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ù),原理跟上面一樣,防止有用戶寫(xiě) $this 或 self。還有就是如果命名空間是以 開(kāi)頭的,要去掉 然后再匹配。
findFileWithExtension:
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; } }
下面我們通過(guò)舉例來(lái)說(shuō)下上面代碼的流程:
如果我們?cè)诖a中寫(xiě)下 "phpDocumentorReflectionexample",PHP 會(huì)通過(guò) SPL 調(diào)用 loadClass->findFile->findFileWithExtension。首先默認(rèn)用 php 作為文件后綴名調(diào)用 findFileWithExtension 函數(shù)里,利用 PSR4 標(biāo)準(zhǔn)嘗試解析目錄文件,如果文件不存在則繼續(xù)用 PSR0 標(biāo)準(zhǔn)解析,如果解析出來(lái)的目錄文件仍然不存在,但是環(huán)境是 HHVM 虛擬機(jī),繼續(xù)用后綴名為 hh 再次調(diào)用 findFileWithExtension 函數(shù),如果不存在,說(shuō)明此命名空間無(wú)法加載,放到 classMap 中設(shè)為 false,以便以后更快地加載。
對(duì)于 phpDocumentorReflectionexample,當(dāng)嘗試?yán)?PSR4 標(biāo)準(zhǔn)映射目錄時(shí),步驟如下:
將 轉(zhuǎn)為文件分隔符 /,加上后綴 php 或 hh,得到 $logicalPathPsr4 即 phpDocumentor//Reflection//example.php(hh);
利用命名空間第一個(gè)字母 p 作為前綴索引搜索 prefixLengthsPsr4 數(shù)組,查到下面這個(gè)數(shù)組:
p" => array ( "phpDocumentorReflection" => 25, "phpDocumentorFake" => 19, )
遍歷這個(gè)數(shù)組,得到兩個(gè)頂層命名空間 phpDocumentorReflection 和 phpDocumentorFake
用這兩個(gè)頂層命名空間與 phpDocumentorReflectionexample_e 相比較,可以得到 phpDocumentorReflection 這個(gè)頂層命名空間
在 prefixLengthsPsr4 映射數(shù)組中得到 phpDocumentorReflection 長(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", ),
PSR0 標(biāo)準(zhǔn)加載遍歷這個(gè)映射數(shù)組,得到三個(gè)目錄映射;
查看 “目錄+文件分隔符 //+substr($logicalPathPsr4, $length)” 文件是否存在,存在即返回。這里就是 "__DIR__/../phpdocumentor/reflection-common/src + /+ substr(phpDocumentor/Reflection/example_e.php(hh),25)"
如果失敗,則利用 fallbackDirsPsr4 數(shù)組里面的目錄繼續(xù)判斷是否存在文件,具體方法是“目錄+文件分隔符//+$logicalPathPsr4”
如果 PSR4 標(biāo)準(zhǔn)加載失敗,則要進(jìn)行 PSR0 標(biāo)準(zhǔn)加載:
找到 phpDocumentorReflectionexample_e 最后“”的位置,將其后面文件名中’‘_’‘字符轉(zhuǎn)為文件分隔符“/”,得到 logicalPathPsr0 即 phpDocumentor/Reflection/example/e.php(hh)
利用命名空間第一個(gè)字母 p 作為前綴索引搜索 prefixLengthsPsr4 數(shù)組,查到下面這個(gè)數(shù)組:
"P" => array ( "Prophecy" => array ( 0 => __DIR__ . "/.." . "/phpspec/prophecy/src", ), "phpDocumentor" => array ( 0 => __DIR__ . "/.." . "/erusev/parsedown", ), ),
結(jié)語(yǔ)遍歷這個(gè)數(shù)組,得到兩個(gè)頂層命名空間phpDocumentor和Prophecy
用這兩個(gè)頂層命名空間與 phpDocumentorReflectionexample_e 相比較,可以得到 phpDocumentor 這個(gè)頂層命名空間
在映射數(shù)組中得到 phpDocumentor 目錄映射為 "_DIR_ . "/.." . "/erusev/parsedown"
查看 “目錄+文件分隔符//+logicalPathPsr0”文件是否存在,存在即返回。這里就是
“_DIR_ . "/.." . "/erusev/parsedown + //+ phpDocumentor//Reflection//example/e.php(hh)”如果失敗,則利用 fallbackDirsPsr0 數(shù)組里面的目錄繼續(xù)判斷是否存在文件,具體方法是“目錄+文件分隔符//+logicalPathPsr0”
如果仍然找不到,則利用 stream_resolve_include_path(),在當(dāng)前 include 目錄尋找該文件,如果找到返回絕對(duì)路徑。
經(jīng)過(guò)三篇文章,終于寫(xiě)完了 PHP Composer 自動(dòng)加載的原理與實(shí)現(xiàn),結(jié)下來(lái)我們開(kāi)始講解 laravel 框架下的門面 Facade,這個(gè)門面功能和自動(dòng)加載有著一些聯(lián)系.
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/22927.html
摘要:任務(wù)是加載類的初始化頂級(jí)命名空間與文件路徑映射初始化和注冊(cè)。在實(shí)際情況下可能會(huì)出現(xiàn)這樣的情況。值得注意的是這個(gè)函數(shù)返回的是一個(gè)匿名函數(shù),為什么呢原因就是類中的等等都是的。。。關(guān)于匿名函數(shù)的綁定功能。 前言 在開(kāi)始之前,歡迎關(guān)注我自己的博客:www.leoyang90.cn 上一篇文章,我們討論了 PHP 的自動(dòng)加載原理、PHP 的命名空間、PHP 的 PSR0 與 PSR4 標(biāo)準(zhǔn),有...
摘要:源碼分析自動(dòng)加載系統(tǒng)會(huì)調(diào)用方法注冊(cè)自動(dòng)加載,在這一步完成后,所有符合規(guī)范的類庫(kù)包括依賴加載的第三方類庫(kù)都將自動(dòng)加載。是通過(guò)加載對(duì)應(yīng)的文件進(jìn)行注冊(cè)加載的。 源碼分析 自動(dòng)加載 系統(tǒng)會(huì)調(diào)用 Loader::register()方法注冊(cè)自動(dòng)加載,在這一步完成后,所有符合規(guī)范的類庫(kù)(包括Composer依賴加載的第三方類庫(kù))都將自動(dòng)加載。 系統(tǒng)的自動(dòng)加載由下面主要部分組成: 1. 注冊(cè)系統(tǒng)的自...
摘要:今天來(lái)寫(xiě)寫(xiě)這個(gè)框架的類加載機(jī)制版本原理在項(xiàng)目啟動(dòng)時(shí),通過(guò)注冊(cè)了要使用的類的自動(dòng)加載處理方法,在類第一次被使用的時(shí)候,類文件通過(guò)該方法被引入,然后類才得以使用源碼分析在的入口文件,我們找到我們隨著這個(gè)路徑我們找打了這個(gè)主要內(nèi)容如下其中是為了注 今天來(lái)寫(xiě)寫(xiě)Symfony2.8 這個(gè)框架的類加載機(jī)制 版本 Symfony 2.8 原理 在項(xiàng)目啟動(dòng)時(shí),Symfony 通過(guò)spl_autoloa...
摘要:如果遍歷后沒(méi)有找到,則加載失敗。在之后碰到了之后直接拿來(lái)用,提高系統(tǒng)自動(dòng)加載的性能。這里我們就講完了注冊(cè)自動(dòng)加載。使用自動(dòng)加載我們?cè)谥卸x了我們自動(dòng)加載函數(shù)式方法。 繼 生命周期的第二篇,大家盡可放心,不會(huì)隨便鴿文章的 第一篇中,我們提到了入口腳本,也說(shuō)了,里面注冊(cè)了自動(dòng)加載的功能 本文默認(rèn)你有自動(dòng)加載和命名空間的基礎(chǔ)。如果沒(méi)有請(qǐng) 看此篇文章 php 類的自動(dòng)加載與命名空間 自動(dòng)加載...
摘要:索性讀一下它的源碼。行載入類載入類,這個(gè)類比較重要,實(shí)現(xiàn)了自動(dòng)加載。注冊(cè)錯(cuò)誤和異常處理機(jī)制加載慣例配置文件接下來(lái)我們看一下自動(dòng)加載的實(shí)現(xiàn)方法。所以借助此函數(shù)可以達(dá)到自動(dòng)加載。博客鏈接解讀源碼一自動(dòng)加載 聽(tīng)說(shuō) TP5 已經(jīng) RC4 了,曾經(jīng)在 RC3 的時(shí)候用它寫(xiě)過(guò)一個(gè)小東西。官方說(shuō)從 RC4 以后改動(dòng)不是太大。索性讀一下它的源碼。然后順便記錄一下,如有錯(cuò)漏,請(qǐng)路過(guò)大神多多指正! 入口 ...
閱讀 1990·2021-11-25 09:43
閱讀 1477·2021-11-22 14:56
閱讀 3337·2021-11-22 09:34
閱讀 2103·2021-11-15 11:37
閱讀 2372·2021-09-01 10:46
閱讀 1464·2019-08-30 15:44
閱讀 2352·2019-08-30 13:15
閱讀 2447·2019-08-29 13:07