PHP自动加载功能原理解析

前言


这篇文章是对PHP自动加载功能的一个总结,内容涉及PHP的自动加载功能、PHP的命名空间、PHP的PSR0与PSR4标准等内容。php

1、PHP自动加载功能


PHP自动加载功能的由来

在PHP开发过程当中,若是但愿从外部引入一个 class,一般会使用 include 和 require 方法,去把定义这个 class 的文件包含进来。这个在小规模开发的时候,没什么大问题。但在大型的开发项目中,使用这种方式会带来一些隐含的问题:若是一个 PHP 文件须要使用不少其它类,那么就须要不少的 require/include 语句,这样有可能会形成遗漏或者包含进没必要要的类文件。若是大量的文件都须要使用其它的类,那么要保证每一个文件都包含正确的类文件确定是一个噩梦, 何况 require_once 的代价很大。html

PHP5 为这个问题提供了一个解决方案,这就是类的自动装载 (autoload) 机制。autoload 机制可使得 PHP 程序有可能在使用类时才自动包含类文件,而不是一开始就将全部的类文件 include 进来,这种机制也称为 lazy loading。laravel

总结起来,自动加载功能带来了几处优势:程序员

  1. 使用类以前无需 include 或者 requiresegmentfault

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

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

  
若是想具体详细的了解关于自动加载的功能,能够查看资料:
PHP的类自动加载机制
PHP的autoload机制的实现解析函数

PHP自动加载函数__autoload()

一般 PHP5 在使用一个类时,若是发现这个类没有加载,就会自动运行 _autoload() 函数,这个函数是咱们在程序中自定义的,在这个函数中咱们能够加载须要使用的类。下面是个简单的示例:post

function __autoload($classname) {
    require_once ($classname . "class.php"); 
}

在咱们这个简单的例子中,咱们直接将类名加上扩展名 ”.class.php” 构成了类文件名,而后使用 require_once 将其加载。从这个例子中,咱们能够看出 autoload 至少要作三件事情:网站

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

  2. 肯定类文件所在的磁盘路径(在咱们的例子是最简单的状况,类与调用它们的PHP程序文件在同一个文件夹下);

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


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

当有大量的类文件要包含的时候,咱们只要肯定相应的规则,而后在 __autoload() 函数中,将类名与实际的磁盘文件对应起来,就能够实现 lazy loading 的效果。从这里咱们也能够看出 _autoload() 函数的实现中最重要的是类名与实际的磁盘文件映射规则的实现。

__autoload() 函数存在的问题

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

那么问题出如今哪里呢?问题出如今 _autoload() 是全局函数只能定义一次,不够灵活,因此全部的类名与文件名对应的逻辑规则都要在一个函数里面实现,形成这个函数的臃肿。那么如何来解决这个问题呢?答案就是使用一个 _autoload 调用堆栈,不一样的映射关系写到不一样的 _autoload 函数中去,而后统一注册统一管理,这个就是 PHP5 引入的 SPL Autoload。

SPL Autoload

SPL是 Standard PHP Library (标准PHP库)的缩写。它是 PHP5 引入的一个扩展库,其主要功能包括 autoload 机制的实现及包括各类 Iterator 接口或类。SPL Autoload 具体有几个函数:

  1. spl_autoload_register:注册__autoload()函数

  2. spl_autoload_unregister:注销已注册的函数

  3. spl_autoload_functions:返回全部已注册的函数

  4. spl_autoload_call:尝试全部已注册的函数来加载类

  5. spl_autoload :__autoload() 的默认实现

  6. spl_autoload_extionsions: 注册并返回 spl_autoload 函数使用的默认文件扩展名。

  
这几个函数具体详细用法可见 php中spl_autoload详解

简单来讲,spl_autoload 就是 SPL 本身的定义 __autoload() 函数,功能很简单,就是去注册的目录(由 set_include_path 设置)找与 $classname 同名的 .php/.inc 文件。固然,你也能够指定特定类型的文件,方法是注册扩展名 (spl_autoload_extionsions)。

而 spl_autoload_register() 就是咱们上面所说的 autoload 调用堆栈,咱们能够向这个函数注册多个咱们本身的 _autoload() 函数,当 PHP 找不到类名时,PHP 就会调用这个堆栈,一个一个去调用自定义的 _autoload() 函数,实现自动加载功能。若是咱们不向这个函数输入任何参数,那么就会注册 spl_autoload() 函数。

