php自动加载

自动加载原理

我们使用另一个文件定义的一个class的时候,传统的情况下需要require XXX.php

//Good.php
<?php
  class Good{
    //......
  }
 
//use.php
<?php
  require 'Good.php';   //包含进来文件    
  $good = new Good();   //现在可以使用Good类

但是写require真的很烦!!现在可以使用一个自动加载函数,发现类没有被定义的时候自动根据我们定义的规则帮我们去require需要的文件。

//Good.php
<?php
  class Good{
    //......
  }

//use.php
<?php
function __autoload($className){
  require $className . '.php';  //定义包含的规则
}

$good = new Good(); //可以使用Good类,发现没有定义,会自动调用__autoload函数,现在'Good'作为参数传入了__autoload函数,去包含需要的文件

spl_autoload_register

__autoload函数不能解决所有的问题,现在的项目已经非常复杂,光一个autoload函数已经不能满足需要了,因为可能不同的模块使用了不同的自动加载规则。

spl_autoload_register将函数注册到SPL autoload函数队列中,它实际上创建了 __autoload 函数的队列,按定义时的顺序逐个执行。相比之下,__ autoload() 只可以定义一次。

函数原型

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

欲注册的自动装载函数。如果没有提供任何参数,则自动注册 autoload 的默认实现函数spl_autoload()。

  • throw

此参数设置了 autoload_function 无法成功注册时, spl_autoload_register()是否抛出异常。

  • prepend

如果是 true,spl_autoload_register() 会添加函数到队列之首,而不是队列尾部。

函数使用

sql_autoload_resister('load_function'); //函数名
sql_autoload_resister(array('load_object', 'load_function')); //类和静态方法
sql_autoload_resister('load_object::load_function'); //类和方法的静态调用

//php 5.3之后,也可以像这样支持匿名函数了。
spl_autoload_register(function($className){
    if (is_file('./lib/' . $className . '.php')) {
        require './lib/' . $className . '.php';
    }
});

note:使用了spl_autoload_register之后原来的__autoload函数就失效了,如果需要继续使用,需要显示的注册它

 if (function_exists('__autoload')) {
   spl_autoload_register('__autoload');
 }

//。。。。。。

 spl_autoload_register('your_autoload_function');   //现在注册另一个

是否继续在自动加载函数队列中查找不取决于加载函数的返回值和是否require了文件,只有真正require需要的文件才会停止


//Good.php位于app文件夹下
<?php
  class Good{
    //......
  }


//Another.php,位于use同级文件夹下
<?php
class Another{
}


//use.php
<?php
function load1($className)
{
    echo "load1\n";
    if (file_exists($className.'.php')) {
        echo 'found: '.$className.PHP_EOL;
        require $className;
    } else {
        echo 'not found: '.$className.PHP_EOL;
    }
}

function load2($className)
{
    echo "load2\n";
    require 'Another.php';
    echo 'require another class'.PHP_EOL;
    return true;
}

function load3($className)
{
    echo "load3\n";
    if (file_exists('app/'.$className.'.php')) {
        echo 'found '.$className.PHP_EOL;
        require 'app/'.$className.'.php';
    } else {
        echo 'not found: '.$className.PHP_EOL;
    }
}


spl_autoload_register('load1');
spl_autoload_register('load2');
spl_autoload_register('load3');

$a = new Good();

输出如下

load1
not found: Good
load2
require another class
load3
found Good

可以使用spl_autoload_functions()获得注册的所有函数

Array
(
    [0] => load1
    [1] => load2
    [2] => load3
)

取消注册的自动加载函数spl_autoload_unregister

spl_autoload_unregister('load1');

spl_autoload()是__autoload()函数的默认实现

提供了autoload()的一个默认实现。如果不使用任何参数调用 spl_autoload_register() 函数,则以后在进行 autoload() 调用时会自动使用此函数。

自动加载和命名空间

PSR-4自动加载规范

完整的规范请参考PSR-4: Autoloader

标准的类名应该具有下列形式

 \<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>

需要强调的:

  • 完整的类名中任意一部分中的下滑线都是没有特殊含义的
  • 所有类名都必须是大小写敏感的
  • 所有类名都必须是大小写敏感的

当根据命名空间载入文件时候:

  • 完整的类名中,去掉最前面的命名空间分隔符,前面连续的一个或多个命名空间和子命名空间,作为“命名空间前缀”,其必须与至少一个“文件基目录”相对应。
  • 紧接命名空间前缀后的子命名空间必须与相应的”文件基目录“相匹配,其中的命名空间分隔符将作为目录分隔符。
  • 末尾的类名必须与对应的以 .php 为后缀的文件同名。
  • 自动加载器(autoloader)的实现一定不能抛出异常、一定不能触发任一级别的错误信息以及不应该有返回值。

例子

完整类名 命名空间前缀 文件基目录 文件路径
\Acme\Log\Writer\File_Writer Acme\Log\Writer ./acme-log-writer/lib/ ./acme-log-writer/lib/File_Writer.php
\Aura\Web\Response\Status Aura\Web /path/to/aura-web/src/ /path/to/aura-web/src/Response/Status.php
\Symfony\Core\Request Symfony\Core ./vendor/Symfony/Core/ ./vendor/Symfony/Core/Request.php
\Zend\Acl Zend /usr/includes/Zend/ /usr/includes/Zend/Acl.php

实现示例

采用了匿名函数的方式

<?php
/**
 * An example of a project-specific implementation.
 *
 * After registering this autoload function with SPL, the following line
 * would cause the function to attempt to load the \Foo\Bar\Baz\Qux class
 * from /path/to/project/src/Baz/Qux.php:
 *
 *      new \Foo\Bar\Baz\Qux;
 *
 * @param string $class The fully-qualified class name.
 * @return void
 */
spl_autoload_register(function ($class) {
    // project-specific namespace prefix
    $prefix = 'Foo\\Bar\\';

    // base directory for the namespace prefix
    $base_dir = __DIR__ . '/src/';

    // does the class use the namespace prefix?
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        // no, move to the next registered autoloader
        return;
    }

    // get the relative class name
    $relative_class = substr($class, $len);

    // replace the namespace prefix with the base directory, replace namespace
    // separators with directory separators in the relative class name, append
    // with .php
    //下边这一行很关键
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';

    // if the file exists, require it
    if (file_exists($file)) {
        require $file;
    }

});

参考资料:

发表评论