PHP 命名空间
Resolving name clashes, sub-namespaces, use aliases, and PSR-4 autoloading.
Why namespaces
Two libraries both define User. Without namespaces one must be renamed. Namespaces prefix the name so App\Models\User and Vendor\User coexist.
<?php
namespace App\Models;
class User {
public function hello(): string {
return "App\\Models\\User";
}
}
namespace App\Services;
use App\Models\User as UserModel;
class User {
public function hello(): string {
return "App\\Services\\User";
}
}
namespace App;
use App\Models\User;
use App\Services\User as UserService;
echo (new User())->hello(), "\n";
echo (new UserService())->hello(), "\n";
echo User::class, "\n";App\Models\User
App\Services\User
App\Models\Usernamespace blocks per file are legal, but real projects use one per file (PSR-4). The above is for demonstration only.Three ways to resolve names
| Form | Meaning | Example |
|---|---|---|
| Foo\Bar | 相对当前命名空间 | 在 App\Models 里指 App\Models\Foo\Bar |
| \Foo\Bar | 完全限定,从根开始 | 始终是 Foo\Bar |
| namespace\Foo | 显式当前命名空间 | App\Models\Foo |
<?php
namespace App\Models;
class User {}
// 三种写法指向同一个类
echo User::class, "\n";
echo namespace\User::class, "\n";
echo \App\Models\User::class, "\n";
// 不存在的相对名会去当前命名空间找
echo \strlen("abc"), "\n"; // 全局函数要加反斜杠App\Models\User
App\Models\User
App\Models\User
3strlen() works unqualified — but a same-named local function shadows it and the fallback costs a lookup.Forms of use
<?php
namespace App;
use App\Models\User; // 导入类
use App\Models\Post as BlogPost; // 别名
use function App\Helpers\format; // 导入函数
use const App\Config\DEBUG; // 导入常量
echo User::class, "\n";
echo BlogPost::class, "\n";
echo DEBUG ? "调试模式" : "生产模式", "\n";App\Models\User
App\Models\Post
调试模式use only affects compile-time name resolution; it does not load anything. Loading is the autoloader's job.
PSR-4 autoloading
PSR-4 in one line: namespace prefix maps to a directory, class name to a file name. App\Models\User → <root>/Models/User.php.
<?php
spl_autoload_register(function (string $class): void {
$prefix = "App\\";
$baseDir = __DIR__ . "/src/";
if (!str_starts_with($class, $prefix)) {
return;
}
$relative = substr($class, strlen($prefix));
$file = $baseDir . str_replace("\\", "/", $relative) . ".php";
if (is_file($file)) {
require $file;
}
});
echo "加载器已注册\n";加载器已注册Real projects rely on composer dump-autoload and a single require 'vendor/autoload.php'. Writing it once teaches you what happens underneath.
Common mistakes
- **
namespacemust be the first statement** (exceptdeclare); prior output is an error - **
useinside a class** means trait import, not namespace import - Two same-named imports ——
use A\User; use B\User;is a fatal conflict - Class names in strings —— escape backslashes or use
User::class
为什么需要命名空间
两个库都定义了 User 类,没有命名空间时只能改其中一个的名字。命名空间给名字加了前缀,App\Models\User 和 Vendor\User 可以共存。
<?php
namespace App\Models;
class User {
public function hello(): string {
return "App\\Models\\User";
}
}
namespace App\Services;
use App\Models\User as UserModel;
class User {
public function hello(): string {
return "App\\Services\\User";
}
}
namespace App;
use App\Models\User;
use App\Services\User as UserService;
echo (new User())->hello(), "\n";
echo (new UserService())->hello(), "\n";
echo User::class, "\n";App\Models\User
App\Services\User
App\Models\Usernamespace 块虽然合法,但实际项目一个文件一个命名空间(PSR-4 要求)。上面只是为了演示方便。三种名字解析
| 写法 | 含义 | 示例 |
|---|---|---|
| Foo\Bar | 相对当前命名空间 | 在 App\Models 里指 App\Models\Foo\Bar |
| \Foo\Bar | 完全限定,从根开始 | 始终是 Foo\Bar |
| namespace\Foo | 显式当前命名空间 | App\Models\Foo |
<?php
namespace App\Models;
class User {}
// 三种写法指向同一个类
echo User::class, "\n";
echo namespace\User::class, "\n";
echo \App\Models\User::class, "\n";
// 不存在的相对名会去当前命名空间找
echo \strlen("abc"), "\n"; // 全局函数要加反斜杠App\Models\User
App\Models\User
App\Models\User
3strlen() 不加反斜杠也能用,但同名函数会被遮蔽,性能也略差。use 的几种形式
<?php
namespace App;
use App\Models\User; // 导入类
use App\Models\Post as BlogPost; // 别名
use function App\Helpers\format; // 导入函数
use const App\Config\DEBUG; // 导入常量
echo User::class, "\n";
echo BlogPost::class, "\n";
echo DEBUG ? "调试模式" : "生产模式", "\n";App\Models\User
App\Models\Post
调试模式use 只影响编译期的名字解析,不会触发文件加载。真正的加载靠自动加载器。
PSR-4 自动加载
PSR-4 的规则一句话:命名空间前缀映射到目录,类名映射到文件名。App\Models\User 对应 <根目录>/Models/User.php。
<?php
spl_autoload_register(function (string $class): void {
$prefix = "App\\";
$baseDir = __DIR__ . "/src/";
if (!str_starts_with($class, $prefix)) {
return;
}
$relative = substr($class, strlen($prefix));
$file = $baseDir . str_replace("\\", "/", $relative) . ".php";
if (is_file($file)) {
require $file;
}
});
echo "加载器已注册\n";加载器已注册实际项目不用手写这个——composer dump-autoload 会生成 vendor/autoload.php,require 一次即可。手写的价值在于理解它到底做了什么。
常见错误
- **
namespace必须是文件第一句**(declare除外),前面有 HTML 输出就报错 - **
use写在类里** ——use只能在文件顶层或类内导 trait,位置不同含义完全不同 - 同名类未加别名 ——
use A\User; use B\User;直接报冲突 - 字符串里的类名 ——
"App\Models\User"要转义反斜杠,或者用User::class