在PHP编程中,命名空间是一种非常重要的概念。它允许开发者将代码组织成逻辑结构,避免类名和函数名之间的冲突。本篇教程将介绍一些PHP命名空间的常见问题,并提供相应的解决方案和实例。
一、常见问题
1、不用命名空间是否要关心它?
不需要。命名空间不影响现存的代码,也不影响即将要写下的不含命名空间的代码。 想要的话可以在命名空间之外访问全局类:
<?php $a = new \stdClass;
以上等同于在命名空间之外访问全局类:
<?php $a = new stdClass;
2、如何在命名空间内使用一个全局/内置的类?
可以这样在命名空间内访问内置的类:
<?php namespace foo; $a = new \stdClass; function test(\ArrayObject $parameter_type_example = null) {} $a = \DirectoryIterator::CURRENT_AS_FILEINFO; // 扩展内置或全局的 class class MyException extends \Exception {} ?>
3、如何在命名空间内访问它自己的类、函数、常量?
在命名空间中访问内置的类、函数、常量示例:
<?php namespace foo; class MyClass {} // 以当前命名空间中的 class 作为参数的类型 function test(MyClass $parameter_type_example = null) {} // 以当前命名空间中的 class 作为参数的类型的另一种方式 function test(\foo\MyClass $parameter_type_example = null) {} // 在当前命名空间中扩展一个类 class Extended extends MyClass {} // 访问全局函数 $a = \globalfunc(); // 访问全局常量 $b = \INI_ALL; ?>
4、类似 \my\name 和 \name 这样的名称是如何解析的?
以 \ 开头的名称总是会解析成原样, 因此 \my\name 实际上是 my\name, 而 \Exception 是 Exception。
完全限定名称示例:
<?php namespace foo; $a = new \my\name(); // class "my\name" 的实例 echo \strlen('hi'); // 调用函数 "strlen" $a = \INI_ALL; // $a 的值设置成常量 "INI_ALL" ?>
5、类似 my\name 这样的名称是如何解析的?
类似 my\name 这样包含反斜线的名称,但不以反斜线开头的名称, 能够以两种不同的方式解析:
- 如果有个导入语句,将其他名字设置别名为 my, 则导入别名会应用到 my\name 的 my 部分;
- 如果没有导入,就会追加当前的命名空间名称为 my\name 的前缀。
限定名称示例:
<?php namespace foo; use blah\blah as foo; $a = new my\name(); // class "foo\my\name" 的实例 foo\bar::name(); // 调用 class "blah\blah\bar" 的静态方法 "name" my\bar(); // 调用函数 "foo\my\bar" $a = my\BAR; // 设置 $a 的值为 "foo\my\BAR" ?>
6、类似name这样的非限定名称是如何解析的?
类似 name 这样不包含反斜线的名称, 能够以两种不同的方式解析:
- 如果有导入语句,设置别名为 name,就会应用导入别名;
- 如果没有,就会把当前命名空间添加到 name 的前缀。
非限定类名示例:
<?php namespace foo; use blah\blah as foo; $a = new name(); // class "foo\name" 的实例 foo::name(); // 调用 class "blah\blah" 的静态方法 "name" ?>
7、类似 name 这样的非限定常量和函数名是如何解析的?
类似 name 这样不包含反斜线的常量和函数名,能以两种不同的方式解析:
- 首先,当前命名空间会添加到 name 的前缀;
- 然后,如果当前命名空间不存在函数和常量 name, 而全局存在,就会使用全局的函数和常量 name。
非限定函数和常量名示例:
<?php namespace foo; use blah\blah as foo; const FOO = 1; function my() {} function foo() {} function sort(&$a) { sort($a); $a = array_flip($a); return $a; } my(); // 调用 "foo\my" $a = strlen('hi'); // 由于 "foo\strlen" 不存在,所以调用全局的 "strlen" $arr = array(1,3,2); $b = sort($arr); // 调用函数 "foo\sort" $c = foo(); // 未导入,调用函数 "foo\foo" $a = FOO; // 未导入,设置 $a 为常量 "foo\FOO" 的值 $b = INI_ALL; // 设置 $b 为全局常量 "INI_ALL" 的值 ?>
二、命名空间实例
1、在同一个文件中,导入名称不能和定义的类名发生冲突
允许以下脚本中的组合:
file1.php
<?php namespace my\stuff; class MyClass {} ?>
another.php
<?php namespace another; class thing {} ?>
file2.php
<?php namespace my\stuff; include 'file1.php'; include 'another.php'; use another\thing as MyClass; $a = new MyClass; // class "thing" 的实例来自于命名空间 another ?>
尽管在 my\stuff 命名空间中存在 MyClass, 因为类定义在了独立的文件中,所以不会发生名称冲突。 不过,接下来的例子中,因为 MyClass 定义在了 use 语句的同一个文件中, 所以发生了名称冲突,导致了 fatal 错误。
<?php namespace my\stuff; use another\thing as MyClass; class MyClass {} // fatal error: MyClass conflicts with import statement $a = new MyClass; ?>
2、不允许嵌套namespace
PHP 不允许嵌套 namespace
<?php namespace my\stuff { namespace nested { class foo {} } } ?>
实际上,它看上去像是这样:
<?php namespace my\stuff\nested { class foo {} } ?>
3、动态命名空间名称(引号标识)应该转义反斜线
重要的是,字符串中反斜线是一个转义字符,因此在字符串中使用时,必须要写两遍。 否则就会在无意中造成一些后果:
在双引号字符串中使用命名空间的危险性示例:
<?php $a = new "dangerous\name"; // 在双引号字符串中,\n 是换行符! $obj = new $a; $a = new 'not\at\all\dangerous'; // 这里没有问题 $obj = new $a; ?>
在单引号字符串中,使用反斜线是安全的。 但在最佳实践中,我们仍然推荐为所有字符串统一转义反斜线。
4、引用一个未定义的、带反斜线的常量,会导致 fatal 错误并退出
像 FOO 这样的非限定名称常量,如果使用的时候还没定义, 会产生一个 notice。PHP 会假设该常量的值是 FOO。 如果没有找到包含反斜线的常量,无论是完全或者不完全限定的名称,都会产生 fatal 错误。
未定义的常量示例:
<?php namespace bar; $a = FOO; // 产生 notice - undefined constants "FOO" assumed "FOO"; $a = \FOO; // fatal error, undefined namespace constant FOO $a = Bar\FOO; // fatal error, undefined namespace constant bar\Bar\FOO $a = \Bar\FOO; // fatal error, undefined namespace constant Bar\FOO ?>
5、不能重载特殊常量:NULL、TRUE、FALSE、ZEND_THREAD_SAFE、ZEND_DEBUG_BUILD
在命名空间内定义特殊的内置常量,会导致 fatal 错误。未定义的常量示例:
<?php namespace bar; const NULL = 0; // fatal error; const true = 'stupid'; // 也是 fatal error; // etc. ?>