首页 » PHP » 正文

PHP自动加载机制总结

【背景】

在使用OO(object oriented,面向对象)模式开发时,我们通常把一个class单独放在一个文件里,并以类名作为php文件名,这样方便管理维护。

在PHP5之前,使用一个类前总是需要将它所在的文件include/require进来,如果使用该类的地方较多或某个文件引用的类较多时,就得不停地手动include/require,这样不仅麻烦,而且容易遗漏。这便带来一个问题——当项目越来越大,如何方便快速地加载我们想要的类文件?

于是,PHP5开始有了自动加载机制。


【原理】

从PHP5开始,PHP在使用一个类时,如果这个类没有被加载,会 autoload_func,__autoload() 等2种方式实现自动加载。autoload_func是在拓展插件(即PHP默认内置ext目录下的拓展)中实现的自动加载方法——全局可用,__autoload()是PHPer自己在PHP代码中定义的自动加载方法——有引入__autoload所在的文件才可用,故一般放在公共文件中。

PHP发现使用的类没有被加载时,执行的优先级如下:

①查看当前拓展插件中是否存在autoload_func方法,若有,则使用autoload_func中定义的规则实现自动加载;若无,则进入下一步骤;

②查看当前PHP代码中是否存在__autoload()方法,若有,则使用__autoload()中定义的规则实现自动加载;若无,则抛出异常——报错提示“class 'XXX' not found”。


即,若插件中已实现autoload_func方法,则不会执行PHPer定义的__autoload()方法。因此,自动加载只能选择其中一种方式实现。


目前PHP已内置提供实现autoload_func的拓展插件——SPL(此扩展从PHP 5.3.0 不再被关闭,会一直有效.成为php内核组件一部份),其中的SPL函数便是专门处理自动加载相关的操作。


【实现】

⑴先看__autoload()方法实现自动加载的方式

这是index.php文件

<?php
function __autoload($class)
{
    $file = $class.'.php';
    if(file_exists($file)){
        include $file;
    }
}

$obj = new Test();

这是Test.php文件

<?php
class Test
{
  function __construct()
  {
        echo 'hello world';
  }
}

以上输出:

hello world

⑵再看看拓展插件实现自动加载

目前只有PHP内置的SPL拓展实现autoload_func,其他拓展暂未发现——毕竟官方内置拓展已实现,无需再实现。这里便用SPL拓展为例。

