PHP 基本语法 09|命名空间、引入与自动加载
为什么需要命名空间
没有命名空间时,全世界的 PHP 代码共享一个"全局符号表"。你写一个 Logger 类,用了某个库也定义了 Logger,就冲突了——而且报的是致命错误,且只在两个文件都被加载时才爆。
命名空间给符号加了前缀,把 Logger 变成 App\Logger 和 Vendor\Logger 两回事。
<?php
namespace App\Services;
class Mailer
{
public function send(): void {}
}
一条硬规则:namespace 必须是文件的第一条语句(declare 可以写在它前面,<?php 不算)。
<?php
declare(strict_types=1);
namespace App\Services; // ✅ declare 之后、其他任何代码之前
// 下面这行会直接报致命错误
// echo "x";
// namespace App; // Fatal error: Namespace declaration statement has to be the very first statement
三种引用方式
<?php
namespace App\Http;
// 1) 完全限定名:从全局根开始,最明确
$r = new \App\Repositories\UserRepository();
// 2) 限定名:相对当前命名空间,App\Http\Repositories\...
$r = new Repositories\UserRepository();
// 3) 非限定名:当前命名空间下的 UserRepository
$r = new UserRepository();
// 全局函数/类在命名空间里必须加反斜杠,否则会先找当前命名空间
$now = \time();
$arr = new \ArrayObject([]);
第 3 条是新手最常见的坑:在 namespace App; 里写 new Exception() 会被解析成 App\Exception,找不到就报"Class not found"。要么 use Exception;,要么写 \Exception。
use 导入
<?php
namespace App\Http;
use App\Repositories\UserRepository;
use App\Services\Mailer as MailService; // 起别名
use function App\Helpers\format_money; // 导入函数(PHP 5.6+)
use const App\Config\DEBUG; // 导入常量
class Controller
{
public function __construct(
private UserRepository $repo, // 直接用短名
private MailService $mail
) {}
}
分组导入(PHP 7.0):
<?php
use App\Repositories\{UserRepository, OrderRepository};
use App\{Services\Mailer, Support\Str};
PSR-4 自动加载:类名和文件路径的约定
现代 PHP 项目不再手写一堆 require。核心是一份 Composer 配置:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
规则是:命名空间前缀映射到目录,剩余部分按 \ 展开成子目录,类名就是文件名 + .php。
所以:
App\Services\Mailer→src/Services/Mailer.phpApp\Http\Controller\UserController→src/Http/Controller/UserController.php
文件内容:
<?php
namespace App\Services;
class Mailer {}
只要这三处(composer.json 的映射、目录结构、文件里的 namespace)一致,Composer 就能自动找到类。改完 composer.json 记得执行:
composer dump-autoload
# 或生产环境
composer dump-autoload --optimize --classmap-authoritative
--optimize 会生成类映射表,把"按路径找文件"变成"查表",生产环境启动能快不少。
不用 Composer 时的自动加载
<?php
spl_autoload_register(function (string $class): void {
$prefix = 'App\\';
if (str_starts_with($class, $prefix)) {
$path = __DIR__ . '/src/' . str_replace('\\', '/', substr($class, strlen($prefix))) . '.php';
if (is_file($path)) {
require $path;
}
}
});
这段代码就是 PSR-4 的核心逻辑,理解了它,Composer 的生成文件也就不神秘了。
include / require:四个关键字的差别
| 找不到文件时 | |
|---|---|
include |
警告(Warning),继续执行 |
require |
致命错误(Fatal),停止执行 |
include_once |
同上,但已加载过就跳过 |
require_once |
同上,但已加载过就跳过 |
<?php
require_once __DIR__ . '/config.php';
引入业务文件一律用 require,不要用 include——找不到配置文件还继续跑,只会引发更难查的错误。
_once 的重复判断是按"解析后的真实路径"来的,所以软链接可能造成判断失效。现代项目用自动加载,_once 的需求基本消失了。
__DIR__ / __FILE__:路径一定用它们
<?php
echo __FILE__; // 当前文件的绝对路径
echo __DIR__; // 当前文件所在目录(等价 dirname(__FILE__))
echo __LINE__; // 当前行号
require __DIR__ . '/../vendor/autoload.php';
不要用相对路径 require './config.php'。相对路径是相对于"当前工作目录",而工作目录取决于你在哪儿执行 php 命令,不是文件的所在位置。这是"本地能跑、线上报错"的经典原因。
__DIR__ 在 5.3 之前不存在,那时候靠 dirname(__FILE__),老代码里能看到这种写法。
函数与常量不被命名空间"包住"的例外
这一点经常让人迷惑:函数和常量在找不到时,会自动回退到全局空间(类不会)。
<?php
namespace App;
echo strlen("abc"); // ✅ 正常,全局函数自动回退
echo MY_CONST; // ✅ 全局常量也能回退
new Exception(); // ✗ 类不回退,报 Class not found
这个回退机制带来一个性能细节:在热点循环里调用全局函数时,加 \ 前缀会略快,因为省掉了"先找当前命名空间"的查找。
<?php
namespace App;
for ($i = 0; $i < 1000000; $i++) {
$len = \strlen("x"); // 比 strlen("x") 少一次查找
}
差异很小,但在这类百万级循环里能测出来。别为此改整个项目,热点循环里注意一下就行。
一个完整的骨架
<?php
declare(strict_types=1);
namespace App\Http\Controller;
use App\Repositories\UserRepository;
use App\Support\Response;
use Psr\Http\Message\ServerRequestInterface;
final class UserController
{
public function __construct(
private readonly UserRepository $users,
) {}
public function show(ServerRequestInterface $req): Response
{
$id = (int)($req->getQueryParams()['id'] ?? 0);
$user = $this->users->find($id);
if ($user === null) {
return Response::notFound("用户不存在");
}
return Response::json($user);
}
}
src/Http/Controller/UserController.php 这个路径,和 App\Http\Controller\UserController 这个类名,是一一对应的。整个项目的结构就是这么长出来的。
坑清单
namespace前有任何输出(包括空行之后的echo、BOM 空白)都会报致命错误。- 命名空间里
new Exception()报 Class not found,要\Exception或use Exception。 - 相对路径
require './x.php'依赖工作目录,线上必炸,用__DIR__。 - 用
include引入必需的配置文件,出错只是警告,然后代码继续在半残状态下跑。 - 改完 composer.json 不
dump-autoload,新类找不到。 - 文件名大小写和类名不一致,Linux 上找不到(Windows 上能找到,部署到 Linux 就挂)。
- 一个文件写多个命名空间(用
{}语法)虽然合法,但工具链和可读性都不友好。
顺带说一句第 6 条:这是"本地开发好好的、上线就 500"的头号原因。Windows 的文件系统不区分大小写,Linux 区分。/src/Services/mailer.php 里声明 class Mailer,在 Windows 能找到,在 Linux 就挂。开发环境用 Docker 跑 Linux 容器,能提前发现这类问题。