当前位置: 首页 > 后端技术 > PHP

PHP自动加载功能原理分析

时间:2023-03-30 01:17:19 PHP

前言开始之前请关注我自己的博客:www.leoyang90.cn本文是对PHP自动加载功能的总结,涉及到PHP的自动加载功能和PHP的命名空间,PHP的PSR0和PSR4标准等一、PHP自动加载功能PHP自动加载功能的由来在PHP开发过程中,如果要从外部引入一个类,通常会使用include和require方法来包含定义这个类的文件.这在小规模开发时问题不大。但是在大型开发项目中,使用这种方式会带来一些隐患:如果一个PHP文件需要使用很多其他的类,那么就需要大量的require/include语句,可能会造成遗漏或者include不需要的类文件。如果大量的文件需要使用其他类,要确保每个文件都包含正确的类文件肯定是一场噩梦,而且require_once非常昂贵。PHP5为这个问题提供了一个解决方案,就是类的自动加载机制。自动加载机制可以使PHP程序在使用类时自动包含class文件,而不是一开始就包含所有的class文件。这种机制也称为延迟加载。总结一下,自动加载功能带来了几个好处:在使用类之前,使用类时不需要include和require。只使用require/include文件,实现懒加载,避免require/include冗余文件。无需考虑导入类的实际磁盘地址,实现了逻辑和实体文件的分离。如果想详细了解autoloading的功能,可以查看资料:PHP类自动加载机制PHP的autoload机制实现分析PHP自动加载函数__autoload()通常在PHP5使用一个类的时候,如果发现这个类做不加载后,会自动运行_autoload()函数。该功能是在程序中自定义的。在这个函数中,我们可以加载我们需要使用的类。这是一个简单的例子:function__autoload($classname){require_once($classname."class.php");在我们这个简单的例子中,我们只是简单地添加扩展名为“.class.php”的类名,形成指定的类文件名,然后使用require_once来加载它。从这个例子我们可以看出autoload至少要做三件事:根据类名确定类文件名;确定类文件所在的磁盘路径(在我们的示例中是最简单的情况,类和调用它们的PHP程序文件在同一个文件夹中);将磁盘文件中的类加载到系统中。第三步最简单,只需要使用include/require。要实现第一步和第二步的功能,开发时必须约定好类名与磁盘文件的映射方式,这样才能根据类名找到对应的磁盘文件。当有大量类文件需要被包含时,我们只需要确定相应的规则,然后在__autoload()函数中,将类名与实际磁盘文件进行匹配即可达到延迟加载的效果。从这里我们也可以看出,_autoload()函数的实现中最重要的是类名和实际磁盘文件映射规则的实现。__autoload()函数的问题如果在一个系统的实现中需要用到很多其他的类库,这些类库可能是由不同的开发者编写的,类名与实际磁盘文件的映射规则是不一样的.这时候如果要实现类库文件的自动加载,就必须在__autoload()函数中实现所有的映射规则。在这种情况下,autoload()函数可能会非常复杂,甚至无法实现。最后,autoload()函数可能会非常臃肿。即使此时能够实现,也会对以后的维护和系统效率产生很大的负面影响。那么问题出在哪里呢?问题是_autoload()是一个全局函数,只能定义一次,不够灵活,所以所有类名和文件名对应的逻辑规则都必须在一个函数中实现,导致函数臃肿。那么如何解决这个问题呢?答案就是使用一个_autoload调用栈,将不同的映射关系写到不同的_autoload函数中,然后统一注册管理。这是PHP5引入的SPLAutoload。SPLAutoloadSPL是StandardPHPLibrary(标准PHP库)的缩写。它是PHP5引入的一个扩展库。其主要功能包括自动加载机制的实现和各种Iterator接口或类。SPLAutoload有几个具体的函数:spl_autoload_register:注册__autoload()函数spl_autoload_unregister:注销注册的函数spl_autoload_functions:返回所有注册的函数spl_autoload_call:尝试所有注册的函数加载类spl_autoload:__autoload()的默认实现spl_autoload_extions:注册并返回spl_autoload函数使用的默认文件扩展名。这些函数的详细用法可以参考php中spl_autoload的详解。简而言之,spl_autoload是SPL自己定义的__autoload()函数。/.inc文件。当然你也可以通过注册扩展名(spl_autoload_extions)来指定具体的文件类型。而spl_autoload_register()就是我们上面提到的autoload调用栈。我们可以向这个函数注册多个我们自己的_autoload()函数。当PHP找不到类名时,PHP会调用这个栈,一个一个调用。调用自定义的_autoload()函数实现自动加载功能。如果我们不向该函数传递任何参数,那么spl_autoload()函数将被注册。嗯,这些是PHP自动加载的底层。注册机制已经很灵活了,还缺少什么呢?上面我们说了,自动加载的关键是类名和文件的映射。不同的框架对这种映射关系有不同的处理方式,非常灵活,但是如果过于灵活,就会显得杂乱无章。PHP对这种映射关系有一个规范,即PSRStandardPSR0和PSR4。不过,在讲PSR0和PSR4之前,我们需要了解一下PHP的命名空间问题,因为这两个标准并不是针对类名和目录文件的映射,而是针对命名空间和文件的映射。为什么会这样?在我的理解中,标准的面向对象的PHP思想,命名空间在一定程度上是类名的别名,那么为什么要引入命名空间,命名空间有什么优点2、Namespace命名空间要理解命名空间,首先First看看官方文档中关于命名空间的介绍:什么是命名空间?从广义上讲,命名空间是一种封装事物的方式。这种抽象可以在很多地方看到。例如,目录在操作系统中用于对相关文件进行分组,它充当目录中文件的名称空间。例如,文件foo.txt可以同时存在于/home/greg和/home/other目录下,但两个foo.txt文件不能存在于同一个目录下。另外,访问/home/greg目录外的foo.txt文件时,必须在文件名前加上目录名和目录分隔符,才能得到/home/greg/foo.txt。将这一原则应用到编程领域就是命名空间的概念。在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是FrameworkInteroperabilityGroup(框架互操作性组织)的缩写。它由几位开源框架的开发人员于2009年创立。从那时起,许多其他成员被选中。虽然它不是一个“官方”组织,但它也代表了社区的一小部分。组织的宗旨是:以最低限度的限制统一各个项目的编码标准,避免各个公司自研风格阻碍程序员发展的麻烦,所以大家发明总结了PSR,PSR正在提出标准建议(RecommendationsforStandards的缩写)。具体详细的规范标准可以查看PHP中的PSR规范PSR0。标准的PRS-0规范是他们的第一套规范。主要制定了一些自动加载标准(AutoloadingStandard)PSR-0的强制性要求:1.一个完全限定的命名空间和类必须符合这个结构:“()*”2.每个命名空间必须有一个顶级命名空间(“供应商名称”提供商名称)3.每个命名空间可以有多个子命名空间4。从文件系统加载时,每个命名空间的分隔符(/)应转换为DIRECTORY_SEPARATOR(操作系统路径分隔符)5。在类名中,每个下划线(_)符号转换为DIRECTORY_SEPARATOR(操作系统路径分隔符)。下划线_符号在命名空间中没有(特殊)含义。6.从文件系统加载时,限定的命名空间和类必须以.php结尾7.Verdor名称、命名空间、类名可以由大小写字母组成(区分大小写)具体规则可能会有所不同比较迷惑,下面开始从一开始就。我们先来看看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标准。一个完全限定的命名空间和类必须符合这个结构:"()*"/上面显示的Symfony是VendorName,也就是第三方库的名字,和/Coreis命名空间名称一般是我们命名空间的一些属性信息(比如request是Symfony的核心功能);最后,Request是我们命名空间的名称。这个标准规范是为了让人们看到命名空间的来源和作用。代码维护。2.每个命名空间必须有一个顶级命名空间(“VendorName”提供者名称),这意味着每个命名空间必须有一个类似于/Symfony的顶级命名空间。为什么会有这样的规定呢?因为PSR0标准只负责顶层命名空间之后的映射关系,也就是/Symfony/Core/Request中的部分,/Symfony应该关联到哪个目录,由用户或者框架定义。所谓顶级命名空间,就是自定义映射关系的命名空间,一般是提供者的名字(第三方库的名字)。换句话说,顶级命名空间是自动加载的基础。为什么标准是这样的?原因很简单,如果有一个命名空间/Symfony/Core/Transport/Request,另一个命名空间是/Symfony/Core/Transport/Request1,如果没有顶级命名空间,我们要写两个路径,这两个对应一个命名空间,如果有Request2和Request3怎么办。有了顶层命名空间/Symfony,我们只需要一个目录对应,剩下的就可以使用PSR标准来解析了。3.每个命名空间可以有多个子命名空间。这很简单。Request可以定义为/Symfony/Core/Request或/Symfony/Core/Transport/Request。/Core命名空间下可以有很多子命名空间,放置多少层命名空间是自己定义的。4.从文件系统加载时,每个命名空间的分隔符(/)转换为DIRECTORY_SEPARATOR(操作系统路径分隔符)现在我们终于来到了映射规范。命名空间的/符号应该转换成路径分隔符,也就是说命名空间/Symfony/Core/Request应该转换成类似SymfonyCoreRequest的目录结构。5、在类名中,每个下划线_符号要转换为DIRECTORYSEPARATOR(操作系统路径分隔符)。在命名空间内,下划线符号没有(特殊)含义。这句话的意思是,如果我们的命名空间是/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一切都很完美,对吧?不,仍然存在缺陷:我们是否也应该在没有名称空间的情况下兼容?根据PSR0标准,命名空间/A/B/C/D/E/F必须对应一个目录结构/A/B/C/D/E/F。这个目录结构层次是不是太深了?2013年底,PSR4标准发布了第五个规范——PSR-4。PSR-4规定了如何指定自动加载类定义的文件路径,也规定了自动加载文件的位置。乍一看,这是PSR-0的翻版,但实际上,确实是功能上的翻版。不同的是PSR-4的规范比较干净,去掉了PHP5.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,为什么想这样改变吗?原因是由于命名空间太长导致目录层级太深,使得命名空间与文件目录的映射关系更加灵活。再举一个例子,来源PSR-4——新发布的PHP规范:PSR-0风格的vendor/vendor_name/package_name/src/Vendor_Name/Package_Name/ClassName.php#Vendor_Name\Package_Name\ClassNametests/Vendor_Name/Package_Name/ClassNameTest。php#Vendor_Name\Package_Name\ClassNamePSR-4stylevendor/vendor_name/package_name/src/ClassName.php#Vendor_Name\Package_Name\ClassNametests/ClassNameTest.php#Vendor_Name\Package_Name\ClassNameTest对比上面两个结构,很明显PSR-4带来更清晰的文件结构。用StackEdit编写。