10.PHP 5.3版本以后新增加的命名空间
PHP中声明的函数名、类名和常量名称,在同一次运行中是不能重复的,否则会产生一个致命的错误,常见的解决办法是约定一个前缀。
在PHP 5.3以后的版本中,增加了很多其他高级语言(如Java、C#等)使用很成熟的功能——命名空间,它的一个最明确的目的就是解决重名问题。命名空间将代码划分出不同的区域,每个区域的常量、函数和类的名字互不影响。
注意: 书中提到的常量从PHP 5.3开始有了新的变化,可以使用const关键字在类的外部声明常量。虽然const和define都是用来声明常量的,但是在命名空间里,define的作用是全局的,而const则作用于当前空间。本书提到的常量是指使用const声明的常量。
命名空间的作用和功能都很强大,在写插件或者通用库的时候再也不用担心重名问题。不过如果项目进行到一定程度,要通过增加命名空间去解决重名问题,工作量不会比重构名字少。因此,从项目一开始的时候就应该很好地规划它,并制定一个命名规范。
10.1.命名空间的基本应用
默认情况下,所有PHP中的常量、类和函数的声明都放在全局空间下。PHP 5.3以后的版本有了独自的空间声明,不同空间中的相同命名是不会冲突的。独立的命名空间使用namespace关键字声明,如下所示:
// 声明这段代码的命名空间 "MyProject"
namespace MyProject;
// ...code...
注意: namespace需要写在PHP脚本的顶部,必须是第一个PHP指令(declare除外)。不要在namespace前面出现非PHP代码、HTML或空格。
从代码“namespace MyProject”开始,到下一个“namespace”出现之前或脚本运行结束是一个独立空间,将这个空间命名为“MyProject”。
如果你为相同代码块嵌套命名空间或定义多个命名空间是不可能的,如果有多个namespace一起使用,则只有最后一个命名空间才能被识别,但你可以在同一个文件中定义不同的命名空间代码,如下:
namespace Myproject1;
// 以下是命名空间MyProject1区域下使用的PHP代码
// 此User属于MyProject1空间的类
class User {
// 类中的成员
}
namespace Myproject2;
// 以下是命名空间MyProject2区域下使用的PHP代码
// 此User属于MyProject2空间的类
class User {
// 类中的成员
}
// 上面的替代语法,另一种声明方法
namespace MyProject3 {
// 这里是命名空间MyProject3区域下使用的PHP代码
}
上面的代码虽然可行,不同命名空间下使用各自的User类,但建议为每个独立文件只定义一个命名空间,这样的代码可读性才是最好的。
在相同的空间可以直接调用自己空间下的任何元素,而在不同空间之间是不可以直接调用其他空间元素的,需要使用命名空间的语法。示例代码如下所示:
namespace MyProject1; //定义命名空间MyProject1
const TEST='this is a const'; //在MyProject1中声明一个常量TEST
function demo() { //在MyProject1中声明一个函数
echo "this is a function";
}
class User { //此User属于MyProject1空间的类
function fun() {
echo "this is User's fun()";
}
}
echo TEST; //在自己命名空间直接使用常量
demo(); //在自己命名空间中直接调用本空间函数
/*********************************命名空间MyProject2******************************************/
namespace MyProject2; //定义命名空间MyProject2
const TEST2 = "this is MyProject2 const"; //在MyProject2中声明一个常量TEST2
echo TEST2; //在自己命名空间直接使用常量
\MyProject1\demo(); //调用MyProject1空间中的demo()函数
$user = new \MyProject1\User(); //使用MyProject1空间的类实例化对象
$user -> fun();
上例中声明了两个空间MyProject1和MyProject2,在自己的空间中可以直接调用本空间中声明的元素,而在MyProject2中调用MyProject1中的元素时,使用了一种类似文件路径的语法“\空间名\元素名”。对于类、函数和常量的用法是一样的。
10.2. 命名空间的子空间和公共空间
命名空间和文件系统的结构很像,文件夹可以有子文件夹,命名空间也可以定义子空间来描述各个空间之间的所属关系。例如,cart和order这两个模块都处于同一个broshop项目内,通过命名空间子空间表达关系的代码如下所示:
namespace broshop\cart; //使用命名空间表示处于brophp下的cart模块
class Test{} //声明Test类
namespace brophp\order; //使用命名空间表示处于brophp下的order模块
class Test{} //声明和上面空间相同的类
$test = new Test(); //调用当前空间的类
$cart_test = new \brophp\cart\Test(); //调用brophp\cart空间的类
命名空间的子空间还可以定义很多层次,例如“cn\ydma\www\broshop”。多层子空间的声明通常使用公司域名的倒置,再加上项目名称组合而成。这样做的好处是域名在互联网上是不重复的,不会出现和网上同名的命名空间,还可以辨别出是哪家公司的具体项目,有很强的广告效应。
命名空间的公共空间很容易理解,其实没有定义命名空间的方法、类库和常量都默认归属于公共空间,这样就解释了在以前版本上编写的代码大部分都可以在PHP 5.3以后的版本中运行。
另外,公共空间中的代码段被引入到某个命名空间下以后,该公共空间中的代码段不属于任何命名空间。例如,声明一个脚本文件common.inc.php,在文件中声明的函数和类如下所示:
/*
文件common.inc.php
*/
//文件common.inc.php中声明一个可用的函数
function func() {
//... ...
}
//文件common.inc.php中声明一个可用的类
class Demo {
//... ...
}
再创建一个PHP文件,并在一个命名空间里引入这个脚本文件common.inc.php,但这个脚本里的类和函数并不会归属到这个命名空间。如果这个脚本里没有定义其他命名空间,它的元素就始终处于公共空间中,代码如下所示:
//声明命名空间cn\ydma
namespace cn\ydma;
//引入当前目录下的脚本文件common.inc.php
include './common.inc.php';
//出现致命错误:找不到cn\ydma\Demo类,默认会在本空间中查找
$demo = new Demo();
//正确,调用公共空间的方式是直接在元素名称前加 \ 就可以了
$demo = new \Demo();
//错误, 系统函数都在公共空间
var_dump();
//正确,使用了"/"
\var_dump();
调用公共空间的方式是直接在元素名称前加上“\”就可以了,否则PHP解析器会认为用户想调用当前空间下的元素。除了自定义的元素,还包括PHP自带的元素,都属于公共空间。
其实公共空间的函数和常量不用加“\”也可以正常调用,但是为了正确区分元素所在区域,还是建议调用函数的时候加上“\”。
10.3.命名空间中的别名和术语
非限定名称、限定名称和完全限定名称是使用命名空间的三个术语,了解它们对学习后面的内容很有帮助。不仅是弄懂概念,也要掌握PHP是怎样解析的。三个名称和术语如表11-2所示。
名称和术语 | 描述 | PHP的解析 |
---|---|---|
非限定名称 | 不包含前缀的类名称, 例如$u=new User(); | 如果当前命名空间是cn\ydma,User将被解析为cn\ydma\User。如果使用User的代码在公共空间中,则User会被解析为User |
限定名称 | 包含前缀的名称, 例如$u=new ydma\User(); | 如果当前的命名空间是cn,则User会被解析为cn\ydma\User。如果使用User的代码在公共空间中,则User会被解析为User |
完全限定名称 | 包含了全局前缀操作符的名称, 例如$u=new\ydma\User(); | 在这种情况下,User总是 |
其实可以把这三种名称类比为文件名(例如user.php)、相对路径名(例如./ydma/user.php)、绝对路径名(例如/cn/ydma/user.php),这样可能会更容易理解,示例代码如下所示:
//创建空间cn
namespace cn;
//当前空间下声明一个测试类User
class User{ }
//非限定名称,表示当前cn空间,这个调用将被解析成 cn\User();
$cn_User = new User();
//限定名称,表示相对于cn空间,类前面没有反斜杆\, 这个调用将被解析成 cn\ydma\User();
$ydma_User = new ydma\User();
//完全限定名称,表示绝对于cn空间,类前面有反斜杆\,这个调用将被解析成 cn\User();
$ydma_User = new \cn\User();
//完全限定名称,表示绝对于cn空间, 类前面有反斜杆\, 这个调用将被解析成 cn\ydma\User();
$ydma_User = new \cn\ydma\User();
//创建cn的子空间ydma
namespace cn\ydma;
class User { }
其实之前介绍的一直在使用非限定名称和完全限定名称,现在它们终于有名称了。
10.4.别名和导入
别名和导入可以看作调用命名空间元素的一种快捷方式。
允许通过别名引用或导入外部的完全限定名称,是命名空间的一个重要特征。
这有点类似于在Linux文件系统中可以创建对其他文件或目录的软链接。
PHP命名空间支持两种使用别名或导入的方式:为类名称使用别名,或为命名空间名称使用别名。
注意PHP不支持导入函数或常量。 在PHP中,别名是通过操作符use来实现的。下面是一个使用所有可能的导入方式的例子:
namespace cn\ydma; //声明命名空间为cn\ydma
class User { } //当前空间下声明一个类User
namespace broshop; //再创建一个broshop空间
use cn\ydma; //导入一个命名空间cn\ydma
$ydma_User = new ydma\User(); //导入命名空间后可使用限定名称调用元素
use cn\ydma as u; //为命名空间使用别名
$ydma_User = new u\User(); //使用别名代替空间名
use cn\ydma\User; //导入一个类
$ydma_User = new User(); //导入类后可使用非限定名称调用元素
use cn\ydma\User as CYUser; //为类使用别名
$ydma_User = new CYUser(); //使用别名代替空间名
需要注意一点,如果在用use进行导入的时候,当前空间有相同的名字元素,将会发生致命错误。示例如下所示:
//在cn\ydma空间中声明一个类User
namespace cn\ydma;
class User { }
//在broshop空间中声明两个类User和CYUser
namespace broshop;
class User { }
Class CYUser { }
//导入一个类
use cn\ydma\User;
//与当前空间的User发生冲突,程序产生致命错误
$ydma_User = new User();
//为类使用别名
use cn\ydma\User as CYUser;
//与当前空间的CYUser发生冲突,程序产生致命错误
$ydma_User = new CYUser();
除了使用别名和导入,还可以通过“namespace”关键字和“NAMESPACE”魔法常量动态地访问元素。其中namespace关键字表示当前空间,而魔法常量NAMESPACE的值是当前空间名称,NAMESPACE可以通过组合字符串的形式来动态调用,示例应用如下所示:
namespace cn\ydma;
const PATH = '/cn/ydma';
class User{ }
//namespace关键字表示当前空间/cn/ydma
echo namespace\PATH;
$User = new namespace\User();
//魔法常量__NAMESPACE__的值是当前空间名称cn\ydma
echo __NAMESPACE__;
//可以组合成字符串并调用
$User_class_name = __NAMESPACE__ . '\User';
$User = new $User_class_name();
上面的动态调用的例子中,字符串形式的动态调用方式,需要注意使用双引号的时候特殊字符可能被转义,例如在“NAMESPACE."\User"”中,“\U”在双引号字符串中会被转义。另外,PHP在编译脚本的时候就确定了元素所在的空间,以及导入的情况。而在解析脚本时字符串形式的调用只能认为是非限定名称和完全限定名称,而永远不可能是限定名称。