PHP管理依赖(dependency)关系工具 Composer的自动加载(autoload)
举例来说,假设我们的项目想要使用monolog这个日志工具,就需要在composer.json里告诉composer我们需要它:
{ "require":{ "monolog/monolog":"1.*" } }
之后执行:
phpcomposer.pharinstall
好,现在安装完了,该怎么使用呢?Composer自动生成了一个autoload文件,你只需要引用它
require'/path/to/vendor/autoload.php';
然后就可以非常方便的去使用第三方的类库了,是不是感觉很棒啊!对于我们需要的monolog,就可以这样用了:
useMonolog\Logger; useMonolog\Handler\StreamHandler; //createalogchannel $log=newLogger('name'); $log->pushHandler(newStreamHandler('/path/to/log/log_name.log',Logger::WARNING)); //addrecordstothelog $log->addWarning('Foo'); $log->addError('Bar');
在这个过程中,Composer做了什么呢?它生成了一个autoloader,再根据各个包自己的autoload配置,从而帮我们进行自动加载的工作。(如果对autoload这部分内容不太了解,可以看我之前的一篇文章
)接下来让我们看看Composer是怎么做的吧。
对于第三方包的自动加载,Composer提供了四种方式的支持,分别是PSR-0和PSR-4的自动加载(我的一篇文章也有介绍过它们),生成class-map,和直接包含files的方式。
PSR-4是composer推荐使用的一种方式,因为它更易使用并能带来更简洁的目录结构。在composer.json里是这样进行配置的:
{ "autoload":{ "psr-4":{ "Foo\\":"src/", } } }
key和value就定义出了namespace以及到相应path的映射。按照PSR-4的规则,当试图自动加载"Foo\\Bar\\Baz"这个class时,会去寻找"src/Bar/Baz.php"这个文件,如果它存在则进行加载。注意,"Foo\\"
并没有出现在文件路径中,这是与PSR-0不同的一点,如果PSR-0有此配置,那么会去寻找
"src/Foo/Bar/Baz.php"
这个文件。
另外注意PSR-4和PSR-0的配置里,"Foo\\"结尾的命名空间分隔符必须加上并且进行转义,以防出现"Foo"匹配到了"FooBar"这样的意外发生。
在composer安装或更新完之后,psr-4的配置换被转换成namespace为key,dirpath为value的Map的形式,并写入生成的vendor/composer/autoload_psr4.php文件之中。
{ "autoload":{ "psr-0":{ "Foo\\":"src/", } } }
最终这个配置也以Map的形式写入生成的
vendor/composer/autoload_namespaces.php
文件之中。
Class-map方式,则是通过配置指定的目录或文件,然后在Composer安装或更新时,它会扫描指定目录下以.php或.inc结尾的文件中的class,生成class到指定filepath的映射,并加入新生成的vendor/composer/autoload_classmap.php文件中,。
{ "autoload":{ "classmap":["src/","lib/","Something.php"] } }
例如src/下有一个BaseController类,那么在autoload_classmap.php文件中,就会生成这样的配置:
'BaseController'=>$baseDir.'/src/BaseController.php'
Files方式,就是手动指定供直接加载的文件。比如说我们有一系列全局的helperfunctions,可以放到一个helper文件里然后直接进行加载
{ "autoload":{ "files":["src/MyLibrary/functions.php"] } }
它会生成一个array,包含这些配置中指定的files,再写入新生成的
vendor/composer/autoload_files.php
文件中,以供autoloader直接进行加载。
下面来看看composerautoload的代码吧
<?php //autoload_real.php@generatedbyComposer classComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11 { privatestatic$loader; publicstaticfunctionloadClassLoader($class) { if('Composer\Autoload\ClassLoader'===$class){ require__DIR__.'/ClassLoader.php'; } } publicstaticfunctiongetLoader() { if(null!==self::$loader){ returnself::$loader; } spl_autoload_register(array('ComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11','loadClassLoader'),true,true); self::$loader=$loader=new\Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11','loadClassLoader')); $vendorDir=dirname(__DIR__);//verdor第三方类库提供者目录 $baseDir=dirname($vendorDir);//整个应用的目录 $includePaths=require__DIR__.'/include_paths.php'; array_push($includePaths,get_include_path()); set_include_path(join(PATH_SEPARATOR,$includePaths)); $map=require__DIR__.'/autoload_namespaces.php'; foreach($mapas$namespace=>$path){ $loader->set($namespace,$path); } $map=require__DIR__.'/autoload_psr4.php'; foreach($mapas$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($includeFilesas$file){ composerRequire73612b48e6c3d0de8d56e03dece61d11($file); } return$loader; } } functioncomposerRequire73612b48e6c3d0de8d56e03dece61d11($file) { require$file; }
首先初始化ClassLoader类,然后依次用上面提到的4种加载方式来注册/直接加载,ClassLoader的一些核心代码如下:
/** *@paramarray$classMapClasstofilenamemap */ publicfunctionaddClassMap(array$classMap) { if($this->classMap){ $this->classMap=array_merge($this->classMap,$classMap); }else{ $this->classMap=$classMap; } } /** *RegistersasetofPSR-0directoriesforagivenprefix, *replacinganyotherspreviouslysetforthisprefix. * *@paramstring$prefixTheprefix *@paramarray|string$pathsThePSR-0basedirectories */ publicfunctionset($prefix,$paths) { if(!$prefix){ $this->fallbackDirsPsr0=(array)$paths; }else{ $this->prefixesPsr0[$prefix[0]][$prefix]=(array)$paths; } } /** *RegistersasetofPSR-4directoriesforagivennamespace, *replacinganyotherspreviouslysetforthisnamespace. * *@paramstring$prefixTheprefix/namespace,withtrailing'\\' *@paramarray|string$pathsThePSR-4basedirectories * *@throws\InvalidArgumentException */ publicfunctionsetPsr4($prefix,$paths) { if(!$prefix){ $this->fallbackDirsPsr4=(array)$paths; }else{ $length=strlen($prefix); if('\\'!==$prefix[$length-1]){ thrownew\InvalidArgumentException("Anon-emptyPSR-4prefixmustendwithanamespaceseparator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix]=$length; $this->prefixDirsPsr4[$prefix]=(array)$paths; } } /** *Registersthisinstanceasanautoloader. * *@parambool$prependWhethertoprependtheautoloaderornot */ publicfunctionregister($prepend=false) { spl_autoload_register(array($this,'loadClass'),true,$prepend); } /** *Loadsthegivenclassorinterface. * *@paramstring$classThenameoftheclass *@returnbool|nullTrueifloaded,nullotherwise */ publicfunctionloadClass($class) { if($file=$this->findFile($class)){ includeFile($file); returntrue; } } /** *Findsthepathtothefilewheretheclassisdefined. * *@paramstring$classThenameoftheclass * *@returnstring|falseThepathiffound,falseotherwise */ publicfunctionfindFile($class) { //这是PHP5.3.0-5.3.2的一个bug详见https://bugs.php.net/50731 if('\\'==$class[0]){ $class=substr($class,1); } //classmap方式的查找 if(isset($this->classMap[$class])){ return$this->classMap[$class]; } //psr-0/4方式的查找 $file=$this->findFileWithExtension($class,'.php'); //SearchforHackfilesifwearerunningonHHVM if($file===null&&defined('HHVM_VERSION')){ $file=$this->findFileWithExtension($class,'.hh'); } if($file===null){ //Rememberthatthisclassdoesnotexist. return$this->classMap[$class]=false; } return$file; } privatefunctionfindFileWithExtension($class,$ext) { //PSR-4lookup $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-4fallbackdirs foreach($this->fallbackDirsPsr4as$dir){ if(file_exists($file=$dir.DIRECTORY_SEPARATOR.$logicalPathPsr4)){ return$file; } } //PSR-0lookup if(false!==$pos=strrpos($class,'\\')){ //namespacedclassname $logicalPathPsr0=substr($logicalPathPsr4,0,$pos+1) .strtr(substr($logicalPathPsr4,$pos+1),'_',DIRECTORY_SEPARATOR); }else{ //PEAR-likeclassname $logicalPathPsr0=strtr($class,'_',DIRECTORY_SEPARATOR).$ext; } if(isset($this->prefixesPsr0[$first])){ foreach($this->prefixesPsr0[$first]as$prefix=>$dirs){ if(0===strpos($class,$prefix)){ foreach($dirsas$dir){ if(file_exists($file=$dir.DIRECTORY_SEPARATOR.$logicalPathPsr0)){ return$file; } } } } } //PSR-0fallbackdirs foreach($this->fallbackDirsPsr0as$dir){ if(file_exists($file=$dir.DIRECTORY_SEPARATOR.$logicalPathPsr0)){ return$file; } } //PSR-0includepaths. if($this->useIncludePath&&$file=stream_resolve_include_path($logicalPathPsr0)){ return$file; } } /** *Scopeisolatedinclude. * *Preventsaccessto$this/selffromincludedfiles. */ functionincludeFile($file) { include$file; }