PHP 命名空间与类自动加载实现
1、从文件引入谈起
在 PHP 5.3 之前,要在一个 PHP 脚本中引入另一个 PHP 脚本中定义的代码(通常是函数或者类),需要借助 include、require、include_once、require_once 等语句,include 和 require 都可以通过指定路径引入一个 PHP 脚本,区别是 include 没有找到对应路径脚本时发出警告(E_WARNING),而 require 会抛出致命错误(E_COMPILE_ERROR),include_once/require_once 也是用于引入指定路径 PHP 脚本,与 include/require 的区别是如果指定路径已经包含过,不会再次包含,换言之,只会包含一次同一路径脚本,include_once 和 require_once 的区别与 include/require 一样。
所以从性能角度说,使用 include_once/require_once 性能更好一些,至于使用 include_once 还是 require_once,取决于你对指定路径 PHP 脚本不存在的预期处理。
在前面的作业中,我们已经多次使用过它们来引入其他 PHP 脚本文件,比如在博客项目入口文件 index.php
中,我们通过如下代码引入 bootstrap.php
以便引入初始化函数 bootApp
进行调用:
<?php
require_once 'bootstrap.php';
// 新增一个 IoC 容器,通过依赖注入获取对象实例
$container = Container::getInstance();
bootApp($container);
...
然后在 bootstrap.php
中,又通过如下代码引入 Container
类定义:
<?php
require_once 'core/Container.php';
...
自动加载类文件
对于类文件的引入,如果你觉得反复编写 require_once
/include_once
语句太麻烦,还可以借助 spl_auto_register 函数注册自动加载器,实现系统未定义类或接口的自动加载。
比如我们将上述 bootstrap.php
中的通过 require_once
引入 Container
类代码调整为通过 spl_autoload_register
函数自动注册:
spl_autoload_register(function ($className) {
require_once 'core/' . $className. '.php';
});
这样,我们只需要通过 spl_autoload_register
全局注册这个匿名函数即可,当 Container
类找不到时,会根据这个自动加载器进行加载。
2、命名空间及其使用
结合 require_once
/include_once
和 spl_autoload_register
,已经可以很好地解决多个 PHP 脚本之间引入和组合的问题,从而构建出复杂系统,比如 Web 开发框架,或者第三方库等,事实上,在 PHP 5.3 之前,第三方框架和库就是这么做的,不过,细心的同学可能已经看出来,spl_autoload_register
这种自动类加载机制存在一个问题,那就是不同库/组件类名冲突问题,因此,从 PHP 5.3 开始,引入了命名空间的概念,通过命名空间,可以很好的解决这个问题,而且相较于前者,代码可读性更好。
在 PHP 中,通过 namespace
关键字声明当前脚本所在的命名空间,通常,一个 PHP 脚本文件归属于一个命名空间。我们在 php_learning
目录下新建一个 ns
子目录存放本篇教程代码,然后在 ns
目录下创建一个 Test.php
文件,编写一段简单的测试代码如下:
<?php
namespace App;
class Test
{
public static function print ()
{
printf("这是一个测试类: %s\n", __CLASS__);
}
}
我们需要在 PHP 脚本的第一行代码声明代码所属的命名空间(必须是第一行,否则会报错):
namespace App;
表明这段脚本中的所有 PHP 常量、变量、类、函数都归属于这个命名空间,然后我们在这个命名空间中声明了一个 Test
类,以及一个静态方法 print
来打印类名。
接下来,我们在同一目录下创建一个 App.php
脚本来调用 Test::print()
方法:
App.php
和 Test.php
归属于同一个目录,所以声明了相同的命名空间,实际开发过程中,我们通常就是根据目录来组织并管理命名空间的。调用同一个命名空间中的类和函数,可以像上面代码这样直接调用,如果是不同命名空间的类和函数,则需要通过 use
关键字引入,我们在 ns
目录下新建一个 testing
子目录,并在该子目录下新建一个 Test.php
,在这个 PHP 脚本中,我们定义了一个继承自上级目录中定义的 Test
父类的同名子类:
这里,我们将该子类所属命名空间声明为 App\Testing
(同一个命名空间下不允许出现重名的类和函数),然后通过 use
关键字引入上级命名空间中的 Test
类,由于该类名与子类名同名,所以通过 as
关键字为其设置一个别名 BaseTest
,接下来,就可以通过 BaseTest
引用 Test
父类。
在 Test
子类中,我们重写了父类 BaseTest
的 print
方法。
最后,我们可以在 App.php
中这样调用这个子类:
<?php
namespace App;
use App\Testing\Test as SubTest;
Test::print();
SubTest::print();
如果不存在类名冲突,则不需要设置别名:
<?php
namespace App;
use App\Testing\Test;
Test::print();
此外,还可以不使用 use
关键字,直接引用包含完整命名空间的类名:
<?php
namespace App;
Test::print();
\App\Testing\Test::print();
或者这样,使用部分命名空间:
<?php
namespace App;
use App\Testing;
Test::print();
Testing\Test::print();
但是,我们这个系列教程约定通过 use
引入完整命名空间,以避免代码的冗长,提高可读性。
注:学院君这里只是抛砖引玉,简单介绍了 PHP 命名空间的基本使用,更多细节请参考官方文档 或者现代 PHP 新特性系列(一) —— 命名空间这篇教程(链接地址:https://xueyuanjun.com/post/4221)。
自动加载命名空间类
当然,现在调用 php App.php
会报错,不论是 App\Test
还是 App\Testing\Test
类都提示找不到:
要解决这个问题,可以借助上面提到的 spl_autoload_register
函数,将类名所属命名空间解析为对应的目录路径(这就是为什么要根据目录来组织命名空间),然后把通过 require_once
/include_once
引入,我们在 App.php
中加入如下这段代码:
<?php
namespace App;
use App\Testing\Test as SubTest;
spl_autoload_register(function ($className) {
$path = explode('\\', $className);
if ($path[0] == 'App') {
$base = __DIR__;
}
$filename = $path[count($path) - 1] . '.php';
$filepath = $base;
foreach ($path as $key => $val)
{
if ($key == 0 || $key == count($path) - 1) {
continue;
}
$filepath .= DIRECTORY_SEPARATOR . strtolower($val);
}
$filepath .= DIRECTORY_SEPARATOR . $filename;
require_once $filepath;
});
Test::print();
SubTest::print();
这样,我们就可以正常调用这段代码了:
3、通过 Composer 管理命名空间
实际项目开发时,手动编写这段 spl_autoload_register
代码有点麻烦,尤其是项目除了自己编写的代码外,还要引入各种第三方库,我们可以借助 PHP 的包管理工具 Composer 帮我们管理这种命名空间与目录路径的映射,在此之前,我们已经在 PHP 环境搭建篇中在本地系统中安装好了 Composer,因此,只需要在 ns
目录下运行 composer init
初始化 Composer 设置即可,按照向导一路往下走即可,最后会在项目根目录下生成一个 composer.json
配置文件:
如果项目有第三方库依赖,可以在 require
中进行配置,这里是一个测试项目,暂时还没有任何依赖,然后我们在其中配置 autoload
选项来设置类自动加载机制:
{
"name": "php/test",
"description": "A php namespace test project",
"type": "project",
"license": "Apache",
"authors": [
{
"name": "xueyuanjun",
"email": "yaojinbu@outlook.com"
}
],
"minimum-stability": "dev",
"require": {},
"autoload": {
"classmap": [
"."
]
}
}
这里,我们通过在 classmap
数组中添加 .
表示当前根目录作为类自动加载的入口目录,Composer 会从这里开始读取所有命名空间并建立目录映射关系。接下来执行 composer install
初始化依赖库和类自动加载设置:
初始化过程中,会在根目录下创建 vendor
用来存放第三方依赖包和类自动加载相关文件。初始化完成后,可以看到 vendor/composer/autoload_static.php
中已经包含了 App
及其子命名空间的目录映射了:
该文件会被 autoload_real.php
引用,autoload_real.php
又会被 vendor/autoload.php
引用:
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit991075cd5c3b2a6d389bb443802f7669::getLoader();
autoload_php
是所有 Composer 管理类自动加载的入口文件,所以我们只需要在代码中引入这个文件即可通过 Composer 来管理所有类的自动加载,在 App.php
中,修改示例代码如下:
<?php
include_once 'vendor/autoload.php';
use App\Test;
use App\Testing\Test as SubTest;
Test::print();
SubTest::print();
比起之前手动编写 spl_autoload_register
进行类自动加载,现在的代码更加简单清晰,执行 php App.php
,运行结果如下:
实际上,Composer 底层也是通过 spl_autoload_register
函数实现类的自动加载的,只是在此之前,还会建立命令空间与类脚本路径的映射,更多细节,可以参考 Laravel 框架如何基于 Composer 实现类和文件的自动加载 这篇教程(链接地址:https://xueyuanjun.com/post/19890),当然,Composer 作为 PHP 的包管理工具,其功能远不止于此,其更强大的功能在于对第三方扩展包和库进行安装、维护和管理,限于篇幅,这里就不详细展开了,感兴趣的同学可以参考以下两篇教程:
漫谈 PHP 组件、框架、Composer 那些事(链接地址:https://xueyuanjun.com/post/4506)
聊聊 PHP 私有组件以及如何创建自己的 PHP 组件(链接地址:https://xueyuanjun.com/post/4545)
综上,有了命令空间和 Composer 加持,我们可以基于 PHP 轻松构建和维护复杂的、现代的大型项目,下篇教程开始,学院君将给大家演示如何从零开始构建一个 PHP Web 框架。
(全文完)
长按下面的二维码,即可订阅学院君最新发布的 PHP 入门到实战教程:
关于本系列教程的更多动态,请点击页面左下角的「阅读原文」链接查看。
本文分享自微信公众号 - xueyuanjun(geekacademy)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
可视化监控大型集群,这一个工具就够了!
许多企业使用Kubernetes来快速发布新功能并提高服务的可靠性。Rancher使团队能够减少管理其云原生工作负载的操作成本——但获得这些环境的持续可见性可能是一个挑战。 在这篇文章中,我们将探讨如何利用Rancher内置支持的Prometheus和Grafana快速开始监控编排工作负载。然后,我们将向你展示如何将Datadog与Rancher集成,通过丰富的可视化、算法告警和其他功能,帮助你获得对这些临时环境更深入的可见性。 Kubernetes监控所面临的挑战 Kubernetes集群本质上是复杂和动态的。容器以极快的速度启动和关闭:在对数千家组织的超过15亿个容器进行调查时,Datadog发现,编排容器的周转速度(一天)是未编排容器的两倍(两天)。 在这种快节奏的环境中,监控你的应用程序和基础设施比以往任何时候都重要。Rancher内置支持开源监控工具(如Prometheus和Grafana),允许你从Kubernetes集群中跟踪基本的健康和资源指标。 Prometheus按照预设的时间间隔从Kubernetes集群收集指标。虽然Prometheus没有可视化选项,但你可以使...
- 下一篇
【框架】122:spring框架之注解
今天是刘小爱自学Java的第122天。 感谢你的观看,谢谢你。 学习内容安排如下: Spring注解的使用。 JavaWeb项目的搭建。 Spring的Web集成。 本来还计划学Spring的junit测试集成的,结果又没时间了。 一、Spring的注解 IoC容器是Spring的特色之一,可以使用它管理很多Bean,前几天我们都是将这些Bean配置在applicationContext.xml文件中的。 而注解的作用在于:用了注解之后,就不需要在xml文件中配置这些了 。 1开启注解 ①开启spring的注解 context:annotation-config 让注解有效了,能够识别注解。 ②配置注解扫描 context:component-scan 用来专门扫描含有@Component注解的类,自动将其作为bean。 base-package 要扫描包的路径,包含子包,com.liuxiaoai表示该包下的所有子包和类定义注解都有效. 注意:注解扫描配置的时候,会自动开启注解功能,也就是说有了②的话,①就不需要了。 如果是注解+XML组合使用,可以只开启①。 2注解的使用 ①@Co...
相关文章
文章评论
共有0条评论来说两句吧...