这篇文章主要讲解了“PHP命名空间与类自动加载的实现方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“PHP命名空间与类自动加载的实现方法”吧!
在 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
类找不到时,会根据这个自动加载器进行加载。
结合 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();
这样,我们就可以正常调用这段代码了:
实际项目开发时,手动编写这段 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
,运行结果如下:
感谢各位的阅读,以上就是“PHP命名空间与类自动加载的实现方法”的内容了,经过本文的学习后,相信大家对PHP命名空间与类自动加载的实现方法这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。