php 原生自动加载与加载规范

自动加载:在需要的时候才把类文件加载进来,为此php内置了__autoload()

__autoload

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

上面函数展示了一般自动加载要做的几个事情:

  • 根据类名确定类文件名;
  • 确定类文件所在的磁盘路径(在我们的例子是最简单的情况,类与调用它们的PHP程序文件在同一个文件夹下);
  • 将类从磁盘文件中加载到系统中。
存在问题

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

SPL Autoload

其包含以下函数:

  • spl_autoload_register:注册 _autoload() 函数
  • spl_autoload_unregister:注销已注册的函数
  • spl_autoload_functions:返回所有已注册的函数
  • spl_autoload_call:尝试所有已注册的函数来加载类
  • spl_autoload :_autoload() 的默认实现
  • spl_autoload_extionsions: 注册并返回 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对这种映射关系做了规范,分别是psr-0和psr-4

psr-0 与 psr-4

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

  • 1、 一个完全合格的 namespace 和 class 必须符合这样的结构:“< Vendor Name>(< Namespace>)*< Class Name>”
  • 2、每个 namespace 必须有一个顶层的namespace("Vendor Name"提供者名字)
  • 3、每个 namespace 可以有多个子 namespace
  • 4、当从文件系统中加载时,每个 namespace 的分隔符(/)要转换成 DIRECTORYSEPARATOR (操作系统路径分隔符)
  • 5、在类名中,每个下划线()符号要转换成 DIRECTORY_SEPARATOR (操作系统路径分隔符)。在 namespace 中,下划线 _符号是没有(特殊)意义的。
  • 6、当从文件系统中载入时,合格的 namespace 和 class 一定是以 .php 结尾的
  • 7、verdor name,namespaces,class 名可以由大小写字母组合而成(大小写敏感的)

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

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

好了,我们先想一想,对于一个具体程序来说,如果它想要支持PSR0标准,它需要做什么调整呢?

首先,程序必须定义一个符合 PSR0 标准第 4、5 条的映射函数,然后把这个函数注册到 spl_register() 中;
其次,定义一个新的命名空间时,命名空间的名字和所在文件的目录位置必须符合第 1、2、3、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,如果没有顶级命名空间,我们就得写两个路径和这两个命名空间相对应,如果再有 Request2、Request3 呢。有了顶层命名空间 /Symfony,那我们就仅仅需要一个目录对应即可,剩下的就利用 PSR 标准去解析就行了。

3.每个 namespace 可以有多个子 namespace

这个很简单,Request 可以定义成 /Symfony/Core/Request,也可以定义成 /Symfony/Core/Transport/Request,/Core 这个命名空间下面可以有很多子命名空间,放多少层命名空间都是自己定义。

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

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

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

这句话的意思就是说,如果我们的命名空间是 /Symfony/Core/Request_a,那么我们就应该把它映射到 \Symfony\Core\Request\a 这样的目录。为什么会有这种规定呢?这是因为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

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

我们是否应该还兼容没有命名空间的情况呢?
按照 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 标准的区别:

在类名中使用下划线没有任何特殊含义。
命名空间与文件目录的映射方法有所调整。
对第二项我们详细解释一下 (Composer自动加载的原理):
假如我们有一个命名空间: Foo/class,Foo 是顶级命名空间,其存在着用户定义的与目录的映射关系:

"Foo/" => "src/"

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

sample

补充,顶级命名空间与文件夹的映射是用户自己或者框架(如laravel)定义好的,比如在laravel中,vender/composer/autoload_psr4.php中,有如下几行代码定义了APP\这个顶级命名空间对应的目录为:项目根文件夹/app

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
	// other class load...
	'App\\' => array($baseDir . '/app'),
);

然后顶级命名空间后的目录解析就按照psr-4的规范进行解析了
即要先定义顶级命名空间对应的目录,然后再按照psr-4规范进行解析,最后加载进来需要用到的类文件

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值