什么是命名空间?从广义上来说,命名空间是一种封装事物的方法,在很多地方都可以见到这种抽象概念。例如,在操作系统中目录用来将相关文件分组,对于目录中的文件来说,它就扮演了命名空间的角色。
举个简单的例子,文件 foo.txt 可以同时在目录 /home/greg 和 /home/other 中存在,但在同一个目录中不能存在两个 foo.txt 文件。另外,在目录 /home/greg 外访问 foo.txt 文件时,我们必须将目录名以及目录分隔符放在文件名之前,例如 /home/greg/foo.txt。这个原理应用到程序设计领域就是命名空间的概念。
PHP 中命名空间(namespace)是在 PHP5.3 中加入的,在 PHP 当中有相当重要的分量。
PHP 命名空间可以解决以下两类问题:
用户编写的代码与 PHP 内部的类/函数/常量或第三方类/函数/常量之间的命名冲突;
为很长的标识符名称(通常是为了缓解第一类问题而定义的)创建一个别名(或简短)的名称,以提高源代码的可读性。
就像一个文件夹当中,不能同时出现两个dianying.txt,通过新建一个文件夹,在两个文件夹中就可以各存放一个dianying.txt.命名空间就是解决类似这样的命名冲突的问题的.
名空间的定义需要通过关键字 namespace 来声明,语法格式如下:
namespace 命名空间名;
注意:命名空间的作用范围是 : 四大全局成员{类(包括抽象类和 traits)、接口、函数和常量等类型的代码受命名空间的影响}。
【示例】下面演示一下如何定义了命名空间:
//demo1.php
<?php
namespace one;
function demo(){
echo '我是one空间中的函数'."<br>";
}
demo();
注意:
a.在声明命名空间之前除了用于定义源文件编码方式的 declare 语句外,所有非 PHP 代码(包括空白符)都不能出现在命名空间声明之前。
b. PHP 其它的语言特征不同,同一个命名空间可以定义在多个文件中,即允许将同一个命名空间的内容分割存放在不同的文件中。
(一).在同一个文件中定义多个命名空间
在一个文件中也可以定义多个命名空间,在同一文件中定义多个命名空间有两种语法形式,下面通过示例演示一下:
1.定义多个命名空间——简单组合语法。 用分号”;”结尾
namespace one;
function demo(){
echo '我是one空间中的函数'."<br>";
}
demo(); //输出 我是one空间中的函数
namespace two;
function demo(){
echo '我是two空间中的函数'."<br>";
}
// 调用当前命名空间的方法或函数
demo(); // 输出 我是two空间中的函数
// 调用其他空间的方法或函数
\one\demo(); //输出 我是one空间中的函数
2.【示例】定义多个命名空间——大括号{}语法。
//demo1.php文件
namespace{ //namespace{}定义全局命名空间
function demo(){
echo '我是 全局 空间中的函数'."<br>";
}
echo demo();
}
namespace one{ //one命名空间
function demo(){
echo '我是one空间中的函数'."<br>";
}
echo demo();
}
namespace two{ //two命名空间
function demo(){
echo '我是two空间中的函数'."<br>";
}
// 调用当前命名空间的方法或函数
demo();
}
//这里以下是命名空间之外,不能在添加任何方法,函数常量和类
class asd(){
echo "asd";
}
const ('a','asdasdasd');
在实际的编程实践中,并不提倡在同一个文件中定义多个命名空间。定义多个命名空间主要用于将多个 PHP 脚本合并在同一个文件中。在定义多个命名空间时建议使用大括号形式的语法。
将全局的非命名空间中的代码与命名空间中的代码组合在一起,只能使用大括号形式的语法,同时全局代码必须用一个不带名称的 namespace 语句加上大括号括起来,如上示例所示.
当在一个文件中声明多个命名空间,且声明了全局空间时,命名空间之外的所有方法,函数,常量和类必须放在全局命名空间当中
在讨论如何使用命名空间之前,必须了解 PHP 是如何知道要使用哪一个命名空间中的元素的。我们可以将 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 总是被解析为代码中的文字名 currentnamespace\foo。
警告:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称。
以下以示例进行相关的解释:我们需要两个 PHP 源文件,分别是 demo.php 和 index.php,示例代码如下:
//文件名称:demo.php
<?php
namespace Foo\Bar\Demo;
const FOO = 1;
function foo() {}
class foo
{
public function demo() {
echo '命名空间为:Foo\Bar\Demo';
}
}
?>
//文件名称:index.php
<?php
namespace Foo\Bar;
include 'Demo.php';
const FOO = 2;
function foo() {
echo 'Foo\Bar 命名空间下的 foo 函数<br>';
}
class foo
{
static function demo(){
echo '命名空间为:Foo\Bar<br>';
}
}
/* 非限定名称 */
foo(); // 解析为 Foo\Bar\foo resolves to function Foo\Bar\foo
foo::demo(); // 解析为类 Foo\Bar\foo 的静态方法 staticmethod。
echo FOO.'<br>'; // 解析为常量 Foo\Bar\FOO
/* 限定名称 */
Demo\foo(); // 解析为函数 Foo\Bar\Demo\foo
Demo\foo::demo(); // 解析为类 Foo\Bar\Demo\foo,
// 以及类的方法 demo
echo Demo\FOO.'<br>'; // 解析为常量 Foo\Bar\Demo\FOO
/* 完全限定名称 */
\Foo\Bar\foo(); // 解析为函数 Foo\Bar\foo
\Foo\Bar\foo::demo(); // 解析为类 Foo\Bar\foo, 以及类的方法 demo
echo \Foo\Bar\FOO.'<br>'; // 解析为常量 Foo\Bar\FOO
?>
输出结果:
Foo\Bar 命名空间下的 foo 函数
命名空间为:Foo\Bar
2
Foo\Bar\Demo 命名空间下的 foo 函数
命名空间为:Foo\Bar\Demo
1
Foo\Bar 命名空间下的 foo 函数
命名空间为:Foo\Bar
2
注意:访问任意全局类、函数或常量,都可以使用完全限定名称,例如 \strlen() 或 \Exception 等。
PHP 允许通过别名引用或导入的方式来使用外部的命名空间,这是命名空间的一个重要特征。这有点类似于在类 unix 文件系统中可以创建对其它的文件或目录的符号连接。
use在命名空间中的作用
use namespace;
在 PHP 中,别名是通过操作符 use 与 as 来实现的,语法格式如下:
use 命名空间 as 别名;
【示例】使用 use 操作符导入和使用别名。
<?php
namespace foo;
use My\Full\Classname as Another;
// 下面的例子与 use My\Full\NSname as NSname 相同
use My\Full\NSname;
// 导入一个全局类
use ArrayObject;
// 导入一个函数
use function My\Full\functionName;
// 导入一个函数并定义别名
use function My\Full\functionName as func;
// 导入一个常量
use const My\Full\CONSTANT;
$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 对象
func(); // 调用 My\Full\functionName 函数
echo CONSTANT; // 打印 My\Full\CONSTANT 常量
?>
注意:对命名空间中的名称(包含命名空间分隔符的完全限定名称,如 Foo\Bar ,以及相对的不包含命名空间分
隔符的全局名称,如 FooBar)来说,前导的反斜杠是不必要的也是不推荐的,因为导入的名称必须是完全限定的,
不会根据当前的命名空间作相对解析。
为了简化操作,PHP 还支持在一行中导入多个命名空间,中间使用’,’隔开,示例代码如下:
<?php
use My\Full\Classname as Another, My\Full\NSname;
$obj = new Another; // 实例化 My\Full\Classname 对象
NSname\subns\func(); // 调用 My\Full\NSname\subns\func 函数
?>
导入操作是编译执行的,但动态的类名称、函数名称或常量名称则不是。
<?php
use My\Full\Classname as Another, My\Full\NSname;
$obj = new Another; // 实例化一个 My\Full\Classname 对象
$a = 'Another';
$obj = new $a; // 实际化一个 Another 对象
?>
另外,导入操作只影响非限定名称和限定名称。完全限定名称由于是确定的,故不受导入的影响。
在使用命名空间时,如果遇到重名的方法,类名或者常量,他们的优先级顺序是: 子空间>公共(全局)空间
示例:以打印输出函数 var_dump()为例:
function var_dump($name){
echo 'hi dudu '.$name;
}
class Test{
const APP_NAME='taobao';
}
var_dump ('David'); //正常输出:string(5)('David')
var_dump ('David'); //这里输出 hi dudu David
PHP 支持两种抽象的访问当前命名空间内部元素的方法,NAMESPACE 魔术常量和 namespace 关键字。
NAMESPACE 常量的值是包含当前命名空间名称的字符串。在全局的,不包括在任何命名空间中的代码,它是一个空的字符串。示例代码如下:
<?php
namespace App\Controller\Home;
echo __NAMESPACE__;
?>
运行结果如下:
App\Controller\Home
namespace 关键字可用来显式访问当前命名空间或子命名空间中的元素,它等价于类中的 self 操作符。示例代码如下:
<?php
namespace MyProject;
use Foo\Bar\Demo as demo; // 导入 Foo\Bar\Demo 命名空间
blah\mine(); // 调用 MyProject\blah\mine() 函数
namespace\blah\mine(); // 调用 MyProject\blah\mine() 函数
namespace\func(); // 调用 MyProject\func() 函数
namespace\sub\func(); // 调用 MyProject\sub\func() 函数
namespace\cname::method(); // 调用 MyProject\cname 类的静态方法 method
$a = new namespace\sub\cname(); // 实例化 MyProject\sub\cname 对象
$b = namespace\CONSTANT; // 将常量 MyProject\CONSTANT 的值赋给 $b
在说明名称解析规则之前,我们先来看看命名空间名称的定义:
非限定名称:名称中不包含命名空间分隔符的标识符,例如 Foo;
限定名称:名称中含有命名空间分隔符的标识符,例如 Foo\Bar;
完全限定名称:名称中包含命名空间分隔符,并以命名空间分隔符开始的标识符,例如 \Foo\Bar。namespace\Foo 也是一个完全限定名称。
名称解析遵循下列规则:
对完全限定名称的函数,类和常量的调用在编译时解析。例如 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() 的函数;
尝试查找并调用全局空间中的 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()。