深入理解composer自动加载原理

Composer 作为 PHP 的包管理工具,为 PHPer 们提供了丰富的类库,本文来一步步剖析 Composer 的原理

从 __autoload魔术方法 到  spl_autoload_register函数

建立vendor目录,下面建立 autoload.php TestClass.php spl_autoload_register.php

利用__autoload实现自动加载

  • autoload.php
<?php
/**
 * Created by 憧憬.
 */


$testClass = new TestClass();
$testClass->print();

function __autoload($className)
{
    require $className . '.php';
}

  • TestClass.php
<?php
/**
 * Created by 憧憬.
 */

class TestClass
{
    public function print()
    {
        echo 'Test Class!!';
    }

}

在编写过程中,我并没有引入 TestClass.php, 但是可以利用 __autoload 这个魔术方法来进行自动引入,该方法就是在类未加载时自动触发,但是这个是之前的实现,未来可能废弃,在 php7.2 后使用 spl_autoload_register 我们来看一下

自动加载的未来: spl_autoload_register

PHPSPL库作为扩展库,其中包含很多类库,已经于5.3.0版本后默认保持开启,成为PHP的一组强大的核心扩展库。

实现自动加载

  • spl_autoload_register.php
<?php
/**
 * Created by 憧憬.
 */

spl_autoload_register(function ($className) {
    require $className . '.php';
});


$testClass = new TestClass();
$testClass->print();

是不是和__autoload()很像,当然作用也很像。我们直接运行这个文件试试,会发现TestClass.php也正常的加载了进来。那么为啥不直接用__autoload()函数,而使用sql_autoload_register()这么诡异的函数,而且还有个神奇的闭包参数!!!

PHP官方文档中的定义

注册给定的函数作为 __autoload 的实现没错

那个匿名函数就是一个__autoload()函数,我们可以理解为给当前这个PHP文件中注册一个__autoload()函数,而使用匿名函数的原因呢?当然就是为了闭包特性,最主要的就是能够带来延迟加载(懒加载 )的实现!

另外,spl_autoload_register()函数不止是仅仅去注册一个__autoload(),它实现并维护了一个__autoload()队列。原来在一个文件中只能有一个__autoload()方法,但现在,你拥有的是一个队列。

函数格式

spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] ) : bool

有点长,我们一步步看:

  • callable autoload_function:闭包函数,不多解释了,上面已经说了,不了解闭包函数的作用可以百度百度
  • bool throw:当autoload_function无法成功注册时,是否抛出异常
  • bool prepend:如果是true,将会添加一个__autoload()函数到队列的顶部
  • 这个函数有返回值,成功或失败

改造一下代码

  • CaseClass.php
<?php
/**
 * Created by 憧憬.
 */

spl_autoload_register(function ($className) {
    require_once 'TestClass' . '.php';
});


spl_autoload_register('CaseAutoLoad');
function CaseAutoLoad( $className ){
    echo "second==>\n";
    require_once 'CaseClass.php';
}


$testClass = new TestClass();
$testClass->print();

echo "还没实例化 CaseClass \n";

// 在这之前都没有执行第二个 spl_autoload_register函数 也就是利用闭包实现了懒加载

$s = new CaseClass();
$s->show();

这个函数最大的作用就是维护这个队列并且可以延迟加载我们需要的文件

查看composer的源码并实现psr4命名空间自动加载

composer init或者直接install之后,自动生成了一个vendor目录,这时您需要在文件中手动的require这个vendor目录下的autoload.php文件,便可以自动加载第三包了,其实这个文件又载入了vendor/composer/autoload_real.php

  • 进入vendor/composer/autoload_real.php
  • getLoader()方法中马上就能发现spl_autoload_register()方法
  • 并在最后看到 $loader->register(true); $loader就是 ClassLoader.php
  • 进入ClassLoader.php文件中,找到register()方法 没错,里面还是一个spl_autoload_register()方法,这样来看,这货就是Composer的灵魂啊!!

我们来看一下,因为考虑到php版本的问题,所以有多种加载方式,比如我们耳熟能详的 命名空间自动加载

vendor 下面建立目录 Model/UserModel.phpautoload_psr4.php

  • Model/UserModel.php
<?php
/**
 * Created by 憧憬.
 */

namespace Model;

class UserModel
{
    public function show()
    {
        echo 'user model!';
    }
}
  • autoload_psr4.php
<?php
/**
 * Created by 憧憬.
 */

spl_autoload_register(function ($class) {
    $file   = __DIR__ . '/' . str_replace('\\', '/', $class) . '.php';
    if (file_exists($file)) {
        require $file;
    }
});

use Model\UserModel;

$t = new UserModel();
$t->show();

这样也就实现了自动加载

autoload_real.php中,我们发现了熟悉的spl_autoload_register函数。但这个文件最大的作用是去加载ClassLoader.php这个文件和一些目录文件,也在同级目录下。在autoload_real.php中,大家可以发现在调用ClassLoader的register()函数前,还加载了几个目录相关的文件

  • autoload_static.php,静态加载方式,顶级类加载命名空间
  • autoload_psr4.php,遵守PSR4规范的包目录映射数组文件
  • autoload_namespaces.php,命名空间映射,PSR0规范
  • autoload_classmap.php,类图映射,命名空间直接映射路径

这些就对应不同的规范,使用不同的自动加载,我们可以安装一些包看一下这些源码会发生哪些改变,我安装了一个 qrcode 的包, autoload_psr4.php的内容变成了这样:

composer源码

接下来,composer这个命令干了什么您应该也就了解了。当您进行composer require时,首先修改了composer.json文件,然后下载包,完成后根据包里的composer.json文件中所对应的规范来修改对应的autoload_xxx.php文件。完成了文件命名空间相关内容的映射。当register()进行加载的时候,自然就得心应手了。

参考自: https://zhuanlan.zhihu.com/p/96505061

您的支持是对我最大的鼓励!

发表于: 作者:憧憬。
关注互联网以及分享全栈工作经验的原创个人博客和技术博客,热爱编程,极客精神
Github 新浪微博 SegmentFault 掘金专栏