本教程适用于PHP 5 >= 5.3.0、PHP 7和PHP 8。
一、命名空间概述
什么是命名空间?在PHP中,命名空间是一种用于解决命名冲突和创建代码别名的方法。可以将其理解为将相关的类、函数和常量封装在一个命名空间中,以便在不同的上下文中进行区分和使用。
命名空间类似于操作系统中的目录结构,可以防止不同目录下的文件名冲突。例如,文件foo.txt可以同时存在于/home/greg和/home/other目录中,但在同一个目录中不能存在两个相同的foo.txt文件。而当从/home/greg目录外访问foo.txt文件时,需要在文件名之前加上目录名和目录分隔符,例如/home/greg/foo.txt。这个概念应用到程序设计中就是命名空间的概念。
在PHP中,命名空间解决了两类问题:
1、避免用户自定义的代码与PHP内部类、函数、常量或第三方类、函数、常量之间的命名冲突。
2、为长命名标识符创建简短的别名,提高源代码的可读性。
PHP 命名空间提供了一种将相关的类、函数和常量组合到一起的途径。下面是一个说明 PHP 命名空间语法的示例:
<?php namespace my\name; // 参考 "定义命名空间" 小节 class MyClass {} function myfunction() {} const MYCONST = 1; $a = new MyClass; $c = new \my\name\MyClass; $a = strlen('hi'); $d = namespace\MYCONST; $d = __NAMESPACE__ . '\MYCONST'; echo constant($d); ?>
注意:
- 命名空间名称大小写不敏感;
- 名为 PHP 的命名空间,以及以这些名字开头的命名空间 (例如 PHP\Classes)被保留用作语言内核使用, 而不应该在用户空间的代码中使用。
二、定义命名空间
虽然任意合法的 PHP 代码都可以包含在命名空间中,但只有以下类型的代码受命名空间的影响,它们是:类(包括抽象类和 trait)、接口、函数和常量。
命名空间通过关键字 namespace 来声明。如果一个文件中包含命名空间,它必须在其它所有代码之前声明命名空间,除了一个以外:declare关键字。
声明单个命名空间示例:
<?php namespace MyProject; const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } ?>
注意:完全限定名称(就是以反斜杠开头的名称)不能用于命名空间的声明。 因为该结构会解析成相对命名空间表达式。
在声明命名空间之前唯一合法的代码是用于定义源文件编码方式的 declare 语句。另外,所有非 PHP 代码包括空白符都不能出现在命名空间的声明之前:
<html> <?php namespace MyProject; // 致命错误 - 命名空间必须是程序脚本的第一条语句 ?>
另外,与 PHP 其它的语言特征不同,同一个命名空间可以定义在多个文件中,即允许将同一个命名空间的内容分割存放在不同的文件中。
三、定义子命名空间
与目录和文件的关系很象,PHP 命名空间也允许指定层次化的命名空间的名称。因此,命名空间的名字可以使用分层次的方式定义:
<?php namespace MyProject\Sub\Level; const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } ?>
上面的例子创建了常量 MyProject\Sub\Level\CONNECT_OK,类 MyProject\Sub\Level\Connection 和函数 MyProject\Sub\Level\connect。
四、同一文件定义多个命名空间
也可以在同一个文件中定义多个命名空间。在同一个文件中定义多个命名空间有两种语法形式。
1、简单组合语法(不推荐)
<?php namespace MyProject; const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } namespace AnotherProject; const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } ?>
2、大括号语法(推荐)
<?php namespace MyProject { const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } } namespace AnotherProject { const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } } ?>
在实际的编程实践中,非常不提倡在同一个文件中定义多个命名空间。这种方式的主要用于将多个 PHP 脚本合并在同一个文件中。
将全局的非命名空间中的代码与命名空间中的代码组合在一起,只能使用大括号形式的语法。全局代码必须用一个不带名称的 namespace 语句加上大括号括起来,例如:
定义多个命名空间和不包含在命名空间中的代码:
<?php namespace MyProject { const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } } namespace { // 全局代码 session_start(); $a = MyProject\connect(); echo MyProject\Connection::start(); } ?>
除了开始的 declare 语句外,命名空间的括号外不得有任何 PHP 代码。
定义多个命名空间和不包含在命名空间中的代码:
<?php declare(encoding='UTF-8'); namespace MyProject { const CONNECT_OK = 1; class Connection { /* ... */ } function connect() { /* ... */ } } namespace { // 全局代码 session_start(); $a = MyProject\connect(); echo MyProject\Connection::start(); } ?>
五、使用命名空间
在讨论如何使用命名空间之前,必须了解 PHP 是如何知道要使用哪一个命名空间中的元素的。可以将 PHP 命名空间与文件系统作一个简单的类比。在文件系统中访问一个文件有三种方式:
1、相对文件名形式如 foo.txt。它会被解析为 currentdirectory/foo.txt,其中 currentdirectory 表示当前目录。因此如果当前目录是 /home/foo,则该文件名被解析为 /home/foo/foo.txt。
2、相对路径名形式如 subdirectory/foo.txt。它会被解析为 currentdirectory/subdirectory/foo.txt。
3、绝对路径名形式如 /main/foo.txt。它会被解析为 /main/foo.txt。
PHP 命名空间中的元素使用同样的原理。例如,类名可以通过三种方式引用:
1、非限定名称,或不包含前缀的类名称,例如 $a=new foo(); 或 foo::staticmethod();。如果当前命名空间是 currentnamespace,foo 将被解析为 currentnamespace\foo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为 foo。 警告:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称。详情参见 使用命名空间:后备全局函数名称/常量名称。
2、限定名称,或包含前缀的名称,例如 $a = new subnamespace\foo(); 或 subnamespace\foo::staticmethod();。如果当前的命名空间是 currentnamespace,则 foo 会被解析为 currentnamespace\subnamespace\foo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,foo 会被解析为 subnamespace\foo。
3、完全限定名称,或包含了全局前缀操作符的名称,例如, $a = new \currentnamespace\foo(); 或 \currentnamespace\foo::staticmethod();。在这种情况下,foo 总是被解析为代码中的文字名(literal name)currentnamespace\foo。
下面是一个使用这三种方式的实例:
实例一:
<?php namespace Foo\Bar\subnamespace; const FOO = 1; function foo() {} class foo { static function staticmethod() {} } ?>
实例二:
<?php namespace Foo\Bar; include 'file1.php'; const FOO = 2; function foo() {} class foo { static function staticmethod() {} } /* 非限定名称 */ foo(); // 解析为函数 Foo\Bar\foo foo::staticmethod(); // 解析为类 Foo\Bar\foo 的静态方法 staticmethod echo FOO; // 解析为常量 Foo\Bar\FOO /* 限定名称 */ subnamespace\foo(); // 解析为函数 Foo\Bar\subnamespace\foo subnamespace\foo::staticmethod(); // 解析为类 Foo\Bar\subnamespace\foo, // 以及类的方法 staticmethod echo subnamespace\FOO; // 解析为常量 Foo\Bar\subnamespace\FOO /* 完全限定名称 */ \Foo\Bar\foo(); // 解析为函数 Foo\Bar\foo \Foo\Bar\foo::staticmethod(); // 解析为类 Foo\Bar\foo, 以及类的方法 staticmethod echo \Foo\Bar\FOO; // 解析为常量 Foo\Bar\FOO ?>
注意访问任意全局类、函数或常量,都可以使用完全限定名称,例如 \strlen() 或 \Exception 或 \INI_ALL。
实例三、在命名空间内部访问全局类、函数和常量:
<?php namespace Foo; function strlen() {} const INI_ALL = 3; class Exception {} $a = \strlen('hi'); // 调用全局函数strlen $b = \INI_ALL; // 访问全局常量 INI_ALL $c = new \Exception('error'); // 实例化全局类 Exception ?>
六、命名空间和动态语言特征
PHP 命名空间的实现受到其语言自身的动态特征的影响。因此,如果要将下面的代码转换到命名空间中:
1、动态访问元素
<?php class classname { function __construct() { echo __METHOD__,"\n"; } } function funcname() { echo __FUNCTION__,"\n"; } const constname = "global"; $a = 'classname'; $obj = new $a; // 输出 classname::__construct $b = 'funcname'; $b(); // 输出 funcname echo constant('constname'), "\n"; // 输出 global ?>
必须使用完全限定名称(包括命名空间前缀的类名称)。注意因为在动态的类名称、函数名称或常量名称中,限定名称和完全限定名称没有区别,因此其前导的反斜杠是不必要的。
2、动态访问命名空间的元素
<?php namespace namespacename; class classname { function __construct() { echo __METHOD__,"\n"; } } function funcname() { echo __FUNCTION__,"\n"; } const constname = "namespaced"; /* 注意,如果使用双引号,要这样写 "\\namespacename\\classname" */ $a = '\namespacename\classname'; $obj = new $a; // 输出 namespacename\classname::__construct $a = 'namespacename\classname'; $obj = new $a; // 也会输出 namespacename\classname::__construct $b = 'namespacename\funcname'; $b(); // 输出 namespacename\funcname $b = '\namespacename\funcname'; $b(); // 也会输出 namespacename\funcname echo constant('\namespacename\constname'), "\n"; // 输出 namespaced echo constant('namespacename\constname'), "\n"; // 也会输出 namespaced ?>
七、namespace和__NAMESPACE__
PHP支持两种抽象的访问当前命名空间内部元素的方法,__NAMESPACE__ 魔术常量和 namespace 关键字。
常量 __NAMESPACE__ 的值是包含当前命名空间名称的字符串。在全局的,不包括在任何命名空间中的代码,它包含一个空的字符串。
__NAMESPACE__ 示例, 在命名空间中的代码:
<?php namespace MyProject; echo '"', __NAMESPACE__, '"'; // 输出 "MyProject" ?>
__NAMESPACE__ 示例,全局代码:
<?php echo '"', __NAMESPACE__, '"'; // 输出 "" ?>
常量 __NAMESPACE__ 在动态创建名称时很有用,例如:
使用 __NAMESPACE__ 动态创建名称:
<?php namespace MyProject; function get($classname) { $a = __NAMESPACE__ . '\\' . $classname; return new $a; } ?>
关键字 namespace 可用来显式访问当前命名空间或子命名空间中的元素。它等价于类中的 self 操作符。
namespace 操作符,命名空间中的代码:
<?php namespace MyProject; use blah\blah as mine; // 参考 "使用命名空间:别名/导入" blah\mine(); // 调用函数 MyProject\blah\mine() namespace\blah\mine(); // 调用函数 MyProject\blah\mine() namespace\func(); // 调用函数 MyProject\func() namespace\sub\func(); // 调用函数 MyProject\sub\func() namespace\cname::method(); // 调用 class MyProject\cname 的静态方法 "method" $a = new namespace\sub\cname(); // class MyProject\sub\cname 的实例对象 $b = namespace\CONSTANT; // 设置 $b 的值为常量 MyProject\CONSTANT ?>
namespace 操作符, 全局代码:
<?php namespace\func(); // 调用函数 func() namespace\sub\func(); // 调用函数 sub\func() namespace\cname::method(); // 调用 class cname 的静态方法 "method" $a = new namespace\sub\cname(); // class sub\cname 的实例对象 $b = namespace\CONSTANT; // 设置 $b 的值为常量 CONSTANT ?>
八、使用命名空间:别名/导入
参见《PHP别名/导入》
九、全局空间
如果没有定义任何命名空间,所有的类与函数的定义都是在全局空间,与 PHP 引入命名空间概念前一样。在名称前加上前缀 \ 表示该名称是全局空间中的名称,即使该名称位于其它的命名空间中时也是如此。
<?php namespace A\B\C; /* 这个函数是 A\B\C\fopen */ function fopen() { /* ... */ $f = \fopen(...); // 调用全局的fopen函数 return $f; } ?>
十、后备全局函数/常量
在一个命名空间中,当 PHP 遇到一个非限定的类、函数或常量名称时,它使用不同的优先策略来解析该名称。类名称总是解析到当前命名空间中的名称。因此在访问系统内部或不包含在命名空间中的类名称时,必须使用完全限定名称,例如:
1、在命名空间中访问全局类
<?php namespace A\B\C; class Exception extends \Exception {} $a = new Exception('hi'); // $a 是类 A\B\C\Exception 的一个对象 $b = new \Exception('hi'); // $b 是类 Exception 的一个对象 $c = new ArrayObject; // 致命错误, 找不到 A\B\C\ArrayObject 类 ?>
对于函数和常量来说,如果当前命名空间中不存在该函数或常量,PHP 会退而使用全局空间中的函数或常量。
2、命名空间中后备的全局函数/常量
<?php namespace A\B\C; const E_ERROR = 45; function strlen($str) { return \strlen($str) - 1; } echo E_ERROR, "\n"; // 输出 "45" echo INI_ALL, "\n"; // 输出 "7" - 使用全局常量 INI_ALL echo strlen('hi'), "\n"; // 输出 "1" if (is_array('hi')) { // 输出 "is not array" echo "is array\n"; } else { echo "is not array\n"; } ?>
十一、名称解析规则
参见《PHP名称解析规则》
十二、常见问题
参见《PHP命名空间常见问题》