好啦,PHP 自动加载的底层就是这些,注册机制已经很是灵活,可是还缺什么呢?咱们上面说过,自动加载关键就是类名和文件的映射,这种映射关系不一样框架有不一样方法,很是灵活,可是过于灵活就会显得杂乱,PHP 有专门对这种映射关系的规范,那就是 PSR 标准中 PSR0 与 PSR4。

不过在谈 PSR0 与 PSR4 以前,咱们还须要了解 PHP 的命名空间的问题,由于这两个标准其实针对的都不是类名与目录文件的映射,而是命名空间与文件的映射。为何会这样呢?在个人理解中,规范的面向对象 PHP 思想,命名空间在必定程度上算是类名的别名,那么为何要推出命名空间,命名空间的优势是什么呢

2、Namespace命名空间


要了解命名空间,首先先看看 官方文档 中对命名空间的介绍:

什么是命名空间?从广义上来讲,命名空间是一种封装事物的方法。在不少地方均可以见到这种抽象概念。例如,在操做系统中目录用来将相关文件分组,对于目录中的文件来讲,它就扮演了命名空间的角色。具体举个例子,文件 foo.txt 能够同时在目录 /home/greg 和 /home/other 中存在,但在同一个目录中不能存在两个 foo.txt 文件。另外,在目录 /home/greg 外访问 foo.txt 文件时,咱们必须将目录名以及目录分隔符放在文件名以前获得 /home/greg/foo.txt。这个原理应用到程序设计领域就是命名空间的概念。
在PHP中,命名空间用来解决在编写类库或应用程序时建立可重用的代码如类或函数时碰到的两类问题:
1 用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突
2 为很长的标识符名称(一般是为了缓解第一类问题而定义的)建立一个或简短)的名称,提升源代码的可读性。
PHP 命名空间提供了一种将相关的类、函数和常量组合到一块儿的途径。

  
简单来讲就是 PHP 是不容许程序中存在两个名字同样同样的类或者函数或者变量名的,那么有人就很疑惑了,那就不起同样名字不就能够了?事实上不少大程序依赖不少第三方库,名字冲突什么的不要太常见,这个就是官网中的第一个问题。那么如何解决这个问题呢?在没有命名空间的时候,可怜的程序员只能给类名起 a_b_c_d_e_f 这样的,其中 a/b/c/d/e/f 通常有其特定意义,这样通常就不会发生冲突了,可是这样长的类名编写起来累,读起来更是难受。所以 PHP5 就推出了命名空间,类名是类名,命名空间是命名空间,程序写 / 看的时候直接用类名,运行起来机器看的是命名空间,这样就解决了问题。

另外,命名空间提供了一种将相关的类、函数和常量组合到一块儿的途径。这也是面向对象语言命名空间的很大用途,把特定用途所须要的类、变量、函数写到一个命名空间中,进行封装。

解决了类名的问题,咱们终于能够回到 PSR 标准来了,那么 PSR0 与 PSR4 是怎么规范文件与命名空间的映射关系的呢?答案就是:对命名空间的命名(额,有点绕)、类文件目录的位置和二者映射关系作出了限制,这个就是标准的核心了。更完整的描述可见 现代 PHP 新特性系列(一) —— 命名空间

3、PSR标准


在说 PSR0 与 PSR4 以前先介绍一下 PSR 标准。PSR 标准的发明者和规范者是:PHP-FIG,它的网站是:www.php-fig.org。就是这个联盟组织发明和创造了 PSR 规范。FIG 是 Framework Interoperability Group(框架可互用性小组)的缩写,由几位开源框架的开发者成立于 2009 年,从那开始也选取了不少其余成员进来,虽然不是 “官方” 组织,但也表明了社区中不小的一块。组织的目的在于:以最低程度的限制,来统一各个项目的编码规范,避免各家自行发展的风格阻碍了程序设计师开发的困扰,因而大伙发明和总结了 PSR,PSR 是 Proposing a Standards Recommendation(提出标准建议)的缩写。

具体详细的规范标准能够查看 
PHP中PSR规范

PSR0 标准

PRS-0规范是他们出的第1套规范,主要是制定了一些自动加载标准(Autoloading Standard)PSR-0 强制性要求几点:

