PHP 命名空间(namespace)详解

PHP 的命名空间(namespace)概念是从 5.3.0 开始引入的。在其它程序语言中,命名空间相当于文件夹,是用来对类文件进行分组的。许多 PHP 框架已经全面使用命名空间特性。

命名空间的引入,主要是为了解决类、函数、常量与 PHP 内部或者第三方库之间的名称冲突。

基础

语法主要是声明与使用。

声明:

// file: foo.php
namespace foo;
class Bar {}

使用:

// file: test.php
include 'foo.php';
$bar = new \foo\Bar();
// 使用别名
use foo as first;
$bar2 = new first\Bar();

声明必须放在其它代码之前,declare 关键字除外。

注意,在使用别名时,前面不能加反斜杠(因为反斜杠表示全局命名空间,而别名不是实际存在的命名空间)。但是引入进来的命名空间 foo,在使用时,必须加上正确的限定,确保命名空间的正确解析。在设置了别名后,原名称依然可用。

如果当前代码没有声明命名空间,则为全局命名空间(或者称为根空间)。

如果当前代码声明了命名空间,那么使用不带命名空间的类,会默认以当前命名空间作为限定。如果当前命名空间不存在该类,则会报错。

要使用引用文件中的类,且被引用的文件使用了命名空间,必须带上命名空间前缀。否则,使用的是当前命名空间下的类。

命名空间的名称,可以是多层级的(可称之为子命名空间),如 namespace foo\bar,使用时可以把它当作一个层级一样处理。

关键字 namespace 也可以出现在命名空间的“路径”中,放在“路径”的开头,指当前的命名空间,不管当前文件有没有声明命名空间。也就是说,在不是反斜杠开头的名称前加 namespace\ 总是正确的。

由于命名空间可能层次很多,书写麻烦,可以使用 use 语句设置别名,不加 as 则表示使用命名空间最后一节作为别名。

use My\Full\Classname;
use My\Full\Classname as C;

上一条语句与 use MyFullClassname as Classname 效果一样。下一条语句,是为了进一步简化别名。

注意,use 语句中的命名空间不需要以反斜杠开头,但在开头加上反斜杠也没有问题。如果 as 的别名与当前命名空间下类名相同(当然该同名类一定不会在当前文件中),将优先使用 as 别名对应的类。

导入函数(使用 function 关键字):

// PHP 5.6+
use function My\Full\functionName as func;

导入常量(使用 const 关键字):

// PHP 5.6+
use const My\Full\CONSTANT;

以上导入函数和常量的名称,如果与当前命名空间的名称冲突,可以用 as 取一个别名。

为了简化操作,可以将多个 use 语句写到一行:

use My\Full\Classname as Another, My\Full\NSname;

与其它语言不一样的地方是,同一个命名空间的代码可以定义在不同的文件中。

使用常量 __NAMESPACE__ 可以获得当前代码所在的命名空间。

进阶

一个文件多个命名空间

PHP 也允许在一个文件声明多个命名空间。

<?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() { /* ... */  }
}

namespace { // global code
session_start();
$a = MyProject\connect();
echo MyProject\Connection::start();
}

上面代码中,第三个 namespace 后没有命名空间名称,这表示是全局代码。混合在一个文件中的代码只能这样组织,命名空间的大括号外不能有任何 PHP 代码(declare 语句除外)。

命名空间的层次关系

名词定义:

  • 非限定名称(Unqualified name):名称中不包含命名空间分隔符(反斜杠)的标识符,例如 Bar
  • 限定名称(Qualified name):名称中含有命名空间分隔符(反斜杠)的标识符,例如 fooBar
  • 完全限定名称(Fully qualified name):名称中包含命名空间分隔符,并以命名空间分隔符开始的标识符,例如 fooBar。 namespaceBar 也是一个完全限定名称。

前面两种名称,没有以反斜杠开头,是相当于当前命名空间的名称。可以将 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 将被解析为 currentnamespacefoo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,则 foo 会被解析为foo。 注意:如果命名空间中的函数或常量未定义,则该非限定的函数名称或常量名称会被解析为全局函数名称或常量名称。(注意只是函数和常量,不包括类。类的访问行为与此不同,见下文。)
  2. 限定名称,或包含前缀的名称,例如 $a = new subnamespacefoo(); 或 subnamespacefoo::staticmethod();。如果当前的命名空间是 currentnamespace,则 foo 会被解析为 currentnamespacesubnamespacefoo。如果使用 foo 的代码是全局的,不包含在任何命名空间中的代码,foo 会被解析为subnamespacefoo。
  3. 完全限定名称,或包含了全局前缀操作符的名称,例如, $a = new currentnamespacefoo(); 或 currentnamespacefoo::staticmethod();。在这种情况下,foo 总是被解析为代码中的字面名(literal name)currentnamespacefoo。

全局命名空间

反斜杠()表示全局命名空间,使用 PHP 内部函数,始终可以使用反斜杠作为前缀。

访问任意全局类、函数或常量,都可以使用完全限定名称,例如 strlen() 或 Exception 或 INI_ALL。尤其是当命名空间内,存在与全局类、函数、常量相同的名称时,若想使用全局类、函数、常量,则必须加上反斜杠,否则使用的是当前命名空间下的类、函数、常量。

namespace A\B\C;

/* 这个函数是 A\B\C\fopen */
function fopen() { 
     /* ... */
     $f = \fopen(...); // 调用全局的fopen函数
     return $f;
}

在声明命名空间的代码中访问全局类(必须使用反斜杠)

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 会使用全局空间中的函数或常量。

注:本文部分内容转载自官方文档。

参考文档:
PHP 命名空间

标签: php

添加新评论