命名空间是一种封装事物的方法,在php中,可以看做是组织代码的一种形式。例如,在操作系统中用目录来把不同的文件分组,这样在不同的目录下可以存在相同文件名,目录就相当于“命名空间”。
命名空间解决的问题
1、用户编写的代码与PHP内部的类/函数/常量或第三方类/函数/常量之间的名字冲突。比如:用户定义了一个函数 is_numeric(),如果在代码中定义一个命名空间namespace user,用户在调用时加上命名空间\user\is_numeric()就可以调用自己的函数了。
2、为很长的标识符名称创建一个简短的别名,提高源代码的可读性。栗子:随着项目的扩大,类名可能会很长,例如A_B_C_D,如果使用命名空间,就可以这样用,在D的类里面定义命名空间:namespace \A_B_C_,在定义D的时候,类名就可以直接写D。
定义命名空间
用关键字namespace关键字声明,如果一个文件中包含命名空间,它必须在其他所有代码之前声明,declare关键字除外。同一个命名空间可以定义在多个文件中,即允许将同一个命名空间的内容分割存放在不同的文件中。
tips:如果用define()定义命名空间的常量,需要在变量名称前加上命名空间,栗子:define('test\HELLO', 'Hello world!');表示在命名空间test下定义常量HELLO,如果不加命名空间,表示在全局范围内定义常量。
在使用一个变量时,如果变量名前不添加任何命名空间,表示该变量是当前文件命名空间的变量;变量名前加\,表示全局范围的变量,栗子:
<pre>
namespace NS;
define(NAMESPACE .'\foo','111’); //在当前命名空间定义常量foo
define('foo','222’);//全局范围定义常量foo
echo foo; // 111,默认取当前命名空间的常量
echo \foo; // 222,取全局范围的常量
echo \NS\foo // 111. 去NS命名空间的常量
echo NS\foo // 报错,因为不是以\开头,表示取\NS\NS\foo,这个常量没有被定义。
</pre>
定义子命名空间
类似文件系统中的文件子目录,命名空间也可以分层,栗子:namespace A\B\C,子命名空间的好处只是更好的表达代码的层级关系,用法和普通的命名空间没有区别,和所谓的“父空间”并没有关系,\A\B\C和\A\B没有关系。
同一个文件中定义多个命名空间:
简单组合方式,在命名空间后直接写代码,栗子:
<pre>
namespace MyProject;
const CONNECT_OK = 1;
class Connection { }
function connect() { }
namespace AnotherProject;
const CONNECT_OK = 1;
class Connection { }
function connect() { }
</pre>
大括号方式,栗子:
<pre>
namespace MyProject {
const CONNECT_OK = 1;
class Connection { }
function connect() { }
}
namespace AnotherProject {
const CONNECT_OK = 1;
class Connection { }
function connect() { }
}
</pre>
使用命名空间
1、不包含前缀,例如 $a=new foo(); 或 foo::staticmethod();。如果当前命名空间是 currentnamespace,foo 将被解析为 currentnamespace\foo。
2、包含前缀: $a = new subnamespace\foo(); 或 subnamespace\foo::staticmethod();。如果当前的命名空间是 currentnamespace,则 foo 会被解析为 currentnamespace\subnamespace\foo。
3、包含全局前缀:例如, $a = new \currentnamespace\foo(); 或 \currentnamespace\foo::staticmethod();。
命名空间和动态语言的关系
php中有一些魔术方法,例如:METHOD、CLASS等,这些方法的输出结果和代码所在的位置有关,如果想用这些魔术方法输出类所在的命名空间,定义类的实例的时候要把命名空间前缀加上,否则不会打印出命名空间的信息。栗子:
<pre>
namespace namespacename;
class classname
{
function __construct()
{
echo METHOD,"\n";
}
}
function funcname()
{
echo FUNCTION,"\n";
}
const constname = "namespaced";
$a = 'classname';
$obj = new $a; //输出classname::__construct,因为定义的时候没有加上namespacename前缀
$b = 'funcname';
$b(); // 同理,只输出funcname
echo constant('constname'), "\n"; // 只输出global
$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"; //输出常量的时候不会输出命名空间信息
echo constant('namespacename\constname'), "\n"; //输出 namespaced
</pre>
namespace关键字
关键字 namespace 可用来显式访问当前命名空间或子命名空间中的元素。它等价于类中的 self 操作符。栗子:
namespace MyProject;//定义当前命名空间是MyProject
namespace\func(); // 调用函数MyProject\func()
__NAMESPACE__常量
常量NAMESPACE的值是包含当前命名空间名称的字符串。在全局的,不包括在任何命名空间中的代码,它包含一个空的字符串。
使用命名空间:别名/导入
如果我们想在当前文件引用另外一个文件的命名空间,而另外一个命名空间很长,如果每用一次那个空间类都要写很长的类名,这样就会使代码很难看,命名空间有个特征:允许通过别名引用或导入外部的完全限定名称,类似于类unix文件系统中可以创建对其他的文件或目录的软连接。
所有支持命名空间的PHP版本支持三种别名或导入方式:为类名称使用别名、为接口使用别名或为命名空间名称使用别名。PHP 5.6开始允许导入函数或常量或者为它们设置别名。
别名是通过操作符 use 来实现的,栗子:
<pre>
namespace foo;
use My\Full\Classname as Another; //引入My\Full\Classname并起个别名Another
use My\Full\NSname;//相当于use My\Full\NSname as 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
</pre>
后备全局函数/常量
在一个命名空间中,当 PHP 遇到一个非限定的类、函数或常量名称时,它使用不同的优先策略来解析该名称。类名称总是解析到当前命名空间中的名称。栗子:
<pre>
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 类
</pre>
对于函数和常量来说,如果当前命名空间中不存在该函数或常量,PHP 会退而使用全局空间中的函数或常量。
名称解析的规则
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() 的调用是这样解析的:在当前命名空间中查找名为 A\B\foo() 的函数尝试查找并调用 全局(global) 空间中的函数 foo()。
6、在命名空间(例如A\B)内部对非限定名称或限定名称类(非完全限定名称)的调用是在运行时解析的。下面是调用 new C() 及 new D\E() 的解析过程: new C()的解析:1、在当前命名空间中查找A\B\C类。2、尝试自动装载类A\B\C。