CakeFest 2024: The Official CakePHP Conference

FAQ:命名空间必知必会

(PHP 5 >= 5.3.0, PHP 7, PHP 8)

本文分两节:常见问题、有助于完全理解的实现详情。

首先,常见问题。

  1. 如果我不用命名空间,是否需要关心它?
  2. 我如何在命名空间内使用一个全局/内置的类?
  3. 如何在命名空间内访问它自己的类、函数、常量?
  4. \my\name\name 这样的名称是如何解析的?
  5. my\name 这样的名称是如何解析的?
  6. name 这样的非限定类名是如何解析的?
  7. name 这样的非限定常量和函数名是如何解析的?

为了帮助理解,我们提供了一些命名空间实现细节。

  1. 在同一个文件中,导入名称不能和定义的类名发生冲突。
  2. 不允许嵌套 namespace。
  3. 动态命名空间名称(引号标识)应该转义反斜线。
  4. 引用一个未定义的、带反斜线的常量,会导致 fatal 错误并退出
  5. 不能重载特殊常量: null, true or false

如果我不用命名空间,是否需要关心它?

不需要。命名空间不影响现存的代码,也不影响即将要写下的不含命名空间的代码。 想要的话可以这样写:

示例 #1 在命名空间之外访问全局类

<?php
$a
= new \stdClass;

以上等同于:

示例 #2 在命名空间之外访问全局类

<?php
$a
= new stdClass;

我如何在命名空间内使用一个全局/内置的类?

示例 #3 在命名空间内访问内置的类

<?php
namespace foo;
$a = new \stdClass;

function
test(\ArrayObject $parameter_type_example = null) {}

$a = \DirectoryIterator::CURRENT_AS_FILEINFO;

// 扩展内置或全局的 class
class MyException extends \Exception {}
?>

如何在命名空间内访问它自己的类、函数、常量?

示例 #4 在命名空间中访问内置的类、函数、常量

<?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;
?>

\my\name\name 这样的名称是如何解析的?

\ 开头的名称总是会解析成原样, 因此 \my\name 实际上是 my\name, 而 \ExceptionException

示例 #5 完全限定名称

<?php
namespace foo;
$a = new \my\name(); // class "my\name" 的实例
echo \strlen('hi'); // 调用函数 "strlen"
$a = \INI_ALL; // $a 的值设置成常量 "INI_ALL"
?>

my\name 这样的名称是如何解析的?

my\name 这样包含反斜线的名称,但不以反斜线开头的名称, 能够以两种不同的方式解析。

如果有个导入语句,将其他名字设置别名为 my, 则导入别名会应用到 my\namemy 部分。

如果没有导入,就会追加当前的命名空间名称为 my\name 的前缀。

示例 #6 限定名称

<?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"
?>

name 这样的非限定名称是如何解析的?

name 这样不包含反斜线的名称, 能够以两种不同的方式解析。

如果有导入语句,设置别名为 name,就会应用导入别名。

如果没有,就会把当前命名空间添加到 name 的前缀。

示例 #7 非限定类名

<?php
namespace foo;
use
blah\blah as foo;

$a = new name(); // class "foo\name" 的实例
foo::name(); // 调用 class "blah\blah" 的静态方法 "name"
?>

name 这样的非限定常量和函数名是如何解析的?

name 这样不包含反斜线的常量和函数名,能以两种不同的方式解析。

首先,当前命名空间会添加到 name 的前缀。

然后,如果当前命名空间不存在函数和常量 name, 而全局存在,就会使用全局的函数和常量 name

示例 #8 非限定函数和常量名

<?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" 的值
?>

在同一个文件中,导入名称不能和定义的类名发生冲突

允许以下脚本中的组合:

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;
?>

不允许嵌套 namespace

PHP 不允许嵌套 namespace

<?php
namespace my\stuff {
namespace
nested {
class
foo {}
}
}
?>
实际上,它看上去像是这样:
<?php
namespace my\stuff\nested {
class
foo {}
}
?>

动态命名空间名称(引号标识)应该转义反斜线

重要的是,字符串中反斜线是一个转义字符,因此在字符串中使用时,必须要写两遍。 否则就会在无意中造成一些后果:

示例 #9 在双引号字符串中使用命名空间的危险性

<?php
$a
= new "dangerous\name"; // 在双引号字符串中,\n 是换行符!
$obj = new $a;

$a = new 'not\at\all\dangerous'; // 这里没有问题
$obj = new $a;
?>
在单引号字符串中,使用反斜线是安全的。 但在最佳实践中,我们仍然推荐为所有字符串统一转义反斜线。

引用一个未定义的、带反斜线的常量,会导致 fatal 错误并退出

FOO 这样的非限定名称常量,如果使用的时候还没定义, 会产生一个 notice。PHP 会假设该常量的值是 FOO。 如果没有找到包含反斜线的常量,无论是完全或者不完全限定的名称,都会产生 fatal 错误。

