PHP新特性之:命名空间(Namespace)


从php5.3.0之后,php开始支持命名空间了。
之前在一些其他语言的资料里面(比如JS),接触过命名空间的概念,有了初步的印象。当时只是觉得就是一个作用域的规范。如今php也开始支持命名空间了,那么就来仔细的学习下吧!
看了几个博客说的都不是很明白,最后还是看php官网上面的文档说明(PHP:命名空间概述),豁然开朗啊!

“什么是命名空间?从广义上来说,命名空间是一种封装事物的方法。在很多地方都可以见到这种抽象概念。例如,在操作系统中目录用来将相关文件分组,对于目录中的文件来说,它就扮演了命名空间的角色。具体举个例子,文件 foo.txt 可以同时在目录/home/greg 和 /home/other 中存在,但在同一个目录中不能存在两个 foo.txt 文件。另外,在目录 /home/greg 外访问 foo.txt 文件时,我们必须将目录名以及目录分隔符放在文件名之前得到 /home/greg/foo.txt。这个原理应用到程序设计领域就是命名空间的概念。”

这是官网在介绍命名空间的第一段话。已经说的很简单明了了。

还记得在之前的php设计模式的博客中我提到的一个快速理解和学习的方法么?对!就是把新的知识和已熟悉和掌握的事物之间建立一个类比模型!

那按照这个方法,上面的这段介绍命名空间的话就很好理解了:我们可以把整个项目的PHP 代码比作一台电脑(当然,是已经装好操作系统的……),而命名空间的作用就相当于操作系统对电脑做的分区。然后可以把每个命名空间理解为新建了一个盘(就是win里面的C、D、E...盘),而每个同名的类、常量或者函数就相当于一个文件。这样在后面理解命名空间的一些特性时,就可以很容易类似理解了。

举个栗子:在PHP没有命名空间之前,相当于系统没有对电脑做任何分区,所有的代码都是放在一个盘里面。这时候如果定义了两个同名的类(比如类名为xskyer)就会冲突,就相当于在电脑里面创建了2个同名的文件会提示冲突对不对?现在有了命名空间之后,我们就可以设置两个命名空间,在每个命名空间里面分别创建xskyer类就不会冲突了。这就相当于电脑分成D、E两个盘,而在每个盘里面都有一个xskyer文件,就不会冲突了。

怎么样,这样是不是很容易理解啦?:)

不过呢,拿win系统来类比命名空间是不太严格的,如果你对linux系统比较熟悉的话,那把linux系统当做命名空间的模型会更合适!

什么?为什么?很好!如果你有这个疑问的话,说明你是在思考着学习:,那接着往下看吧,相信看完了,你就会找到问题的答案! 🙂

一、定义命名空间

在PHP中,任何的PHP代码都可以包含在命名空间中,但只有三种类型的代码受命名空间的影响,它们是:类,函数和常量。命名空间通过关键字namespace?来声明。如果一个文件中包含命名空间,它必须在其它所有代码之前声明命名空间。在声明命名空间之前唯一合法的代码是用于定义源文件编码方式的?declare?语句。


所有非 PHP 代码包括空白符都不能出现在命名空间的声明之前。
另外,与PHP其它的语言特征不同,同一个命名空间可以定义在多个文件中,即允许将同一个命名空间的内容分割存放在不同的文件中。

与目录和文件的关系很象,PHP 命名空间也允许指定层次化的命名空间的名称,即子命名空间。因此,命名空间的名字可以使用分层次的方式定义:


上面的例子创建了常量MyProject\Sub\Level\CONNECT_OK,类?MyProject\Sub\Level\Connection和函数MyProject\Sub\Level\Connection
另外,同一个文件中也可以定义多个命名空间。比如:


二、命名空间的使用

对于命名空间的使用,可以与文件系统作一个简单的类比。在文件系统中访问一个文件有三种方式:

  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。

允许通过别名引用或导入外部的完全限定名称,是命名空间的一个重要特征。这有点类似于在类 unix 文件系统中可以创建对其它的文件或目录的符号连接。

PHP 命名空间支持 有两种使用别名或导入方式:为类名称使用别名,或为命名空间名称使用别名。注意PHP不支持导入函数或常量。

在PHP中,别名是通过操作符?use?来实现的.


全局空间:

如果没有定义任何命名空间,所有的类与函数的定义都是在全局空间,与 PHP 引入命名空间概念前一样。在名称前加上前缀?\?表示该名称是全局空间中的名称,即使该名称位于其它的命名空间中时也是如此。


namespace关键字和__NAMESPACE__常量

PHP支持两种抽象的访问当前命名空间内部元素的方法,__NAMESPACE__?魔术常量和namespace关键字。常量__NAMESPACE__的值是包含当前命名空间名称的字符串。在全局的,不包括在任何命名空间中的代码,它包含一个空的字符串。

三、名称解析规则

在说明名称解析规则之前,我们先看一些重要的定义:

命名空间名称定义

非限定名称Unqualified name
名称中不包含命名空间分隔符的标识符,例如?Foo
限定名称Qualified name
名称中含有命名空间分隔符的标识符,例如?Foo\Bar
完全限定名称Fully qualified name
名称中包含命名空间分隔符,并以命名空间分隔符开始的标识符,例如?\Foo\Bar。?namespace\Foo?也是一个完全限定名称。

名称解析遵循下列规则:(类比操作系统的绝对路径和相对路径将很容易理解)

  1. 对完全限定名称的函数,类和常量的调用在编译时解析。例如?new \A\B?解析为类?A\B。
  2. 所有的非限定名称和限定名称(非完全限定名称)根据当前的导入规则在编译时进行转换。例如,如果命名空间?A\B\C?被导入为?C,那么对?C\D\e()?的调用就会被转换为?A\B\C\D\e()。
  3. 在命名空间内部,所有的没有根据导入规则转换的限定名称均会在其前面加上当前的命名空间名称。例如,在命名空间?A\B?内部调用?C\D\e(),则?C\D\e()?会被转换为?A\B\C\D\e()?。
  4. 非限定类名根据当前的导入规则在编译时转换(用全名代替短的导入名称)。例如,如果命名空间?A\B\C?导入为C,则?new C()?被转换为?new A\B\C()?。
  5. 在命名空间内部(例如A\B),对非限定名称的函数调用是在运行时解析的。例如对函数?foo()?的调用是这样解析的:
    1. 在当前命名空间中查找名为?A\B\foo()?的函数
    2. 尝试查找并调用?全局(global)?空间中的函数?foo()。
  6. 在命名空间(例如A\B)内部对非限定名称或限定名称类(非完全限定名称)的调用是在运行时解析的。下面是调用?new C()?及?new D\E()?的解析过程:?new C()的解析:
    1. 在当前命名空间中查找A\B\C类。
    2. 尝试自动装载类A\B\C。

    new D\E()的解析:

    1. 在类名称前面加上当前命名空间名称变成:A\B\D\E,然后查找该类。
    2. 尝试自动装载类?A\B\D\E。

    为了引用全局命名空间中的全局类,必须使用完全限定名称?new \C()。


世界不可能那么远