PHP自动加载

前言

在PHP里引用类,通常使用include,include_once,require,require_once这个几个函数引入PHP文件。但如果你有一堆classes需要引入呢?

自动加载

在PHP5.3以后,为了解决批量加载文件的问题,__autoload函数就出现了。__autoload是标准的SPL扩展函数,当调用一个不存在的类,会尝试从该函数进行加载。这也就是所谓的延迟加载。

简单的代码实现如下:

<?php
// php/src/Autoload/AutoCase.php
class AutoCase
{
    public static function run()
    {
        echo "run by __autoload." . PHP_EOL;
    }
}

测试用例:

<?php
// php/tests/autoload/case1.php
function __autoload($class)
{
    $dir = dirname(dirname(__DIR__));
    include_once $dir . '/src/Autoload/AutoCase.php';
}
AutoCase::run();

执行测试用例:

php php/tests/autoload/case1.php
# 输出
run by __autoload.

内部实现

实际上__autoload函数的内部实现是spl_autoload_register。
代码实现如下:

<?php
// php/src/Autoload/SplCase.php
class AutoCase
{
    public static function run()
    {
        echo "run by spl_autoload_register." . PHP_EOL;
    }
}

测试用例:

<?php
// php/tests/autoload/case2.php
spl_autoload_register(function($class)
{
    $dir = dirname(dirname(__DIR__));
    include_once $dir . '/src/Autoload/SplCase.php';
});
AutoCase::run();

执行测试用例:

php php/tests/autoload/case2.php
# 输出
run by spl_autoload_register.

那如果两个函数同时存在呢?会出现什么情况呢?

测试用例如下:

<?php
// php/tests/autoload/case3.php
function __autoload($class)
{
    $dir = dirname(dirname(__DIR__));
    include_once $dir . '/src/Autoload/AutoCase.php';
}
spl_autoload_register(function($class)
{
    $dir = dirname(dirname(__DIR__));
    include_once $dir . '/src/Autoload/SplCase.php';
});
AutoCase::run();

执行测试用例:

php php/tests/autoload/case3.php
# 输出
run by spl_autoload_register.

事实上,spl_autoload_register函数默认会覆盖__autoload加载规则。

为什么要使用spl_autoload_register

首先,PHP官方在7.2已经把__autoload 变为DEPRECATED,也就是废弃的状态,后续该函数肯定会被删除的。
其次,spl_autoload_register函数可以有多个自动加载函数,更加灵活地加载自定义类。而__autoload 只能定义一次,只能有一个加载函数。
最后,spl_autoload_register与__autoload 同时存在的话,spl_autoload_register函数默认会覆盖__autoload加载规则。

spl_autoload_register应用

spl_autoload_register常用于多个类的加载,在composer里,composer install 后会生成一个vendor目录,有一个autoload.php。源代码如下:

<?php
// vendor/autoload.php
// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitc8bbd05443dfc4a7281619cb2b14f876::getLoader();

实际上的加载规则在autoload_real.php和ClassLoader.php

<?php
// vendor/composer/autoload_real.php
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInitc8bbd05443dfc4a7281619cb2b14f876
{
    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('ComposerAutoloaderInitc8bbd05443dfc4a7281619cb2b14f876', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        // bala bala
        $loader->register(true);
    }
}

class ClassLoader
{
   /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }
}

总结

我们对比了PHP的spl_autoload_register与__autoload 加载的实现。在后续的版本中,__autoload 实现是应该要废弃的。spl_autoload_register与__autoload 同时存在的话,spl_autoload_register函数默认会覆盖__autoload加载规则。我把上面的用例相关的代码在GitHub版本库,你可以随时查看。

发表评论

电子邮件地址不会被公开。 必填项已用*标注