Heim > Artikel > Entwicklungswerkzeuge > Vertiefte Kenntnisse des automatischen Ladens von Composer
下面由composer教程栏目给大家深入 Composer autoload,希望对需要的朋友有所帮助!
这几天看到 phphub 上面有人开始进坑怒看 laravel 源代码,于是我也凑个热闹来看下这个故事。
众所周知 composer
是现代 PHP 项目的基石, 与古老的 pear
不同, composer
并不是一款专注于系统级别 php 管理的包管理系统,而是基于项目的一个库管理系统。这就好比 npm install -g
和 npm install
的区别。而且最主要的是 pear
不太能跟上时代的潮流,在大家都在用 psr-*
的时候 pear
依然我行我素自成一体。
好吧,可能这是好事,但是也是坏事。好事是很多优秀的包都从 pear
发家致富,比如 PHP_CodeSniffer
, PHP_Unit
等等。但是随着时代的发展,php社区也渐渐地从其他社区汲取到了一些精华,慢慢地向前发展。最近的 laravel
就是直接扔进了 composer
。因为 psr-4
这个规范真是不能再爽更多。这真的是我用各种包用得最顺手的一套命名规范了。
扯远了,扯回 vendor/composer/autoload_real.php
这个核心 composer
文件。
总体来说 composer 提供了几种自动加载类型
这几种自动加载都会用到,理论上来说,项目代码用 psr-4
自动加载, helper
用 files
自动加载,development
相关用 classmap
自动加载。 psr-0
已经被抛弃了,不过有些历史遗留依然在用,所以偶尔也会看到。
这应该是最最简单的 autoload 模式了。大概的意思就是这样的:
{ "classmap": ["src/"] }
然后 composer 在背后就会读取这个文件夹中所有的文件 然后再 vendor/composer/autoload_classmap.php
中怒将所有的 class 的 namespace + classname 生成成一个 key => value 的 php 数组
<?php return [ 'App\\Console\\Kernel' => $baseDir . '/app/Console/Kernel.php' ]; ?>
然后就可以光明正大地用 spl_autoload_register
这个函数来怒做自动加载了。
好吧 上面的例子其实有点 tricky 就是上面这个 autoload 实际上是根据 prs-4 来生成出来的。不过这不重要,了解底层重要点,我们可以看到所有的所谓的 autoloading 其实可以理解为生成了这么一个 classmap
,这是 composer dump-autoload -o
做的事儿。不然的话compoesr
会吭哧吭哧地去动态读取 psr-4 和 prs-0 的内容。
现在这个标准已经过时了。当初制定这个标准的时候主要是在 php 从 5.2 刚刚跃迁到 5.3+ 有了命名空间的概念。所以这个时候 psr-0
的标准主要考虑到了 <5.2 的 php 中 类似 Acme_Util_ClassName
这样的写法。
{ "name": "acme/util", "auto" : { "psr-0": { "Acme\\Util\\": "src/" } } }
文档结构是这样的
vendor/ acme/ util/ composer.json src/ Acme/ Util/ ClassName.php
ClassName.php 中是这样的
<?php class Acme_Util_ClassName{} ?></p> <p>我们可以看到由于旧版本的 php 没有 namespace 所以必须通过 <code>_</code> 将类区分开。</p> <p>这样稍微有点麻烦。指向一个文件夹之后 <code>src</code> 还要在 <code>src</code> 中分成好几层文档树。这样太深了。没有办法,但是似乎想要兼容 <code>_</code> 的写法仔细想想这是唯一的办法了。(psr-0 要求 autoloading 的时候将 类中的 <code>_</code> 转义为 '\')</p> <p>所以在 php5.2 版本已经彻底被抛弃的今天, <code>FIG</code> 就怒推出了 <code>psr-4</code></p> <h3>psr-4</h3> <p>这个标准出来的时候一片喷声,大概的意思就是 <code>FIG</code> 都是傻逼么,刚刚出了 <code>psr-0</code> 然后紧跟着进推翻了自己。不过 FIG 也有自己的苦衷,帮没有 namespace 支持的 php5.2 擦了那么久的屁股,在2014年10月21日的早晨,终于丢失了睡眠。</p> <p>抛弃了 psr-0 的 composer 从此变得非常清爽。</p> <p>最简单来讲就是可以把 prs-4 的 namespace 直接想想成 file structure</p> <pre class="brush:php;toolbar:false">{ "name": "acme/util", "auto" : { "psr-4": { "Acme\\Util\\": "src/" } } }
vendor/ acme/ util/ composer.json src/ ClassName.php
可以看到将 Acme\Util
指向了 src
之后 psr-4 就会默认所有的 src
下面的 class 都已经有了 Acme\Util
的 基本 namespace,而 psr-4 中不会将 _
转义成 \
所以就没有必要有 psr-0 那么深得文档结构了。
<?php namespace Acme\Util; class ClassName {} ?>
然而这还是不够。因为可能会有一些全局的 helper function 的存在。
这个写法很简单就不多看了。
{ "files": [ "path/to/file.php" ] }
好了看了所有的 autoload 类型那么直接怒看一发实现。
首先映入眼帘的就是一坨,我的是这样的
ComposerAutoloaderInit64c47026c93126586e44d036738c0862
为啥?
因为这个类是全局的啊少年。
作为模块化大行其道的今天,全局的类总是有那么点奇怪。为了不让这个 autoload 的 helper 污染全局,composer 的仁兄们还是绞尽脑汁怒弄了这么一个奇怪的 hash。这直接就逼迫广大二笔程序员们不要跟这个撞衫。
好吧,接着往下看。
主要只有这么一个方法 getLoader
<?php // autoload_real.php @generated by Composer class ComposerAutoloaderInit64c47026c93126586e44d036738c0862 { private static $loader; public static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } } public static function getLoader() { if (null !== self::$loader) { return self::$loader; } spl_autoload_register(array('ComposerAutoloaderInit64c47026c93126586e44d036738c0862', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInit64c47026c93126586e44d036738c0862', 'loadClassLoader')); $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); } $loader->register(true); $includeFiles = require __DIR__ . '/autoload_files.php'; foreach ($includeFiles as $file) { composerRequire64c47026c93126586e44d036738c0862($file); } return $loader; } } function composerRequire64c47026c93126586e44d036738c0862($file) { require $file; } ?>
在讲什么?其实很简单。
ComposerAutoloaderInit64c47026c93126586e44d036738c0862
中autoload_psr4
, autoload_classmap
, autoload_namespaces
(psr-0) 全都注册到 Composer\ClassLoader 中。autoload_files
中的文件其中 composerRequire64c47026c93126586e44d036738c0862
要解释下。 为什么这个不直接用 require 而是定义在了类的外边?
调查 Composer\ClassLoader 发现了这么一个注释
Scope isolated include. Prevents access to $this/self from included files.
好吧还是怕二笔程序员犯浑。想想一下,如果有人在 autoload_files 中的文件中写了 $this
或者 self
那就屎了。所以当require 一个file的时候我们希望解释器能够成功报错。
不容易,终于快要胜利了。
composer dump-autoload
?刚开始接触用 composer 的时候一直被这个问题蛊惑。很不理解为什么总是要打这句命令才能不报错,现在终于知道根结了。
因为
database
文件夹使用 classmap 来做加载的。所以只有在打了 composer dumpautoload 之后 composer 才会更新 autoload_classmap 的内容。
composer dump-autoload
?可以怒用 psr-4 注册一个文件夹这样增减文件就不用再管了。Composer\ClassLoader
会自动检查 composer.json
中注册的 psr-4 入口然后根据 psr-4
去自动查找文件。
composer dump-atoload -o
?因为 psr-4 自动加载会再背后跑一些逻辑。具体可以调查 Composer\ClassLoader
去看。
<?php 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 (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (is_file($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 (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } } ?>
可以看到 psr-4 或者 psr-0 的自动加载都是一件很累人的事儿。基本是个 O(n2)
的复杂度。另外有一大堆 is_file
之类的 IO 操作所以性能堪忧。
所以给出的解决方案就是空间换时间。
Compsoer\ClassLoader
会优先查看 autoload_classmap
中所有生成的注册类。如果在classmap
中没有发现再 fallback 到 psr-4 然后 psr-0
所以当打了 composer dump-autoload -o
之后,composer 就会提前加载需要的类并提前返回。这样大大减少了 IO 和深层次的 loop。
Das obige ist der detaillierte Inhalt vonVertiefte Kenntnisse des automatischen Ladens von Composer. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!