2024年PHP日本大会

名称解析规则

(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"
?>
添加注释

用户贡献注释 7 条注释

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

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

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

->foo.php

<?php
命名空间 ns;
foo
{
公共
$say;

公共函数
__construct()
{
$this->say = "bar";
}

}
?>

-> loader.php

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

foo 扩展 ns\foo // ns\foo在此处加载
{
公共函数
__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
14年前
在使用命名空间和(自定义或基本)自动加载结构时;魔术函数__autoload必须定义在全局作用域中,不能在命名空间中,也不能在其他函数或方法中。

<?php
命名空间 Glue {
/**
* 在此类中定义您的自定义结构和算法
* 用于自动加载。
*/
Import
{
公共静态函数
load ($classname)
{
echo
'正在自动加载类 '.$classname."\n";
require_once
$classname.'.php';
}
}
}

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

函数
__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::这样的延迟静态绑定的配套,它在运行时解析为当前类。

所以你不能这样做

命名空间 Alpha;
类 Helper {
公共静态 $Value = "ALPHA";
}
类 Base {
公共静态函数 Write() {
echo Helper::$Value;
}
}

命名空间 Beta;
类 Helper 扩展 \Alpha\Helper {
公共静态 $Value = 'BETA';
}
类 Base 扩展 \Alpha\Base {}

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

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

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

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

->foo.php

<?php
命名空间 ns;
foo
{
公共
$say;

公共函数
__construct()
{
$this->say = "bar";
}

}
?>

-> loader.php

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

foo 扩展 ns\foo // ns\foo在此处加载
{
公共函数
__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
anrdaemon at freemail dot ru
8年前
命名空间可能不区分大小写,但自动加载器通常区分大小写。
帮自己一个忙,保持你的大小写与文件名一致,并且不要过度复杂化自动加载器。
像这样就足够了

<?php

命名空间 org\example;

函数
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');
?>
To Top