PHP

深入解析 composer 的自动加载原理

勇康博客网
预计阅读时长 21 分钟
位置: 首页 PHP 正文

前言

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 (惰性加载)

总结起来,自动加载功能带来了几处优点:

  1. 使用类之前无需 include / require

  2. 使用类的时候才会 include / require 文件,实现了 lazy loading ,避免了 include / require 多余文件。

  3. 无需考虑引入 类的实际磁盘地址 ,实现了逻辑和实体文件的分离。

PHP自动加载函数__autoload()

从 PHP5 开始,当我们在使用一个类时,如果发现这个类没有加载,就会自动运行 __autoload() 函数,这个函数是我们在程序中自定义的,在这个函数中我们可以加载需要使用的类。下面是个简单的示例:

<?phpfunction __autoload($classname) {        
    require_once ($classname . ".class.php");
}

在我们这个简单的例子中,我们直接将类名加上扩展名 .class.php 构成了类文件名,然后使用 require_once 将其加载。

从这个例子中,我们可以看出 __autoload 至少要做三件事情:

  1. 根据类名确定类文件名;

  2. 确定类文件所在的磁盘路径;

  3. 将类从磁盘文件中加载到系统中。

第三步最简单,只需要使用 include / require 即可。要实现第一步,第二步的功能,必须在开发时约定类名与磁盘文件的映射方法,只有这样我们才能根据类名找到它对应的磁盘文件。

当有大量的类文件要包含的时候,我们只要确定相应的规则,然后在 __autoload() 函数中,将类名与实际的磁盘文件对应起来,就可以实现 lazy loading 的效果 。

__autoload()函数存在的问题

  1. 如果在一个系统的实现中,如果需要使用很多其它的类库,这些类库可能是由不同的开发人员编写的, 其类名与实际的磁盘文件的映射规则不尽相同。这时如果要实现类库文件的自动加载,就必须 在 __autoload() 函数中将所有的映射规则全部实现,这样的话 __autoload() 函数有可能会非常复杂,甚至无法实现。最后可能会导致 __autoload() 函数十分臃肿,这时即便能够实现,也会给将来的维护和系统效率带来很大的负面影响。

  2. 那么问题出现在哪里呢?问题出现在 __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 我们的命名空间了。

本文来自投稿,不代表本站立场,如若转载,请注明出处:
-- 展开阅读全文 --
头像
zblog教程笔记:显示ip评论来源地教程
« 上一篇 2022-05-13
小程序实战(一):创建第一个小程序
下一篇 » 2022-06-14
取消
微信二维码
微信二维码
支付宝二维码

发表评论

暂无评论,1050人围观

作者信息

勇康博客网
承接企业、个人,仿站、定制。域名,主机一键代发
TA的最新作品

热门文章

2
3

动态快讯

标签列表

目录[+]