示例 #10 未定义的常量

<?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
?>

不能重载特殊常量:null, true or false

在命名空间内定义特殊的内置常量,会导致 fatal 错误

示例 #11 未定义的常量

<?php
namespace bar;
const
NULL = 0; // fatal error;
const true = 'stupid'; // 也是 fatal error;
// etc.
?>

add a note

User Contributed Notes 6 notes

up
15
manolachef at gmail dot com
11 years ago
There is a way to define a namespaced constant that is a special, built-in constant, using define function and setting the third parameter case_insensitive to false:

<?php
namespace foo;
define(__NAMESPACE__ . '\NULL', 10); // defines the constant NULL in the current namespace
var_dump(NULL); // will show 10
var_dump(null); // will show NULL
?>

No need to specify the namespace in your call to define(), like it happens usually
<?php
namespace foo;
define(INI_ALL, 'bar'); // produces notice - Constant INI_ALL already defined. But:

define(__NAMESPACE__ . '\INI_ALL', 'bar'); // defines the constant INI_ALL in the current namespace
var_dump(INI_ALL); // will show string(3)"bar". Nothing unespected so far. But:

define('NULL', 10); // defines the constant NULL in the current namespace...
var_dump(NULL); // will show 10
var_dump(null); // will show NULL
?>

If the parameter case_insensitive is set to true
<?php
namespace foo;
define (__NAMESPACE__ . '\NULL', 10, true); // produces notice - Constant null already defined
?>
up
7
shaun at slickdesign dot com dot au
7 years ago
When creating classes or calling static methods from within namespaces using variables, you need to keep in mind that they require the full namespace in order for the appropriate class to be used; you CANNOT use an alias or short name, even if it is called within the same namespace. Neglecting to take this into account can cause your code to use the wrong class, throw a fatal missing class exception, or throw errors or warnings.

In these cases, you can use the magic constant __NAMESPACE__, or specify the full namespace and class name directly. The function class_exists also requires the full namespace and class name, and can be used to ensure that a fatal error won't be thrown due to missing classes.

<?php

namespace Foo;
class
Bar {
public static function
test() {
return
get_called_class();
}
}

namespace
Foo\Foo;
class
Bar extends \Foo\Bar {
}

var_dump( Bar::test() ); // string(11) "Foo\Foo\Bar"

$bar = 'Foo\Bar';
var_dump( $bar::test() ); // string(7) "Foo\Bar"

$bar = __NAMESPACE__ . '\Bar';
var_dump( $bar::test() ); // string(11) "Foo\Foo\Bar"

$bar = 'Bar';
var_dump( $bar::test() ); // FATAL ERROR: Class 'Bar' not found or Incorrect class \Bar used
up
3
teohad at NOSPAM dot gmail dot com
7 years ago
[Editor's note: that behavior is caused by a bug in PHP 7.0, which has been fixed as of PHP 7.0.7.]

Regarding the entry "Import names cannot conflict with classes defined in the same file".
- I found that since PHP 7.0 this is no longer the case.
In PHP 7.0 you can have a class with a name that matches an imported class (or namespace or both at the same time).

<?php
namespace ns1 {
class
ns1 {
public static function
write() {
echo
"ns1\\ns1::write()\n";
}
}
}

namespace
ns1\ns1 {
class
ns1c {
public static function
write() {
echo
"ns1\\ns1\\ns1c::write()\n";
}
}
}

namespace
ns2 {
use
ns1\ns1 as ns1; // both a class in ns1, and a namespace ns1\ns1

// the next class causes fatal error in php 5.6, not in 7.0
class ns1 {
public static function
write() {
echo
"ns2\\ns1::write()\n";
}
}

ns1::write(); // calls imported ns1\ns1::write()
ns1\ns1c::write(); // calls imported ns1\ns1\ns1c::write()
namespace\ns1::write(); // calls ns2\ns1::write()
}
?>
up
2
theking2 at king dot ma
1 year ago
Just like class names currently namespaces are not case sensitive. So no errors will be shown here:

<?php declare(strict_types=1);
namespace
Foo;
class
Bar {
public function
__construct() {
echo
'Map constructed';
}
}

$foobar = new \foo\bar();
up
6
phpcoder
9 years ago
Regarding "Neither functions nor constants can be imported via the use statement." Actually you can do it in PHP 5.6+:

<?php

// importing a function (PHP 5.6+)
use function My\Full\functionName;

// aliasing a function (PHP 5.6+)
use function My\Full\functionName as func;

// importing a constant (PHP 5.6+)
use const My\Full\CONSTANT;
?>
up
-4
okaresz
10 years ago
To correct manolachef's answer: define() ALWAYS defines constants in the GLOBAL namespace.

As nl-x at bita dot nl states in the note at http://www.php.net/manual/en/function.define.php, the constant "NULL" can be defined with define() case-sensitively, but can only be retrieved with constant(), leaving the meaning of NULL uppercase keyword as the only value of the type null.
To Top