Composer?是PHP的一個包依賴管理工具,類似Ruby中的RubyGems或者Node中的NPM,它并非官方,但現(xiàn)在已經(jīng)非常流行。此文并不介紹如何使用Composer,而是關(guān)注于它的autoload的內(nèi)容吧。
舉例來說,假設(shè)我們的項目想要使用?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配置,從而幫我們進行自動加載的工作。(如果對autoload這部分內(nèi)容不太了解,可以看我之前的?一篇文章
)接下來讓我們看看Composer是怎么做的吧。
對于第三方包的自動加載,Composer提供了四種方式的支持,分別是 PSR-0和PSR-4的自動加載(我的一篇文章也有介紹過它們),生成class-map,和直接包含files的方式。
PSR-4是composer推薦使用的一種方式,因為它更易使用并能帶來更簡潔的目錄結(jié)構(gòu)。在composer.json里是這樣進行配置的:
{
????"autoload":?{
????????"psr-4":?{
????????????"Foo\":?"src/",
????????}
????}
}key和value就定義出了namespace以及到相應(yīng)path的映射。按照PSR-4的規(guī)則,當試圖自動加載?"Foo\Bar\Baz"?這個class時,會去尋找?"src/Bar/Baz.php"?這個文件,如果它存在則進行加載。注意,?"Foo\"
并沒有出現(xiàn)在文件路徑中,這是與PSR-0不同的一點,如果PSR-0有此配置,那么會去尋找
"src/Foo/Bar/Baz.php"
這個文件。
另外注意PSR-4和PSR-0的配置里,"Foo\"結(jié)尾的命名空間分隔符必須加上并且進行轉(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文件里然后直接進行加載
{
????"autoload":?{
????????"files":?["src/MyLibrary/functions.php"]
????}
}它會生成一個array,包含這些配置中指定的files,再寫入新生成的
vendor/composer/autoload_files.php
文件中,以供autoloader直接進行加載。
下面來看看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;
}如此最終實現(xiàn)的原理是在vendor目錄下的
return?ComposerAutoloaderInit5cbf6bb00ad8ca1716a58cda814c22a3::getLoader();
在getLoader中為ComposerAutoloadClassLoader();類填充了信息包括psr-0 psr-4等自動加載機制所需的消息然后調(diào)用
ComposerAutoloadClassLoader()的register函數(shù),進行sql_autoload_register的調(diào)用,這樣之后每當進行類加載的時候 依次在?psr4前綴中進行查找文件目錄,在psr4?fallback目錄中查找,在psr0前綴中查找目錄,在psr0?fallback中查找還有就是在psr0?include?path中進行查找文件





