命名空间
在进入命名空间的学习之前,我们先了解一下 PHP 的文件包含。
文件包含
在之前的章节里,我们的示例代码大多都很短,也没有复杂的功能需求,故而集中放在一个文件中也可以接受。但如果创建一个项目呢?拥有许多类和运行实例,如果都放在一个文件将会造成管理困难,合理地分散在不同的文件并进行组合使用才是良好的编程实践。
PHP有文件包含的概念,用来组织不同文件之间的代码。有以下四个语句(语言结构):
include
- 用来引入并运行指定文件(相当于将指定文件内容复制过来),失败时返回
false
并给出警告。
- 用来引入并运行指定文件(相当于将指定文件内容复制过来),失败时返回
require
- 同样用来引入并运行指定文件,但失败时将造成致命错误从而结束脚本
include_once
- 同
include
一样,但所有文件只包含一次,可以避免重复包含的错误
- 同
require_once
- 同
require
一样,但所有文件只包含一次,可以避免重复包含的错误
- 同
比如以下示例:
<?php
// 文件:val.php
$name = 'PHP教程';
$web = '极速教程';
<?php
// 文件:test1.php
include './val.php';
echo $name;// 输出:PHP教程
可以看到即使文件test1.php
中没有定义变量$name
也可以使用。
另外,我们来了解一下 PHP 中一些关于文件目录的常量和方法。
文件目录
在上述示例中,我们可以看到include
语句在包含文件时使用了'./val.php
,其中./
表示本文件所在目录,例如test1.php
文件路径为e:/tutorial/code/test1.php
,那么它所在的目录就是e:/tutorial/code/
,我们可以使用./
定位同目录下的文件。
类似的还有../
,它代表本文件所在目录的上级目录,还是以test1.php
文件为例,它所在目录的上级目录就是e:/tutorial/code/
。这两个组合符表示的是相对路径,意为“相对于本文件...”。但是没有形如.../
这样的符号!
在PHP中有一些关于文件目录的常量和方法,具体看下表:
常量/方法 | 描述 |
---|---|
basename() |
返回一个路径的全路径字符串(绝对路径) |
dirname() |
返回一个路径的父目录字符串(绝对路径) |
__FILE__ |
返回文件的完整路径和文件名 |
__DIR__ |
返回文件所在目录,相当于dirname(__FILE__) |
为什么使用命名空间
了解完PHP的文件包含,我们可以把代码组织在不同的文件中了,但是还有一个问题:当我们引入其他人写的文件时,可能与我们的代码发生命名冲突,以至于我们不得不去一个个修改它们。这很常见,因为许多变量/常量/函数/类名都可能有相似的属性,比如说,小明定义了一个库函数,其中有一个名为add
的函数,小红引入了小明的这个库,但是小红自己也定义了一个名为add
但作用不同的函数,这时候就会产生重复定义的错误。
为了解决这个问题,我们可能会想到将标识符名称写的很长,用于阐述功能与区分代码。但这样也会导致代码臃肿,也并没有有效解决命名冲突的问题,反而不利于代码阅读。
命名空间应运而生。它将相关的类、常量、变量、函数等组合在一起,给这些具有相关性的代码一个特定的标识,即使它们可能并不在同一个文件。
如何声明命名空间
定义一个命名空间使用关键字namespace
声明,除了declare
语句(设定一段代码的执行指令),命名空间必须放在代码的最前面(注释不受影响)。如下示例:
<?php
class Test {}
namespace lib;// 输出:Fatal error: Namespace declaration statement has to be the very first statement or after any declare call in the script
正确方式:
<?php
// 文件:code/10/namespace.php
namespace lib;
class Test {}
同时,其他文件也可以声明为同一个命名空间,说个不大恰当的例子:如果把各个代码文件或者代码片段当做分布在各种地方的垃圾,那么命名空间就相当于把这些垃圾收集在一起的垃圾篓。
子命名空间
由名称可以看出,子命名空间算是命名空间的下层级,定义方式与文件目录系统相似,使用反斜线\
表示其结构。例如我们经常会用到数据库,那么关于数据库的操作代码可以定义顶级命名空间db
,数据库连接放在命名空间db
下,而关于数据库增删查改等操作可以放在命名空间db\handle
下。
也就是说,我们可以使用命名空间对代码组织进行分层。示例如下:
<?php
// 文件:code/10/Connection.php
namespace db;
class Connection
{
// 数据库连接
}
?>
<?php
// 文件:code/10/Search.php
namespace db\handle;
class Search
{
// 搜索
}
?>
<?php
// 文件:code/10/CRUD.php
namespace db\handle;
class CRUD
{
// 增删查改
}
?>
可以在同一文件中声明多个命名空间,但不建议这样做。
如何使用命名空间
使用命名空间的元素(类、Trait、接口、函数、常量)有三种方式:
- 非限定名称:不包含命名空间前缀(相当于文件系统的“当前文件”)
- 限定名称:包含命名空间前缀(相当于文件系统的“相对路径”)
- 完全限定名称:包含全局前缀操作符的命名空间(相当于文件系统的“绝对路径”)
我们定义了一个命名空间为tutorial\test
的文件,代码如下:
<?php
// 文件:code/10/namespace.php
namespace tutorial\test;
class Test {
public function __construct()
{
echo '你创建了一个 ',__CLASS__,' 类',"\n";
}
}
// 函数:冒泡排序
function bubbleSort($arr)
{
for($i = 0; $i < count($arr); $i++) {
for($j = 0; $j < count($arr) - 1; $j++) {
if($arr[$j] > $arr[$j + 1]) {
$tmp = $arr[$j];
$arr[$j] = $arr[$j + 1];
$arr[$j + 1] = $tmp;
}
}
}
return $arr;
}
又创建了另一个文件来使用命名空间中的元素:
<?php
// 文件:code/10/test2.php
include __DIR__.'/namespace.php';
$arr = [1, 3, 43, 21, 78];
// 当前文件未处于命名空间中
// 3.使用完全限定名称(包含全局操作符的名称)访问命名空间
$test = new \tutorial\test\Test();// 输出:你创建了一个 tutorial\test\Test 类
$res = \tutorial\test\bubbleSort($arr);
var_dump($res);
/* 输出:
array(5) {
[0]=>
int(1)
[1]=>
int(3)
[2]=>
int(21)
[3]=>
int(43)
[4]=>
int(78)
}
*/
// 2.使用限定名称(包含前缀的名称)访问命名空间
$test = new tutorial\test\Test();// 输出:你创建了一个 tutorial\test\Test 类
$res = tutorial\test\bubbleSort($arr);
var_dump($res);
/* 输出:
array(5) {
[0]=>
int(1)
[1]=>
int(3)
[2]=>
int(21)
[3]=>
int(43)
[4]=>
int(78)
}
*/
// 1.使用非限定名称访问命名空间
$test = new Test();// 输出:Fatal error: Uncaught Error: Class 'Test' not found
分析上面的例子,在未处于命名空间的文件中(相当于处于全局范围):
- 使用完全限定名称的方式:
\完整的namespace\元素
- 使用限定名称的方式:
完整的namespace\元素
- 使用非限定名称的方式:直接访问命名空间元素,通常会出错
再来看看处于命名空间内的文件访问命名空间的元素会是什么情况:
<?php
// 文件:code/10/test3.php
// 当前文件处于命名空间`tutorial\test`中
namespace tutorial\test;
include __DIR__.'/namespace.php';
$arr = [1, 3, 43, 21, 78];
// 1.使用完全限定名称(包含全局操作符的名称)访问命名空间
$test = new \tutorial\test\Test();// 输出:你创建了一个 tutorial\test\Test 类
$res = \tutorial\test\bubbleSort($arr);
var_dump($res);
/* 输出:
array(5) {
[0]=>
int(1)
[1]=>
int(3)
[2]=>
int(21)
[3]=>
int(43)
[4]=>
int(78)
}
*/
// 2.使用非限定名称访问命名空间
$test = new Test();// 输出:你创建了一个 tutorial\test\Test 类
$res = bubbleSort($arr);
var_dump($res);
/* 输出:
array(5) {
[0]=>
int(1)
[1]=>
int(3)
[2]=>
int(21)
[3]=>
int(43)
[4]=>
int(78)
}
*/
// 3.使用限定名称(包含前缀的名称)访问命名空间
$test = new tutorial\test\Test();// 输出:Fatal error: Uncaught Error: Class 'tutorial\test\tutorial\test\Test' not found
可以看到,使用完全限定名称访问命名空间的元素是正确的。
观察错误输出提示,使用限定名称和非限定名称都会在当前文件的命名空间(如果未定义,则为全局空间)中查找要访问的命名空间元素。
另一种使用命名空间的方式:别名
PHP可以通过关键字use
来导入命名空间元素,并使用as
关键字定义其别名。
使用方式如下:
<?php
// 文件:code/10/use2.php
include __DIR__.'/namespace.php';
// 使用类:Test
use tutorial\test\Test as Test1;// 定义该命名空间的别名为 Test1
// 使用函数:bubbleSort()
use function tutorial\test\bubbleSort;
// 使用常量:CONSTANT
use const tutorial\test\CONSTANT;
$test = new Test1();// 等同于使用 \tutorial\test\Test()
// 测试:
$arr = [1, 3, 43, 21, 78];
var_dump(bubbleSort($arr));
/* 输出:
array(5) {
[0]=>
int(1)
[1]=>
int(3)
[2]=>
int(21)
[3]=>
int(43)
[4]=>
int(78)
}
*/
echo CONSTANT;
使用命名空间中的类时只需使用use
关键字后跟命名空间的完全限定名称后跟元素名;使用函数时需要在use
后跟关键字function
;使用常量则需要在use
后跟关键字const
。如果还需要为它们创建别名,则在元素名后使用as
关键字,后跟别名即可。
一行内导入多个命名空间元素
有时候我们可能会用到许多命名空间元素,每个元素都要写一行use
语句会显得过于臃肿,所以可以在一行内导入多个命名空间元素,使用如下:
<?php
// 文件:code/10/use3.php
include __DIR__.'/namespace.php';
use tutorial\test\Test as Test1, db\Connection;
use function tutorial\test\bubbleSort, db\handle\search;
use const tutorial\test\CONSTANT as val;
// 测试:
$test = new Test1();
$arr = [1, 3, 43, 21, 78];
var_dump(bubbleSort($arr));
/* 输出:
array(5) {
[0]=>
int(1)
[1]=>
int(3)
[2]=>
int(21)
[3]=>
int(43)
[4]=>
int(78)
}
*/
echo val;
需要注意的是,导入多个命名空间元素时每次只能导入同一类型(类、函数、常量),使用逗号隔开。
学完本章节,我们可以更好地组织我们的代码,并进行项目开发了。
下一章我们做一个简单的项目实战(需要了解 HTML 表单知识)。