名称解析规则

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

为了这些解析规则的目的,以下是一些重要的定义

命名空间名称定义
非限定名称

这是一个没有命名空间分隔符的标识符,例如 Foo

限定名称

这是一个带有命名空间分隔符的标识符,例如 Foo\Bar

完全限定名称

这是一个带有命名空间分隔符的标识符,它以命名空间分隔符开头,例如 \Foo\Bar。命名空间 \Foo 也是完全限定名称。

相对名称

这是一个以 namespace 开头的标识符,例如 namespace\Foo\Bar

名称解析遵循以下解析规则

  1. 完全限定名称始终解析为没有前导命名空间分隔符的名称。例如 \A\B 解析为 A\B
  2. 相对名称始终解析为用当前命名空间替换 namespace 的名称。如果名称出现在全局命名空间中,则会剥离 namespace\ 前缀。例如,命名空间 X\Y 中的 namespace\A 解析为 X\Y\A。全局命名空间中的相同名称解析为 A
  3. 对于限定名称,名称的第一段根据当前类/命名空间导入表进行转换。例如,如果命名空间 A\B\C 导入为 C,则名称 C\D\E 将转换为 A\B\C\D\E
  4. 对于限定名称,如果没有任何导入规则适用,则当前命名空间将被附加到名称之前。例如,命名空间 A\B 中的名称 C\D\E 解析为 A\B\C\D\E
  5. 对于非限定名称,根据相应符号类型的当前导入表转换名称。这意味着类类名称根据类/命名空间导入表进行转换,函数名称根据函数导入表进行转换,常量根据常量导入表进行转换。例如,在 use A\B\C; 之后,使用诸如 new C() 的用法将解析为名称 A\B\C()。类似地,在 use function A\B\foo; 之后,使用诸如 foo() 的用法将解析为名称 A\B\foo
  6. 对于非限定名称,如果没有任何导入规则适用,并且名称指的是类类符号,则当前命名空间将被附加到之前。例如,命名空间 A\B 中的 new C() 解析为名称 A\B\C
  7. 对于非限定名称,如果没有任何导入规则适用,并且名称指的是函数或常量,并且代码不在全局命名空间中,则名称在运行时解析。假设代码在命名空间 A\B 中,以下是函数 foo() 的调用解析方式
    1. 它从当前命名空间查找函数:A\B\foo()
    2. 它尝试查找并调用全局函数 foo()

示例 #1 说明名称解析

<?php
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" 的方法 "foo"
// 如果类 "A\B" 找不到,它会尝试自动加载类 "A\B"
?>
添加注释

用户贡献的注释 9 notes

37
kdimi
13 年前
如果你想在命名空间或类中声明一个 __autoload 函数,使用 spl_autoload_register() 函数注册它,它就可以正常工作。
33
rangel
15 年前
这里提到的“自动加载”术语不应与 __autoload 函数混淆,后者用于自动加载对象。关于 __autoload 和命名空间的解析,我想分享以下经验

-> 假设你有以下目录结构

- 根目录
| - loader.php
| - ns
| - foo.php

->foo.php

<?php
namespace ns;
class
foo
{
public
$say;

public function
__construct()
{
$this->say = "bar";
}

}
?>

-> loader.php

<?php
// 全局空间 <--
function __autoload($c)
{
require_once
$c . ".php";
}

class
foo extends ns\foo // ns\foo 这里被加载
{
public function
__construct()
{
parent::__construct();
echo
"<br />foo" . $this->say;
}
}
$a = new ns\foo(); // ns\foo 这里正常加载 ns/foo.php.
echo $a->say; // 输出 bar,符合预期.
$b = new foo; // 输出 foobar,正常.
?>

如果你的目录/文件结构与命名空间/类保持一致,__autoload 会正常工作。
但是... 如果你尝试为 loader.php 指定命名空间,你显然会遇到致命错误。
我的示例只有一个级别的目录,但我已经在更复杂、更深的结构中测试过。希望对大家有所帮助。

干杯!
5
safakozpinar at NOSPAM dot gmail dot com
13 年前
在使用命名空间和(自定义或基本)自动加载结构时,魔术函数 __autoload 必须定义在全局范围内,而不是在命名空间中,也不要在其他函数或方法中定义。

<?php
namespace Glue {
/**
* 在这个类中定义你自定义的自动加载结构和算法。
*/
class Import
{
public static function
load ($classname)
{
echo
'Autoloading class '.$classname."\n";
require_once
$classname.'.php';
}
}
}

