prepend-autoloader生效的前提是框架未提前注册autoloader且mock类通过autoload-dev的psr-4精确映射;若框架先注册loader或类已被加载,则Composer loader无法触发,覆盖失效。

启用 prepend-autoloader 本身不能解决同名类覆盖问题,真正起效的前提是:项目框架没提前注册自己的 autoloader,且你要覆盖的类已通过 autoload-dev 的 psr-4 显式映射——否则 prepend-autoloader 只是把一个无效的加载器插到了前面。
为什么 prepend-autoloader 看起来不生效
根本原因不是配置没加,而是 PHP 的自动加载机制按 spl_autoload_register() 的注册顺序触发。如果框架(如 Laravel、ThinkPHP)在 require vendor/autoload.php 之前就调用了自己的 spl_autoload_register(),那它的 loader 就会先命中项目目录下的 AppHttpControllerUserController.php,Composer 的 loader 根本没机会运行。
- 检查入口文件(如
public/index.php)里require __DIR__.'/../vendor/autoload.php';是否在所有框架初始化代码之前 - Laravel 9+ 默认已前置 autoload,但自定义
bootstrap/app.php或早期版本可能绕过它 - 运行
php -r "print_r(spl_autoload_functions());"查看当前注册顺序,确认 Composer 的 loader 是否排第一
怎么让测试替身类真正优先加载
关键不在“插到最前”,而在“让 Composer 的 loader 能匹配到你的类”。必须用 autoload-dev + psr-4 精确声明命名空间与路径,而不是依赖 classmap 或模糊映射。
-
composer.json中只在autoload-dev下配,不要动autoload:"autoload-dev": { "psr-4": { "App\Http\Controller\": "tests/mocks/app/Http/Controller/" } } -
tests/mocks/app/Http/Controller/UserController.php的 namespace 必须是AppHttpController,不能少反斜杠或大小写错误 - 执行
composer dump-autoload --dev --optimize,--dev确保加载autoload-dev配置 - 绝对不要手动
require原始类文件——这会绕过 autoloader,导致覆盖失效
哪些操作会让覆盖彻底失败
即使配置全对,以下行为也会让同名类覆盖失效,且很难排查:
- 在入口文件中
require了项目目录下的类(比如app/Http/Controller/UserController.php),PHP 会直接定义该类,后续 autoloader 再也不会触发 - 用
classmap方式映射 mock 类:它不解析 namespace,只认文件路径,无法支持子命名空间或接口实现 - 多个项目共用同一份
vendor/目录:autoload_psr4.php里的路径是硬编码的__DIR__,跨项目加载时指向错误物理路径 - CI 构建机上没执行
composer dump-autoload --dev,导致测试环境压根没加载 mock 映射
最易被忽略的一点:autoload 是静态注册行为,一旦某个类被加载过,PHP 就不会再尝试其他 loader。所以「覆盖」只发生在类第一次被引用时;如果之前已被框架 loader 加载过,后续任何配置都无效——必须重启 PHP 进程(如 FPM reload 或 CLI 新进程)才能重试。











