深入解析 composer 的自动加载原理
前言
php 自5.3的版本之后,已经重焕新生,命名空间、性状(trait)、闭包、接口、PSR 规范、以及 composer 的出现已经让 PHP 变成了一门现代化的脚本语言。PHP 的生态系统也一直在演进,而 composer 的出现更是彻底的改变了以往构建 PHP 应用的方式,我们可以根据 PHP 的应用需求混合搭配最合适的 PHP 组件。当然这也得益于 PSR 规范的提出。
大纲
PHP 自动加载功能
PSR 规范
comoposer 的自动加载过程
php自动加载功能
PHP自动加载功能的由来
在 PHP 开发过程中,如果希望从外部引入一个 Class ,通常会使用 include 和 require 方法,去把定义这个 Class 的文件包含进来。这个在小规模开发的时候,没什么大问题。但在大型的开发项目中,使用这种方式会带来一些隐含的问题:如果一个 PHP 文件需要使用很多其它类,那么就需要很多的 require/include 语句,这样有可能会 造成遗漏 或者 包含进不必要的类文件。如果大量的文件都需要使用其它的类,那么要保证每个文件都包含正确的类文件肯定是一个噩梦, 况且 require或 incloud 的性能代价很大。
PHP5 为这个问题提供了一个解决方案,这就是 类的自动加载(autoload)机制。autoload机制 可以使得 PHP 程序有可能在使用类时才自动包含类文件,而不是一开始就将所有的类文件include进来,这种机制也称为 Lazy loading (惰性加载)。
总结起来,自动加载功能带来了几处优点:
使用类之前无需 include / require
使用类的时候才会 include / require 文件,实现了 lazy loading ,避免了 include / require 多余文件。
无需考虑引入 类的实际磁盘地址 ,实现了逻辑和实体文件的分离。
PHP自动加载函数__autoload()
从 PHP5 开始,当我们在使用一个类时,如果发现这个类没有加载,就会自动运行 __autoload() 函数,这个函数是我们在程序中自定义的,在这个函数中我们可以加载需要使用的类。下面是个简单的示例:
<?phpfunction __autoload($classname) { require_once ($classname . ".class.php"); }
在我们这个简单的例子中,我们直接将类名加上扩展名 .class.php 构成了类文件名,然后使用 require_once 将其加载。
从这个例子中,我们可以看出 __autoload 至少要做三件事情:
根据类名确定类文件名;
确定类文件所在的磁盘路径;
将类从磁盘文件中加载到系统中。
第三步最简单,只需要使用 include / require 即可。要实现第一步,第二步的功能,必须在开发时约定类名与磁盘文件的映射方法,只有这样我们才能根据类名找到它对应的磁盘文件。
当有大量的类文件要包含的时候,我们只要确定相应的规则,然后在 __autoload() 函数中,将类名与实际的磁盘文件对应起来,就可以实现 lazy loading 的效果 。
__autoload()函数存在的问题
如果在一个系统的实现中,如果需要使用很多其它的类库,这些类库可能是由不同的开发人员编写的, 其类名与实际的磁盘文件的映射规则不尽相同。这时如果要实现类库文件的自动加载,就必须 在 __autoload() 函数中将所有的映射规则全部实现,这样的话 __autoload() 函数有可能会非常复杂,甚至无法实现。最后可能会导致 __autoload() 函数十分臃肿,这时即便能够实现,也会给将来的维护和系统效率带来很大的负面影响。
那么问题出现在哪里呢?问题出现在 __autoload() 是全局函数只能定义一次 ,不够灵活,所以所有的类名与文件名对应的逻辑规则都要在一个函数里面实现,造成这个函数的臃肿。那么如何来解决这个问题呢?答案就是使用一个 __autoload调用堆栈 ,不同的映射关系写到不同的 __autoload函数 中去,然后统一注册统一管理,这个就是 PHP5 引入的 SPL Autoload 。
SPL AUTOLOAD
SPL是 Standard PHP Library(标准PHP库)的缩写。它是 PHP5 引入的一个扩展标准库,包括 spl autoload 相关的函数以及各种数据结构和迭代器的接口或类。
<?php // __autoload 函数 // // function __autoload($class) { // include 'classes/' . $class . '.class.php'; // } function my_autoloader($class) { include 'classes/' . $class . '.class.php'; } spl_autoload_register('my_autoloader'); // 定义的 autoload 函数在 class 里 // 静态方法 class MyClass { public static function autoload($className) { // ... } } spl_autoload_register(array('MyClass', 'autoload')); // 非静态方法 class MyClass { public function autoload($className) { // ... } } $instance = new MyClass(); spl_autoload_register(array($instance, 'autoload'));
spl_autoload_register() 就是我们上面所说的__autoload调用堆栈,我们可以向这个函数注册多个我们自己的 autoload() 函数,当 PHP 找不到类名时,PHP就会调用这个堆栈,然后去调用自定义的 autoload() 函数,实现自动加载功能。如果我们不向这个函数输入任何参数,那么就会默认注册 spl_autoload() 函数。
PSR规范
与自动加载相关的规范是 PSR4,在说 PSR4 之前先介绍一下 PSR 标准。PSR 标准的发明和推出组织是:PHP-FIG,它的网站是:www.php-fig.org。由几位开源框架的开发者成立于 2009 年,从那开始也选取了很多其他成员进来,虽然不是 “官方” 组织,但也代表了社区中不小的一块。组织的目的在于:以最低程度的限制,来统一各个项目的编码规范,避免各家自行发展的风格阻碍了程序员开发的困扰,于是大伙发明和总结了 PSR,PSR 是 PHP Standards Recommendation 的缩写,截止到目前为止,总共有 14 套 PSR 规范,其中有 7 套PSR规范已通过表决并推出使用,分别是:
PSR-0 自动加载标准(已废弃,一些旧的第三方库还有在使用)
PSR-1 基础编码标准
PSR-2 编码风格向导
PSR-3 日志接口
PSR-4 自动加载的增强版,替换掉了 PSR-0
PSR-6 缓存接口规范
PSR-7 http 消息接口规范
PSR4标准
2013 年底,PHP-FIG 推出了第 5 个规范——PSR-4。
PSR-4 规范了如何指定文件路径从而自动加载类定义,同时规范了自动加载文件的位置。
1)一个完整的类名需具有以下结构:
\<命名空间>\<子命名空间>\<类名>
完整的类名必须要有一个顶级命名空间,被称为 "vendor namespace";
完整的类名可以有一个或多个子命名空间;
完整的类名必须有一个最终的类名;
完整的类名中任意一部分中的下滑线都是没有特殊含义的;
完整的类名可以由任意大小写字母组成;
所有类名都必须是大小写敏感的。
2)根据完整的类名载入相应的文件
完整的类名中,去掉最前面的命名空间分隔符,前面连续的一个或多个命名空间和子命名空间,作为「命名空间前缀」,其必须与至少一个「文件基目录」相对应;
紧接命名空间前缀后的子命名空间 必须 与相应的「文件基目录」相匹配,其中的命名空间分隔符将作为目录分隔符。
末尾的类名必须与对应的以 .php 为后缀的文件同名。
自动加载器(autoloader)的实现一定不可抛出异常、一定不可触发任一级别的错误信息以及不应该有返回值。
3) 例子
PSR-4风格
类名:ZendAbc
命名空间前缀:Zend
文件基目录:/usr/includes/Zend/
文件路径:/usr/includes/Zend/Abc.php类名:SymfonyCoreRequest
命名空间前缀:SymfonyCore
文件基目录:./vendor/Symfony/Core/
文件路径:./vendor/Symfony/Core/Request.php
目录结构
-vendor/ | -vendor_name/ | | -package_name/ | | | -src/ | | | | -ClassName.php # Vendor_Name\Package_Name\ClassName | | | -tests/ | | | | -ClassNameTest.php # Vendor_Name\Package_Name\ClassNameTest
Composer自动加载过程
Composer 做了哪些事情
你有一个项目依赖于若干个库。
其中一些库依赖于其他库。
你声明你所依赖的东西。
Composer 会找出哪个版本的包需要安装,并安装它们(将它们下载到你的项目中)。
例如,你正在创建一个项目,需要做一些单元测试。你决定使用 phpunit 。为了将它添加到你的项目中,你所需要做的就是在 composer.json 文件里描述项目的依赖关系。
{ "require": { "phpunit/phpunit":"~6.0", } }
然后在 composer require 之后我们只要在项目里面直接 use phpunit 的类即可使用。
执行 composer require 时发生了什么
composer 会找到符合 PR4 规范的第三方库的源
将其加载到 vendor 目录下
初始化顶级域名的映射并写入到指定的文件里
(如:'PHPUnit\\Framework\\Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php')
写好一个 autoload 函数,并且注册到 spl_autoload_register()里
题外话:现在很多框架都已经帮我们写好了顶级域名映射了,我们只需要在框架里面新建文件,在新建的文件中写好命名空间,就可以在任何地方 use 我们的命名空间了。
暂无评论,1572人围观