一、 一个彻底合格的 namespace 和 class 必须符合这样的结构:“< Vendor Name>(< Namespace>)*< Class Name>”
二、每一个 namespace 必须有一个顶层的 namespace("Vendor Name" 提供者名字)
三、每一个 namespace 能够有多个子 namespace
四、当从文件系统中加载时,每一个 namespace 的分隔符(/)要转换成 DIRECTORY_SEPARATOR (操做系统路径分隔符)
五、在类名中,每一个下划线(_)符号要转换成 DIRECTORY_SEPARATOR (操做系统路径分隔符)。在 namespace 中,下划线_符号是没有(特殊)意义的。
六、当从文件系统中载入时,合格的 namespace 和 class 必定是以 .php 结尾的
七、verdor name,namespaces,class 名能够由大小写字母组合而成(大小写敏感的)

  
具体规则可能有些让人晕,咱们从头讲一下。
  
咱们先来看PSR0标准大体内容,第一、二、三、7条对命名空间的名字作出了限制,第四、5条对命名空间和文件目录的映射关系作出了限制,第6条是文件后缀名。
  
前面咱们说过,PSR标准是如何规范命名空间和所在文件目录之间的映射关系?是经过限制命名空间的名字、所在文件目录的位置和二者映射关系。
  
那么咱们可能就要问了,哪里限制了文件所在目录的位置了呢?其实答案就是:
  

限制命名空间名字 + 限制命名空间名字与文件目录映射 = 限制文件目录

  
好了,咱们先想想,对于一个具体程序来讲,若是它想要支持PSR0标准,它须要作什么调整呢?

  1. 首先,程序必须定义一个符合PSR0标准第四、5条的映射函数,而后把这个函数注册到 spl_register() 中;

  2. 其次,定义一个新的命名空间时,命名空间的名字和所在文件的目录位置必须符合第一、二、三、7条。

   
通常为了代码维护方便,咱们会在一个文件只定义一个命名空间。
   
好了,咱们有了符合PSR0的命名空间的名字,经过符合PSR0标准的映射关系就能够获得符合PSR0标准的文件目录地址,若是咱们按照PSR0标准正确存放文件,就能够顺利require该文件了,咱们就可使用该命名空间啦,是否是很神奇呢?
   
接下来,咱们详细地来看看PSR0标准到底规范了什么呢?
   
咱们以 laravel 中第三方库Symfony其中一个命名空间 /Symfony/Core/Request 为例,讲一讲上面 PSR0 标准。
  

  1. 一个彻底合格的 namespace 和 class 必须符合这样的结构:“< Vendor Name>(< Namespace>)*< Class Name>”

上面所展现的 /Symfony 就是 Vendor Name,也就是第三方库的名字,/Core 是 Namespace 名字,通常是咱们命名空间的一些属性信息(例如 request 是 Symfony 的核心功能);最后 Request 就是咱们命名空间的名字,这个标准规范就是让人看到命名空间的来源、功能很是明朗,有利于代码的维护。
  

2 . 每一个 namespace 必须有一个顶层的 namespace("Vendor Name" 提供者名字)

也就是说每一个命名空间都要有一个相似于 /Symfony 的顶级命名空间,为何要有这种规则呢?由于 PSR0 标准只负责顶级命名空间以后的映射关系,也就是 /Symfony/Core/Request 这一部分,关于 /Symfony 应该关联到哪一个目录,那就是用户或者框架本身定义的了。所谓的顶层的 namespace,就是自定义了映射关系的命名空间,通常就是提供者名字(第三方库的名字)。换句话说顶级命名空间是自动加载的基础。为何标准要这么设置呢?缘由很简单,若是有个命名空间是 /Symfony/Core/Transport/Request,还有个命名空间是 /Symfony/Core/Transport/Request1,若是没有顶级命名空间,咱们就得写两个路径和这两个命名空间相对应,若是再有 Request二、Request3 呢。有了顶层命名空间 /Symfony,那咱们就仅仅须要一个目录对应便可,剩下的就利用 PSR 标准去解析就好了。
  

3.每一个namespace能够有多个子namespace

这个很简单,Request 能够定义成 /Symfony/Core/Request,也能够定义成 /Symfony/Core/Transport/Request,/Core 这个命名空间下面能够有不少子命名空间,放多少层命名空间都是本身定义。

  

