命名空间学习笔记

时间:2022-09-24 14:28:19

        断断续续看了命名空间相关知识点,由于工作关系老是没有接触相关的代码,因此过了一段时间就忘了,距离上次关于命名空间的博客过了九个月,新版的ThinkPHP3.2版本就加上了命名空间,去年看的symfony2框架也用到了,这次是要下定决心好好整了。


        命名空间是php3.0版本后加上去的,因此使用它php版本需得在3.0及以上,其关键字是namespace,在声明命名空间之前,唯一合法的语句是declare,写入其他的语句会报错。若是在脚本内声明了命名空间,受其影响的只有常量、函数、类三种类型。与其他语言不同的是,同一个命名空间可以定义在多个文件中,看过ThinkPHP3.2源码的朋友应该深有体会。下面是命名空间常用的的几种写法。


        写法一、*命名空间

namespace script

        写法二、子命名空间

namespace script\subScript

        写法三、同一个文件中定义多个命名空间,该写法不常用,知道就好

namespace myScriptOne {
function test() {}
}
namespace myScriptTwo {
function test() {}
}

        写法四、定义多个命名空间和不包含在命名空间中的代码,该写法也比较少见

namespace myScriptOne {
function test() {}
}
namespace myScriptTwo {
function test() {}
}
namespace {
function test() {}
}


        理解了命名空间的正确写法后,还需掌握在命名空间内的三个基本概念


        概念一、非限定名称,或不包含前缀的名称。以类为例,写法为new action()或action::password(),也就是没有使用命名空间前的正常写法。如果当前脚本的命名空间为script,那么将会被解析为new script\action()或script\action::password。如果在命名空间中使用了全局的test()方法,它将使用不同优先策略来解析。首页是解析当前命名空间内,若是没找到,则提升到全局,还是没有,则报错,这点对于常量也适用,而类则不是这样,类名称总是解析到当前命名空间中的名称,如果当前命名空间没有该类,则会报错。


namespace script{
echo test('123');
}
namespace {
function test($num) {
return 3;
}
}

        概念二、限定名称,或包含前缀的名称。以类为例,写法为new subAction\action()或subAction\action::password(),如果当前脚本的命名空间为script,那么将会被解析为new script\subAction\action()或script\subAction\action::password。


namespace script\subAction;

class action {
public static function password () {}
}

namespace script;
include 'script.php';
class action {
public static function password () {}
}

new subAction\action;
subAction\action::password();


        概念三、完全限定名称,或包含了全局前缀操作符的名称。以类为例,写法为new \subAction\action()或\subAction\action::password(),此种情况下,不会做其解析,将直接调用。访问任意全局类、函数或常量,都可以使用完全限定名称,如,使用ThinkPHP 3.2.3,若想实例化PHPExcel类,则只需new \PHPExcel()。

namespace script;
include 'script.php';
class action {
public static function password () {}
}

new \script\subAction\action;
\script\subAction\action::password();

function strlen($str) {
return 3;
}

// 调用全局中的strlen函数
echo \strlen('test'); // 4

// 先从当前命名空间内解析
echo strlen('test'); //3

        了解了三个基本概念后,再来结合PHP动态语言的特征,对上面的代码进行改动,看看结果

namespace script;
class action {
public static function password () {}
}

$action = 'script\action';
new $action;

$action = '\script\action';
new $action;

        通过上例所知, 在命名空间内,经过动态处理后,是不解析的,必须使用完全限定名称(包括命名空间前缀的类名称)。注意因为在动态的类名称、函数名称或常量名称中,限定名称和完全限定名称没有区别,因此其前导的反斜杠是不必要的。


        接下来说说namespace关键字和__NAMESPACE__魔术常量。使用namespace关键字可以直接来访问命名空间内的常量、类、函数,而__NAMESPACE__魔术常量则通常和动态语句相关联,该魔术常量输出的是命名空间的字符串名称,看看下面的示例就明白了。


namespace script;

class action {
public function password() {}
}

// 此时的namespace相当于类里的self操作
new namespace\action;

// 若当前脚本没有使用命名空间,则输出空
echo __NAMESPACE__; //script
$action = __NAMESPACE__ . '\\action';
new $action;


        再来谈谈use。在项目开发中,若使用命名空间,每个脚本中都有一个独立的命名空间,而且有可能命名空间名称还有很多层次,如A脚本命名空间名称为A\B\C\D,B脚本的命名空间为E\F\G,这种形式的,若A脚本继承B脚本的类,或引用相应的类,则需要写一大串命名空间,这是很不方便的,还不如没有使用命名空间前,为了避免类重名,从而定义老长的类名来的划算。为了避免出现这种情况,use的作用就体现出来了。use的意思是"别名/导入",看到了吧,别名,也就是可以通过把一长串的字符串缩小为很短的名称,大大省事了,用法为use namespacename [as alias]。还是先来看看没有使用use和使用use的代码,来对比下,就发现其好用之处。


namespace E\F\G;
class father {}

// 没有使用use

namespace A\B\C\D;

include 'father.php';

class son extends \E\F\G\father {}

// 使用use

namespace A\B\C\D;
use E\F\G\father; // 等同于 use E\F\G\father as father
include 'father.php';

class son extends father {}

        允许通过别名引用或导入外部的完全限定名称,是命名空间的一个重要特征,没了这个特征,命名空间就差不多属鸡肋了。use可以为类使用别名,如上例所示,还有为命名空间使用别名。看到这儿,或许有的朋友会有疑惑,为什么没有解析成‘A\B\C\D\E\F\G\father’呢,这是因为导入的名称必须是完全限定的(其前置的分隔符可以不加),不会根据当前的命名空间作相对解析。注意,使用别名,都是在导入外部的命名空间的前提下的。同时PHP不支持导入常量或函数,也就是说,use大多数就是为类服务的了,之所以说大多数,因为use还可以为命名空间使用别名,看看下面示例。

namespace C\D\E;

class action {
public static function password() {}
}

function passward() {}
const COUNT = 1;

use C\D as dd;
include 'cde.php';

dd\E\passward(); // 解析为 C\D\E\passward
dd\E\COUNT; // 解析为 C\D\E\COUNT
new dd\E\action; // 解析为 C\D\E\action

        use导入命名空间后,对非限定名称和限定名称有影响,而完全限定名称由于名称是确定的,因此不受影响,同样不受影响的还有动态创建的名称。在使用时,一条语句可以包含多个use语句。

namespace test;

class action {
public function __construct() {
echo __METHOD__;
}
}

namespace alias;

class action {
public function __construct() {
echo __METHOD__;
}
}

namespace script{

include 'action.php';
include 'alias.php';

use test\action as aliasScript, alias;

new aliasScript; // 非限定名称 test\action::__construct
new alias\action; // 限定名称 alias\action::__construct
new \action; // 全局限定名称 action::__construct

$action = 'action';
new $action; // 全局中aciton action::__construct
}

namespace {
class action {
public function __construct() {
echo __METHOD__;
}
}
}


        最后,说下命名空间的解析规则,摘自官方文档。

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()。

        若是看到了这里,说明对命名空间了解了大概了,若是能看懂下面的示例,那么命名空间就掌握了。

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"

        ps:本学习笔记是参照 官方教材而来的。