/**
* 在全局命名空间中定义函数 __autoload。
*/
namespace {

function
__autoload ($classname)
{
\Glue\Import::load($classname);
}

}
?>
0
Kavoir.com
10 年前
对于第 4 点,“例如,如果命名空间 A\B\C 被导入为 C”应该改为“例如,如果类 A\B\C 被导入为 C”。
-2
llmll
9 年前
提到的文件系统类比在一个重要点上失效了。

命名空间解析*仅*在声明时生效。编译器将所有命名空间/类引用固定为绝对路径,就像创建绝对符号链接一样。

你不能期望相对符号链接,因为它们应该在访问时进行评估 -> 在 PHP 运行时。

换句话说,命名空间就像 __CLASS__ 或 self:: 一样,在解析时进行评估。*没有*发生的事情,就是像 static:: 一样的延迟静态绑定,它在运行时解析为当前类。

所以你不能做以下操作:

namespace Alpha;
class Helper {
public static $Value = "ALPHA";
}
class Base {
public static function Write() {
echo Helper::$Value;
}
}

namespace Beta;
class Helper extends \Alpha\Helper {
public static $Value = 'BETA';
}
class Base extends \Alpha\Base {}

\Beta\Base::Write(); // 应该输出 "BETA",因为这是运行时执行的命名空间上下文。

如果你将 write() 函数复制到 \Beta\Base 中,它将按预期工作。
-5
rangel
15 年前
这里提到的“自动加载”术语不应与 __autoload 函数混淆,后者用于自动加载对象。关于 __autoload 和命名空间的解析,我想分享以下经验

-> 假设你有以下目录结构

- 根目录
| - loader.php
| - ns
| - foo.php

->foo.php

<?php
namespace ns;
class
foo
{
public
$say;

public function
__construct()
{
$this->say = "bar";
}

}
?>

-> loader.php

<?php
// 全局空间 <--
function __autoload($c)
{
require_once
$c . ".php";
}

class
foo extends ns\foo // ns\foo 这里被加载
{
public function
__construct()
{
parent::__construct();
echo
"<br />foo" . $this->say;
}
}
$a = new ns\foo(); // ns\foo 这里正常加载 ns/foo.php.
echo $a->say; // 输出 bar,符合预期.
$b = new foo; // 输出 foobar,正常.
?>

如果你的目录/文件结构与命名空间/类保持一致,__autoload 会正常工作。
但是... 如果你尝试为 loader.php 指定命名空间,你显然会遇到致命错误。
我的示例只有一个级别的目录,但我已经在更复杂、更深的结构中测试过。希望对大家有所帮助。

干杯!
-7
dn dot permyakov at gmail dot com
10 年前
有人能解释一下,为什么我们需要第 4 点,如果我们有第 2 点(它涵盖了非限定名和限定名)?
-5
anrdaemon at freemail dot ru
8 年前
命名空间可能不区分大小写,但自动加载器通常区分大小写。
对自己好一点,保持你的大小写与文件名一致,不要将自动加载器复杂化,超出必要。
像这样应该足以应对大多数情况

<?php

namespace org\example;

function
spl_autoload($className)
{
$file = new \SplFileInfo(__DIR__ . substr(strtr("$className.php", '\\', '/'), 11));
$path = $file->getRealPath();
if(empty(
$path))
{
return
false;
}
else
{
return include_once
$path;
}
}

\spl_autoload_register('\org\example\spl_autoload');
?>
-7
CJ Taylor
10 年前
我花了一些时间才搞明白,因为我很难找到关于类名何时与命名空间匹配的文档,以及这种情况是否合法,以及应该预期什么样的行为。在 #6 中有解释,但我认为我会通过示例分享这一点,让像我一样看到它的人更容易理解。假设下面三个文件都在同一个目录中。

file1.php
<?php
namespace foo;

class
foo {
static function
hello() {
echo
"hello world!";
}
}
?>

file2.php
<?php
namespace foo;
include(
'file1.php');

foo::hello(); // 你在同一个命名空间或作用域中.
\foo\foo::hello(); // 在全局作用域中调用.
?>

file3.php
<?php
include('file1.php');

foo\foo::hello(); // 你在命名空间之外.
\foo\foo::hello(); // 在全局作用域中调用.
?>

取决于你构建的内容(例如,在大型应用程序上的模块、插件或包),有时声明一个与命名空间匹配的类是有意义的,甚至可能是必需的。请注意,如果你尝试引用任何共享相同命名空间的类,请省略命名空间,除非你像上面的示例一样在全局范围内进行。

我希望这对你有所帮助,尤其是那些尝试理解这个 5.3 特性的人。
To Top