Composer本身不提供热重载能力,所谓“热重载”本质是通过监听文件变化、重注册autoloader、禁用classmap权威模式及OPcache等缓存,在下次请求时重新执行PSR-4路径解析,而非运行时替换已加载类。

Composer 本身不提供热重载能力,所谓“热重载脚本”,本质是绕过 composer dump-autoload 的手动流程,用监听 + 重注册 + 环境适配组合出开发态的类加载刷新效果。
为什么直接跑 composer dump-autoload 不行
每次执行该命令会重写 vendor/autoload.php 和 vendor/composer/autoload_classmap.php,但已加载的类不会被卸载——PHP 不允许运行时替换类定义。更关键的是:dump-autoload -o 或 -a 生成的优化文件是静态映射,改完代码后不重跑就找不到新类;而默认模式(无参数)虽支持 PSR-4 实时路径解析,但若 autoloader 被缓存(如 APCu、OPcache),或 loader 实例被复用且未重置查找状态,依然会加载旧版本。
php-watch 配合手动重注册 loader
这是轻量、可靠、不侵入框架的常见做法。核心不是“重载类”,而是确保下一次请求从头走一遍 PSR-4 查找逻辑。
- 安装:用
npm install -g php-watch(推荐)或composer global require symfony/cli后用symfony server:start --watch=src/,config/ - 启动时强制禁用权威 classmap:
$loader = require __DIR__.'/vendor/autoload.php'; if (getenv('APP_ENV') === 'dev') { $loader->setClassMapAuthoritative(false); $loader->setApcuPrefix(null); } - 避免在 CLI 模式下误触发:检查
php_sapi_name() === 'cli'再执行重置逻辑,否则php-watch自身进程会反复 reload 自己
Docker 环境下 inotify + FPM 重载的坑
在容器里跑 inotifywait 监听 /var/www/html 并 kill -USR2 1 看似自动,实际有三个硬伤:
-
USR2是 PHP-FPM 的平滑重启信号,它会 fork 新 worker,但旧 worker 仍处理完手头请求——这意味着你改完代码后,可能连续几次请求还命中旧进程里的旧类 - 如果用了 OPcache,
opcache_invalidate()必须显式调用,且需配置opcache.enable_cli=1和opcache.file_update_protection=0,否则无效 - Volume 挂载在 macOS 上对 inotify 事件支持不稳定,常漏触发;改用
watchexec -r --exts php,env --on-change "kill -USR2 1"更健壮
真正要警惕的“伪热重载”陷阱
很多方案号称热重载,却在关键环节断链:
- 自己写了个简易 PSR-4 加载器,但没
require vendor/autoload.php→vendor/里的包无法加载,monolog、guzzle全挂掉 - 在 Laravel 中用
php artisan serve+ 文件监听 → 它底层是 PHP 内置服务器,每次请求都重新 include,看似热,实则掩盖了真实环境(Nginx + FPM)下的缓存问题 - 把配置读进静态属性或常量里(如
self::$config = config('app');)→ 即使类重载了,配置还是旧的,根本没刷新
热重载不是魔法,它是对加载时机、缓存层级、进程生命周期的一次次精确控制。最易被忽略的,其实是 OPcache 的 opcache.revalidate_freq 默认设为 2 秒——你改完代码立刻刷新,可能还在用 2 秒前的 opcode 缓存。


