4.当从文件系统中加载时,每一个 namespace 的分隔符(/)要转换成 DIRECTORY_SEPARATOR (操做系统路径分隔符)

如今咱们终于来到了映射规范了。命名空间的/符号要转为路径分隔符,也就是说要把 /Symfony/Core/Request 这个命名空间转为 SymfonyCoreRequest 这样的目录结构。
  

5.在类名中,每一个下划线 _ 符号要转换成 DIRECTORYSEPARATOR (操做系统路径分隔符)。在 namespace 中,下划线符号是没有(特殊)意义的。

这句话的意思就是说,若是咱们的命名空间是 /Symfony/Core/Request_a,那么咱们就应该把它映射到 SymfonyCoreRequesta 这样的目录。为何会有这种规定呢?这是由于 PHP5 以前并无命名空间,程序员只能把名字起成 Symfony_Core_Request_a 这样, PSR0 的这条规定就是为了兼容这种状况。
剩下两个很简单就不说了。
  
有这样的命名空间命名规则和映射标准,咱们就能够推理出咱们应该把命名空间所在的文件该放在哪里了。依旧以 Symfony/Core/Request 为例, 它的目录是 /path/to/project/vendor/Symfony/Core/Request.php,其中 /path/to/project是你项目在磁盘的位置,/path/to/project/vendor 是项目用的全部第三方库所在目录。/path/to/project/vendor/Symfony 就是与顶级命名空间 /Symfony 存在对应关系的目录,再往下的文件目录就是按照PSR0标准创建的:

/Symfony/Core/Request => /Symfony/Core/Request.php

一切很完满了是吗?不,还有一些瑕疵:

  1. 咱们是否应该还兼容没有命名空间的状况呢?

  2. 按照 PSR0 标准,命名空间 /A/B/C/D/E/F 必然对应一个目录结构 /A/B/C/D/E/F,这种目录结构层次是否是太深了?

PSR4标准

2013年末,新出了第5个规范——PSR-4。

PSR-4规范了如何指定文件路径从而自动加载类定义,同时规范了自动加载文件的位置。这个乍一看和 PSR-0 重复了,实际上,在功能上确实有所重复。区别在于 PSR-4 的规范比较干净,去除了兼容 PHP 5.3 之前版本的内容,有一点 PSR-0 升级版的感受。固然,PSR-4 也不是要彻底替代 PSR-0,而是在必要的时候补充 PSR-0 ——固然,若是你愿意,PSR-4 也能够替代 PSR-0。PSR-4 能够和包括 PSR-0 在内的其余自动加载机制共同使用。
  
PSR4标准与PSR0标准的区别:

  1. 在类名中使用下划线没有任何特殊含义。

  2. 命名空间与文件目录的映射方法有所调整。

对第二项咱们详细解释一下 ( Composer自动加载的原理):
假如咱们有一个命名空间:Foo/class,Foo 是顶级命名空间,其存在着用户定义的与目录的映射关系:

"Foo/" => "src/"

按照PSR0标准,映射后的文件目录是: src/Foo/class.php,可是按照 PSR4 标准,映射后的文件目录就会是:src/class.php,为何要这么更改呢?缘由就是怕命名空间太长致使目录层次太深,使得命名空间和文件目录的映射关系更加灵活。

再举一个例子,来源 PSR-4——新鲜出炉的PHP规范
PSR-0风格

    vendor/
    vendor_name/
        package_name/
            src/
                Vendor_Name/
                    Package_Name/
                        ClassName.php       # Vendor_Name\Package_Name\ClassName
            tests/
                Vendor_Name/
                    Package_Name/
                        ClassNameTest.php   # Vendor_Name\Package_Name\ClassName

  PSR-4风格

    vendor/
    vendor_name/
        package_name/
            src/
                ClassName.php       # Vendor_Name\Package_Name\ClassName
            tests/
                ClassNameTest.php   # Vendor_Name\Package_Name\ClassNameTest    vendor/
    vendor_name/
        package_name/
            src/
                ClassName.php       # Vendor_Name\Package_Name\ClassName
            tests/
                ClassNameTest.php   # Vendor_Name\Package_Name\ClassNameTest

对比以上两种结构,明显能够看出PSR-4带来更简洁的文件结构。

 

本文转自:https://segmentfault.com/a/1190000009368742

相关文章
相关标签/搜索