PHP 命名空间使用 ( namespace )
使用 namespace 关键字声明了命名空间后,我们就可以使用命名空间中的类、函数、常量
PHP 命名空间中的类名可以通过三种方式引用:
-
非限定名称,或不包含前缀的类名称,
例如
$a=new foo();
或
foo::staticmethod();
如果当前命名空间是
currentnamespace
,foo 将被解析为currentnamespace\foo
如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为
foo
警告:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称
-
限定名称或包含前缀的名称
例如
$a = new subnamespace\foo();
或
subnamespace\foo::staticmethod();
如果当前的命名空间是 currentnamespace,则 foo 会被解析为
currentnamespace\subnamespace\foo
如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,foo 会被解析为
subnamespace\foo
-
完全限定名称或包含了全局前缀操作符的名称
例如
$a = new \currentnamespace\foo();
或
\currentnamespace\foo::staticmethod();
在这种情况下,foo 总是被解析为代码中的文字名(literal name)
currentnamespace\foo
范例
demo_namespace_use1.php
<?php namespace Foo\Bar\subnamespace; const FOO = 1; function foo() {} class foo { static function staticmethod() {} }
demo_namespace_use2.php
<?php namespace Foo\Bar; include 'demo_namespace_use1.php'; const FOO = 2; function foo() {} class foo { static function staticmethod() {} } /* 非限定名称 */ // 解析为 Foo\Bar\foo foo(); // 解析为类 Foo\Bar\foo 的静态方法 staticmethod foo::staticmethod(); // 解析为 Foo\Bar\FOO 常量 echo FOO; /* 限定名称 */ // 解析为函数 Foo\Bar\subnamespace\foo subnamespace\foo(); // 解析为类 Foo\Bar\subnamespace\foo, 的静态方法 staticmethod subnamespace\foo::staticmethod(); // 解析为常量 Foo\Bar\subnamespace\FOO echo subnamespace\FOO; /* 完全限定名称 */ // 解析为函数 Foo\Bar\foo \Foo\Bar\foo(); // 解析为类 Foo\Bar\foo 的静态方法 staticmethod \Foo\Bar\foo::staticmethod(); // 解析为常量 Foo\Bar\FOO echo \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 命名空间的实现受到其语言自身的动态特征的影响
所以,如果要将下面的代码转换到命名空间中,动态访问元素
demo_namespace_dynamic.php
<?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
必须使用完全限定名称(包括命名空间前缀的类名称)
注意: 因为在动态的类名称、函数名称或常量名称中,限定名称和完全限定名称没有区别,因此其前导的反斜杠是不必要的
动态访问命名空间的元素
<?php namespace namespacename; class classname { function __construct() { echo __METHOD__,"\n"; } } function funcname() { echo __FUNCTION__,"\n"; } const constname = "namespaced"; include 'example1.php'; $a = 'classname'; $obj = new $a; // 输出 classname::__construct $b = 'funcname'; $b(); // 输出 funcname echo constant('constname'), "\n"; //输出 global /* 如果使用双引号,则必须如下格式 "\\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(); // 输出 prints namespacename\funcname echo constant('\namespacename\constname'), "\n"; // 输出 namespaced echo constant('namespacename\constname'), "\n"; // 输出 namespaced
namespace 关键字和 __NAMESPACE__ 常量
PHP支持两种抽象的访问当前命名空间内部元素的方法
- __NAMESPACE__ 魔术常量
- 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
关键字 namespace 可用来显式访问当前命名空间或子命名空间中的元素
它等价于类中的 self 操作符
范例
namespace 操作符,命名空间中的代码
<?php namespace MyProject; use blah\blah as mine; blah\mine(); // 调用函数 blah\blah\mine() namespace\blah\mine(); // 调用函数 MyProject\blah\mine() namespace\func(); // 调用函数 MyProject\func() namespace\sub\func(); // 调用函数 MyProject\sub\func() namespace\cname::method(); // 调用静态方法 MyProject\cname $a = new namespace\sub\cname(); // 实例化类 MyProject\sub\cname $b = namespace\CONSTANT; // 将常量 MyProject\CONSTANT 赋值给 $b
namespace 操作符, 全局代码
<?php namespace\func(); // 调用函数 func() namespace\sub\func(); // 调用函数 sub\func() namespace\cname::method(); // 调用 cname 类静态方法 method $a = new namespace\sub\cname(); // 实例化类 sub\cname $b = namespace\CONSTANT; // 将常量 CONSTANT 赋值给 $b
使用命名空间:别名/导入
PHP 命名空间支持 有两种使用别名或导入方式:
- 为类名称使用别名
- 为命名空间名称使用别名
注意 PHP 不支持导入函数或常量
PHP 语言中, 别名是通过操作符 use 来实现
范例
下面是一个使用所有可能的三种导入方式例子:
1. 使用 use 关键字导入/使用别名
<?php namespace foo; use My\Full\Classname as Another; // 下面的例子与 use My\Full\NSname as NSname 相同 use My\Full\NSname; // 导入一个全局类 use \ArrayObject; $obj = new namespace\Another; // 实例化 foo\Another 对象 $obj = new Another; // 实例化 My\Full\Classname 对象 NSname\subns\func(); // 调用函数 My\Full\NSname\subns\func $a = new ArrayObject(array(1)); // 实例化 ArrayObject 对象 // 如果不使用 "use \ArrayObject" ,则实例化一个 foo\ArrayObject 对象
2. 一行中包含多个 use 语句
<?php use My\Full\Classname as Another, My\Full\NSname; $obj = new Another; // 实例化 My\Full\Classname 对象 NSname\subns\func(); // 调用函数 My\Full\NSname\subns\func
导入操作是在编译执行的,但动态的类名称、函数名称或常量名称则不是
3. 导入和动态名称
<?php use My\Full\Classname as Another, My\Full\NSname; $obj = new Another; // 实例化一个 My\Full\Classname 对象 $a = 'Another'; $obj = new $a; // 实际化一个 Another 对象 ?>
导入操作只影响非限定名称和限定名称
完全限定名称由于是确定的,故不受导入的影响
4. 导入和完全限定名称
<?php use My\Full\Classname as Another, My\Full\NSname; $obj = new Another; //实例化类 My\Full\Classname $obj = new \Another; //实例化类 Another $obj = new Another\thing; //实例化类 My\Full\Classname\thing $obj = new \Another\thing; //实例化类 Another\thing
使用命名空间:后备全局函数/常量
在一个命名空间中,当 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 namespace A\B\C; /* 这个函数是 A\B\C\fopen */ function fopen() { /* ... */ $f = \fopen(...); // 调用全局的fopen函数 return $f; }
命名空间的顺序
PHP 5.3.0 引入命名空间后,最容易犯的错误就是使用类的时候,不知道这个类的查找路径
比如下面的代码
<?php namespace A; use B\D, C\E as F; // 函数调用 foo(); // 首先尝试调用定义在命名空间"A"中的函数foo() // 再尝试调用全局函数 "foo" \foo(); // 调用全局空间函数 "foo" my\foo(); // 调用定义在命名空间"A\my"中函数 "foo" F(); // 首先尝试调用定义在命名空间"A"中的函数 "F" // 再尝试调用全局函数 "F" // 类引用 new B(); // 创建命名空间 "A" 中定义的类 "B" 的一个对象 // 如果未找到,则尝试自动装载类 "A\B" new D(); // 使用导入规则,创建命名空间 "B" 中定义的类 "D" 的一个对象 // 如果未找到,则尝试自动装载类 "B\D" new F(); // 使用导入规则,创建命名空间 "C" 中定义的类 "E" 的一个对象 // 如果未找到,则尝试自动装载类 "C\E" new \B(); // 创建定义在全局空间中的类 "B" 的一个对象 // 如果未发现,则尝试自动装载类 "B" new \D(); // 创建定义在全局空间中的类 "D" 的一个对象 // 如果未发现,则尝试自动装载类 "D" new \F(); // 创建定义在全局空间中的类 "F" 的一个对象 // 如果未发现,则尝试自动装载类 "F" // 调用另一个命名空间中的静态方法或命名空间函数 B\foo(); // 调用命名空间 "A\B" 中函数 "foo" B::foo(); // 调用命名空间 "A" 中定义的类 "B" 的 "foo" 方法 // 如果未找到类 "A\B" ,则尝试自动装载类 "A\B" D::foo(); // 使用导入规则,调用命名空间 "B" 中定义的类 "D" 的 "foo" 方法 // 如果类 "B\D" 未找到,则尝试自动装载类 "B\D" \B\foo(); // 调用命名空间 "B" 中的函数 "foo" \B::foo(); // 调用全局空间中的类 "B" 的 "foo" 方法 // 如果类 "B" 未找到,则尝试自动装载类 "B" // 当前命名空间中的静态方法或函数 A\B::foo(); // 调用命名空间 "A\A" 中定义的类 "B" 的 "foo" 方法 // 如果类 "A\A\B" 未找到,则尝试自动装载类 "A\A\B" \A\B::foo(); // 调用命名空间 "A\B" 中定义的类 "B" 的 "foo" 方法 // 如果类 "A\B" 未找到,则尝试自动装载类 "A\B" ?>
命名空间名称解析规则如下
-
对完全限定名称的函数,类和常量的调用在编译时解析
例如 new \A\B 解析为类 A\B
-
所有的非限定名称和限定名称(非完全限定名称)根据当前的导入规则在编译时进行转换
例如,如果命名空间 A\B\C 被导入为 C ,那么对 C\D\e() 的调用就会被转换为 A\B\C\D\e()
-
在命名空间内部,所有的没有根据导入规则转换的限定名称均会在其前面加上当前的命名空间名称
例如,在命名空间 A\B 内部调用 C\D\e() ,则 C\D\e() 会被转换为 A\B\C\D\e()
-
非限定类名根据当前的导入规则在编译时转换(用全名代替短的导入名称)
例如,如果命名空间 A\B\C 导入为C,则 new C() 被转换为 new A\B\C()
-
在命名空间内部(例如A\B),对非限定名称的函数调用是在运行时解析的
例如对函数 foo() 的调用是这样解析的:
- 在当前命名空间中查找名为 A\B\foo() 的函数
- 尝试查找并调用 全局(global) 空间中的函数 foo()
-
在命名空间(例如 A\B )内部对非限定名称或限定名称类(非完全限定名称)的调用是在运行时解析的
下面是调用 new C() 及 new D\E() 的解析过程:
new C() 的解析:
- 在当前命名空间中查找 A\B\C 类
- 尝试自动装载类 A\B\C
new D\E() 的解析:
- 在类名称前面加上当前命名空间名称变成: A\B\D\E ,然后查找该类
- 尝试自动装载类 A\B\D\E
为了引用全局命名空间中的全局类,必须使用完全限定名称 new \C() 。