①方式一(注册默认加载规则,即spl_autoload()

这是index.php文件

<?php
spl_autoload_register();
$obj = new Test();

这是Test.PHP文件

<?php
class Test
{
  function __construct()
  {
         echo 'hello world';
  }
}

②方式二(注册自定义加载规则)

这是index.php文件

<?php
function __autoload($class)
{
    $file = $class.'.php';
    if(file_exists($file)){
        include $file;
    }
}

function register($class)
{
    $file = 'extend/'.$class.'.php';
    if(file_exists($file)){
        include $file;
    }
}

class Reader
{
    static function load($class)
    {
        $file = 'app/controller/'.$class.'.php';
	if(file_exists($file)){
      include $file;
        }
    }
}

spl_autoload_register('__autoload');//原代码中,PHPer已实现__autoload(),但在使用SPL拓展之后,__autoload()便失效,须手动添加到注册表中才会生效
spl_autoload_register('register');//把定义了规则的方法添加到注册表中
spl_autoload_register(['Reader','load']);//把定义了规则的类中的函数添加到注册表中

$objTest = new Test();
$objSms = new Sms();
$objUser = new User();

这是Test.PHP文件

<?php
class Test
{
  function __construct()
  {
        echo 'hello world'.PHP_EOL;
  }
}


这是extend/Sms.php

<?php
class Sms
{
  function __construct()
  {
        echo 'I am Sms'.PHP_EOL;
  }
}

这是app/controller/User.php

<?php
class User
{
  function __construct()
  {
        echo 'I am User'.PHP_EOL;
  }
}

以上输出:

hello world
I am Sms
I am User

【注意】

1.不使用SPL系列的函数时,PHPer定义的__autoload()才会生效。

2.若原代码中,PHPer已实现__autoload(),但在使用SPL拓展(即,使用了SPL系列的函数)之后,__autoload()将失效,须手动添加到注册表中才会生效。

3.注册到SPL中的规则按照注册顺序进行匹配,但当有一个规则引入当前要加载的类之后,后面的规则便不再匹配运行。(例如,上述的extend/下也存在一个User类时,因为'register'的注册顺序在['Reader', 'load']之前,故只会加载extend下的User类,不会加载app/controller/下的User类)

4.__autoload()在多人开发时不便于维护,推荐使用SPL,只需使用spl_autoload_register()注册各自所要的规则即可,互不干扰。

【命名空间】

当类越来越多时,很容易造成类名冲突,__autoload()无法识别相同类名所在文件,而SPL的注册表按顺序匹配规则,永远只会加载首个匹配到的类,排后面的同名的类都加载不进来了。这时便需要使用命名空间来管理。

有了命名空间之后,一个类的逻辑位置由PHPer自己声明,而该类所在文件的物理位置在__autoload()或SPL的注册表定义好规则引入,相当于做映射绑定。

先看__autoload()方法如何加载命名空间的类

这是index.php文件

<?php
function __autoload($class)
{
    $arr = explorer('\\', $class);
    if($arr[1]=='app'){
        $arr[1] = 'app/controller';
    }
    if($arr[1]=='extend'){
        $arr[1] = 'extend';
    }
    $class = implode('/', $arr);//将\替换成/,兼容Windows和Linux
    $file = $class.'.php';
  if(file_exists($file)){
    include $file;
  }
}

$objA = new \app\User();
$objB = new \extend\User();


这是app/controller/User.php

<?php
namespace app;

class User
{
  function __construct()
  {
        echo 'I am app's User'.PHP_EOL;
  }
}

这是extend/User.php

<?php
namespace extend;

class User
{
  function __construct()
  {
        echo 'I am extend's User'.PHP_EOL;
  }
}

⑵再看SPL的spl_autoload_register如何加载命名空间的类

这是index.php文件

class Reader
{
    static function app($class)
    {
        $arr = explorer('\\', $class);
        if($arr[1]=='app'){
            $arr[1] = 'app/controller';
    }
    $file = implode('/', $arr) . '.php';//将\替换成/,兼容Windows和Linux
    self::includeFile($file);
    }

    static function extend($class)
    {
        $arr = explorer('\\', $class);
        if($arr[1]=='extend'){
            $arr[1] = 'extend';
        }
        $file = implode('/', $arr) . '.php';//将\替换成/,兼容Windows和Linux
        self::includeFile($file);
    }

    static function includeFile($file)
    {
        if(file_exists($file)){
            include $file;
        }
    }
}

spl_autoload_register(['Reader','app']);
spl_autoload_register(['Reader','extend']);

$objA = new \app\User();
$objB = new \extend\User();

这是app/controller/User.php

<?php
namespace app;

class User
{
  function __construct()
  {
        echo 'I am app's User'.PHP_EOL;
  }
}


这是extend/User.php

<?php
namespace extend;

class User
{
  function __construct()
  {
        echo 'I am extend's User'.PHP_EOL;
  }
}

【问答】

1.为什么要用spl_autoload_register来取代__autoload()?

答:因为__autoload()只是一个加载一次的方法,且全部的加载规则放在一个方法中维护不是最优方案。
相比之下,SPL拓展提供了很多操作加载规则的函数,非常灵活。
而spl_autoload_register()可以多处注册规则,且各个模块的规则可以分开维护。

2.spl_autoload()函数的作用以及其和spl_autoload_register()的关系?

答:spl_autoload()是SPL实现的默认加载规则——应是默认类的命名空间与文件的物理位置相同。
当spl_autoload_register()不带参数时,便自动调用此函数。
据说spl_autoload()是历史遗留问题,虽然PHPer可以调用,但参数有限,且现在也没有什么作用。
个人感觉可能就是解决SPL注册表中没有规则时的问题。

参考文档:

  1. PHP官方文档——PHP标准库 (SPL)

  2. PHP官方文档——SPL 函数

  3. PHP官方文档——spl_autoload

  4. PHP autoload机制详解:http://zccst.iteye.com/blog/1374737

  5. https://segmentfault.com/q/1010000000625354

  6. spl_autoload_register与autoload的区别详解:https://www.jb51.net/article/37746.htm

  7. https://segmentfault.com/q/1010000004321436

以上只是个人的理解总结,欢迎交流。


发表评论