Composer?是PHP的一個包依賴管理工具,類似Ruby中的RubyGems或者Node中的NPM,它并非官方,但現(xiàn)在已經(jīng)非常流行。此文并不介紹如何使用Composer,而是關(guān)注于它的autoload的內(nèi)容吧。
舉例來說,假設(shè)我們的項(xiàng)目想要使用?monolog?這個日志工具,就需要在composer.json里告訴composer我們需要它:
{ ??"require":?{ ????"monolog/monolog":?"1.*" ??} }
之后執(zhí)行:
php composer.phar install
好,現(xiàn)在安裝完了,該怎么使用呢?Composer自動生成了一個autoload文件,你只需要引用它
require '/path/to/vendor/autoload.php';
然后就可以非常方便的去使用第三方的類庫了,是不是感覺很棒??!對于我們需要的monolog,就可以這樣用了:
use?MonologLogger; use?MonologHandlerStreamHandler; //?create?a?log?channel $log?=?new?Logger('name'); $log->pushHandler(new?StreamHandler('/path/to/log/log_name.log',?Logger::WARNING)); //?add?records?to?the?log $log->addWarning('Foo'); $log->addError('Bar');
在這個過程中,Composer做了什么呢?它生成了一個autoloader,再根據(jù)各個包自己的autoload配置,從而幫我們進(jìn)行自動加載的工作。(如果對autoload這部分內(nèi)容不太了解,可以看我之前的?一篇文章
)接下來讓我們看看Composer是怎么做的吧。
對于第三方包的自動加載,Composer提供了四種方式的支持,分別是 PSR-0和PSR-4的自動加載(我的一篇文章也有介紹過它們),生成class-map,和直接包含files的方式。
PSR-4是composer推薦使用的一種方式,因?yàn)樗资褂貌⒛軒砀啙嵉哪夸浗Y(jié)構(gòu)。在composer.json里是這樣進(jìn)行配置的:
{ ????"autoload":?{ ????????"psr-4":?{ ????????????"Foo\":?"src/", ????????} ????} }
key和value就定義出了namespace以及到相應(yīng)path的映射。按照PSR-4的規(guī)則,當(dāng)試圖自動加載?"Foo\Bar\Baz"?這個class時,會去尋找?"src/Bar/Baz.php"?這個文件,如果它存在則進(jìn)行加載。注意,?"Foo\"
并沒有出現(xiàn)在文件路徑中,這是與PSR-0不同的一點(diǎn),如果PSR-0有此配置,那么會去尋找
"src/Foo/Bar/Baz.php"
這個文件。
另外注意PSR-4和PSR-0的配置里,"Foo\"結(jié)尾的命名空間分隔符必須加上并且進(jìn)行轉(zhuǎn)義,以防出現(xiàn)"Foo"匹配到了"FooBar"這樣的意外發(fā)生。
在composer安裝或更新完之后,psr-4的配置換被轉(zhuǎn)換成namespace為key,dir path為value的Map的形式,并寫入生成的?vendor/composer/autoload_psr4.php?文件之中。
{ ????"autoload":?{ ????????"psr-0":?{ ????????????"Foo\":?"src/", ????????} ????} }
最終這個配置也以Map的形式寫入生成的
vendor/composer/autoload_namespaces.php
文件之中。
Class-map方式,則是通過配置指定的目錄或文件,然后在Composer安裝或更新時,它會掃描指定目錄下以.php或.inc結(jié)尾的文件中的class,生成class到指定file path的映射,并加入新生成的?vendor/composer/autoload_classmap.php?文件中,。
{ ????"autoload":?{ ????????"classmap":?["src/",?"lib/",?"Something.php"] ????} }
例如src/下有一個BaseController類,那么在autoload_classmap.php文件中,就會生成這樣的配置:
'BaseController' => $baseDir . '/src/BaseController.php'
Files方式,就是手動指定供直接加載的文件。比如說我們有一系列全局的helper functions,可以放到一個helper文件里然后直接進(jìn)行加載
{ ????"autoload":?{ ????????"files":?["src/MyLibrary/functions.php"] ????} }
它會生成一個array,包含這些配置中指定的files,再寫入新生成的
vendor/composer/autoload_files.php
文件中,以供autoloader直接進(jìn)行加載。
下面來看看composer autoload的代碼吧
?$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); ??$includeFiles?=?require?__DIR__?.?'/autoload_files.php'; ??foreach?($includeFiles?as?$file)?{ ??????composerRequire73612b48e6c3d0de8d56e03dece61d11($file); ??} ??return?$loader; ????} } function?composerRequire73612b48e6c3d0de8d56e03dece61d11($file) { ????require?$file; }
首先初始化ClassLoader類,然后依次用上面提到的4種加載方式來注冊/直接加載,ClassLoader的一些核心代碼如下:
/** ???*?@param?array?$classMap?Class?to?filename?map ???*/ ??public?function?addClassMap(array?$classMap) ??{ ????if?($this->classMap)?{ ??????$this->classMap?=?array_merge($this->classMap,?$classMap); ????}?else?{ ??????$this->classMap?=?$classMap; ????} ??} ??/** ???*?Registers?a?set?of?PSR-0?directories?for?a?given?prefix, ???*?replacing?any?others?previously?set?for?this?prefix. ???* ???*?@param?string ???$prefix?The?prefix ???*?@param?array|string?$paths??The?PSR-0?base?directories ???*/ ??public?function?set($prefix,?$paths) ??{ ????if?(!$prefix)?{ ??????$this->fallbackDirsPsr0?=?(array)?$paths; ????}?else?{ ??????$this->prefixesPsr0[$prefix[0]][$prefix]?=?(array)?$paths; ????} ??} ??/** ???*?Registers?a?set?of?PSR-4?directories?for?a?given?namespace, ???*?replacing?any?others?previously?set?for?this?namespace. ???* ???*?@param?string ???$prefix?The?prefix/namespace,?with?trailing?'\' ???*?@param?array|string?$paths??The?PSR-4?base?directories ???* ???*?@throws?InvalidArgumentException ???*/ ??public?function?setPsr4($prefix,?$paths) ??{ ????if?(!$prefix)?{ ??????$this->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; ????} ??} ??/** ???*?Registers?this?instance?as?an?autoloader. ???* ???*?@param?bool?$prepend?Whether?to?prepend?the?autoloader?or?not ???*/ ??public?function?register($prepend?=?false) ??{ ????spl_autoload_register(array($this,?'loadClass'),?true,?$prepend); ??} ??/** ???*?Loads?the?given?class?or?interface. ???* ???*?@param??string $class?The?name?of?the?class ???*?@return?bool|null?True?if?loaded,?null?otherwise ???*/ ??public?function?loadClass($class) ??{ ????if?($file?=?$this->findFile($class))?{ ??????includeFile($file); ??????return?true; ????} ??} ??/** ???*?Finds?the?path?to?the?file?where?the?class?is?defined. ???* ???*?@param?string?$class?The?name?of?the?class ???* ???*?@return?string|false?The?path?if?found,?false?otherwise ???*/ ??public?function?findFile($class) ??{ ????//這是PHP5.3.0?-?5.3.2的一個bug??詳見https://bugs.php.net/50731 ????if?('\'?==?$class[0])?{ ??????$class?=?substr($class,?1); ????} ????//?class?map?方式的查找 ????if?(isset($this->classMap[$class]))?{ ??????return?$this->classMap[$class]; ????} ????//psr-0/4方式的查找 ????$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; ??} ??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; ????} ??} /** ?*?Scope?isolated?include. ?* ?*?Prevents?access?to?$this/self?from?included?files. ?*/ function?includeFile($file) { ??include?$file; }
如此最終實(shí)現(xiàn)的原理是在vendor目錄下的
return?ComposerAutoloaderInit5cbf6bb00ad8ca1716a58cda814c22a3::getLoader();
在getLoader中為ComposerAutoloadClassLoader();類填充了信息包括psr-0 psr-4等自動加載機(jī)制所需的消息然后調(diào)用
ComposerAutoloadClassLoader()的register函數(shù),進(jìn)行sql_autoload_register的調(diào)用,這樣之后每當(dāng)進(jìn)行類加載的時候 依次在?psr4前綴中進(jìn)行查找文件目錄,在psr4?fallback目錄中查找,在psr0前綴中查找目錄,在psr0?fallback中查找還有就是在psr0?include?path中進(jìn)行查找文件