如果你想在命名空间或类中声明一个 __autoload 函数,使用 spl_autoload_register() 函数注册它,它就可以正常工作。
(PHP 5 >= 5.3.0, PHP 7, PHP 8)
为了这些解析规则的目的,以下是一些重要的定义
这是一个没有命名空间分隔符的标识符,例如 Foo
这是一个带有命名空间分隔符的标识符,例如 Foo\Bar
这是一个带有命名空间分隔符的标识符,它以命名空间分隔符开头,例如 \Foo\Bar
。命名空间 \Foo
也是完全限定名称。
这是一个以 namespace
开头的标识符,例如 namespace\Foo\Bar
。
名称解析遵循以下解析规则
\A\B
解析为 A\B
。
namespace
的名称。如果名称出现在全局命名空间中,则会剥离 namespace\
前缀。例如,命名空间 X\Y
中的 namespace\A
解析为 X\Y\A
。全局命名空间中的相同名称解析为 A
。
A\B\C
导入为 C
,则名称 C\D\E
将转换为 A\B\C\D\E
。
A\B
中的名称 C\D\E
解析为 A\B\C\D\E
。
use A\B\C;
之后,使用诸如 new C()
的用法将解析为名称 A\B\C()
。类似地,在 use function A\B\foo;
之后,使用诸如 foo()
的用法将解析为名称 A\B\foo
。
A\B
中的 new C()
解析为名称 A\B\C
。
A\B
中,以下是函数 foo()
的调用解析方式
A\B\foo()
。
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"
?>
这里提到的“自动加载”术语不应与 __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 指定命名空间,你显然会遇到致命错误。
我的示例只有一个级别的目录,但我已经在更复杂、更深的结构中测试过。希望对大家有所帮助。
干杯!
在使用命名空间和(自定义或基本)自动加载结构时,魔术函数 __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);
}
}
?>
提到的文件系统类比在一个重要点上失效了。
命名空间解析*仅*在声明时生效。编译器将所有命名空间/类引用固定为绝对路径,就像创建绝对符号链接一样。
你不能期望相对符号链接,因为它们应该在访问时进行评估 -> 在 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 中,它将按预期工作。
这里提到的“自动加载”术语不应与 __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 指定命名空间,你显然会遇到致命错误。
我的示例只有一个级别的目录,但我已经在更复杂、更深的结构中测试过。希望对大家有所帮助。
干杯!
命名空间可能不区分大小写,但自动加载器通常区分大小写。
对自己好一点,保持你的大小写与文件名一致,不要将自动加载器复杂化,超出必要。
像这样应该足以应对大多数情况
<?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');
?>
我花了一些时间才搞明白,因为我很难找到关于类名何时与命名空间匹配的文档,以及这种情况是否合法,以及应该预期什么样的行为。在 #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 特性的人。