Symfony 中如何禁用 OPcache 解决类名重复声明错误
结论
在 Debian、Nginx、PHP-FPM 环境中,OPcache 由 PHP 的 .ini 文件控制,Nginx、Laravel、Symfony、Artisan 和 Composer 都不会改变这一机制。Web 请求和 php artisan 使用不同的 PHP 配置:
- Nginx 通常把请求交给 PHP-FPM,读取
fpm配置。 php artisan route:list由 PHP CLI 执行,读取cli配置。
如果错误只在 php artisan route:list 中出现,可以先通过一次性参数关闭 CLI OPcache:
php -d opcache.enable=0 -d opcache.enable_cli=0 artisan route:list
命令仍报出相同错误时,通常是同一个类被加载了两次。接下来应检查重复的类定义、Composer 自动加载配置、文件名大小写冲突和 PHP 7.4 的预加载配置。
长期关闭 OPcache 不适合作为最终修复方案。生产环境关闭 OPcache 会降低 PHP 性能,而类名重复声明通常源于代码或自动加载问题。
先确认实际使用的配置文件
配置路径应以当前环境为准,不要直接套用网上的示例。先分别确认 CLI 和 PHP-FPM 加载了哪些配置。
查看 CLI 配置:
php --ini
php -i | grep -E 'Loaded Configuration|Scan this dir|opcache.enable|opcache.enable_cli|opcache.preload'
检查 OPcache 是否已加载:
php -m | grep -i opcache
检查当前 CLI 进程中的状态:
php -r 'var_dump(ini_get("opcache.enable"), ini_get("opcache.enable_cli"), ini_get("opcache.preload"));'
常见的 Debian PHP 7.4 安装可能使用以下配置文件:
/etc/php/7.4/cli/php.ini
/etc/php/7.4/cli/conf.d/10-opcache.ini
/etc/php/7.4/fpm/php.ini
/etc/php/7.4/fpm/conf.d/10-opcache.ini
/etc/php/7.4/mods-available/opcache.ini
文件名和数字前缀会随安装来源变化,最终应以 php --ini 和 PHP-FPM 实际加载的配置为准。
只为 Artisan 临时禁用 OPcache
排查 route:list 时,可以先用命令行参数关闭 OPcache,无需修改系统配置:
php -d opcache.enable=0 -d opcache.enable_cli=0 artisan route:list
也可以运行下面的命令检查状态:
php -d opcache.enable=0 -d opcache.enable_cli=0 \
-r 'var_dump(ini_get("opcache.enable"), ini_get("opcache.enable_cli"), opcache_get_status(false));'
相应配置通常会显示为 "0",opcache_get_status(false) 会返回 false。
PHP 7.4 的常见默认配置已经关闭 opcache.enable_cli。可以用下面的命令确认:
php -r 'var_dump(ini_get("opcache.enable_cli"));'
如果结果为 0,说明 Artisan 原本就没有使用 CLI OPcache。继续修改 PHP-FPM 的 OPcache 设置,通常不会改变 php artisan route:list 的运行结果。
持久禁用 CLI OPcache
确实需要长期关闭 CLI OPcache 时,可以在 CLI 的 OPcache 配置文件中加入:
opcache.enable_cli=0
实际文件可能是:
/etc/php/7.4/cli/conf.d/10-opcache.ini
保存配置后重新执行命令即可。每次运行命令都会启动新的 CLI 进程,因此不需要重启 Nginx 或 PHP-FPM。
在 Debian 中,还可以禁用 CLI SAPI 的 OPcache 模块链接:
sudo phpdismod -s cli opcache
恢复时运行:
sudo phpenmod -s cli opcache
phpdismod 和 phpenmod 是否可用,取决于 PHP 是否由 Debian 的打包系统安装。如果 PHP 来自第三方仓库或自行编译,应直接修改 php --ini 列出的配置文件。
为 Nginx 请求禁用 OPcache
Nginx 本身不运行 PHP。要影响 Web 请求,需要修改 PHP-FPM 的配置。修改 Nginx 配置或 Apache 的 .htaccess 都不会关闭 PHP-FPM 的 OPcache。
在 PHP-FPM 使用的 php.ini 或 OPcache 配置文件中设置:
opcache.enable=0
opcache.enable_cli=0
opcache.enable_cli 对 PHP-FPM 没有实际影响,保留该设置可以让配置意图更明确。
随后重启对应的 PHP-FPM 服务:
sudo systemctl restart php7.4-fpm
一般不需要重启 Nginx。修改完成后,可以创建一个仅允许本机访问的临时诊断页面:
<?php
var_dump(
PHP_SAPI,
php_ini_loaded_file(),
php_ini_scanned_files(),
ini_get('opcache.enable'),
ini_get('opcache.preload'),
opcache_get_status(false)
);
检查结束后应删除这个文件,以免公开配置路径和 PHP 环境信息。
如果系统中安装了多个 PHP 版本,还要确认 Nginx 的 FastCGI 配置连接了哪个套接字,例如:
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
否则,即使修改了 PHP 7.4 的配置,Nginx 也可能实际连接着另一个版本的 PHP-FPM。
检查 PHP 7.4 预加载配置
PHP 7.4 引入的 preload 与普通 OPcache 缓存机制不同。预加载脚本会在 PHP-FPM 启动时载入类,这些类会一直保留到 PHP-FPM 进程重启。
检查相关配置:
php -i | grep -E 'opcache.preload|opcache.preload_user'
再搜索配置文件:
grep -R "opcache.preload" /etc/php/7.4 2>/dev/null
如果找到类似配置:
opcache.preload=/path/to/preload.php
可以先将它注释掉:
;opcache.preload=/path/to/preload.php
然后重启 PHP-FPM:
sudo systemctl restart php7.4-fpm
对于已经预加载的类,单独执行 opcache_reset() 通常不足以完成排查,重启 PHP-FPM 更可靠。同时检查 preload 脚本是否通过 require 再次加载了已由 Composer 管理的类文件。
PHP-FPM 的 preload 不会直接影响独立启动的 php artisan CLI 进程。如果错误始终出现在 Artisan 中,应先检查自动加载配置和重复定义。
排查重复类定义
下面的错误:
Cannot declare class Acme, because the name is already in use
表示当前 PHP 进程中已经声明了同名类。仅凭这条错误,无法认定 Symfony 或 OPcache 存在缺陷。
先在项目中搜索类定义:
rg -n --glob '*.php' \
'class\s+Acme\b|interface\s+Acme\b|trait\s+Acme\b|enum\s+Acme\b' \
app src routes config database tests
PHP 7.4 不支持 enum。如果代码库还会在其他 PHP 版本上运行,可以保留这项搜索;对于只运行 PHP 7.4 的项目,主要检查 class、interface 和 trait。
还要搜索手工加载代码:
rg -n --glob '*.php' \
'require|require_once|include|include_once' \
app src routes config bootstrap
常见原因有:
- 两个文件在相同命名空间中声明了
Acme。 - 同一个文件既由 Composer 加载,又被手写的
require加载。 composer.json中的autoload.files与 PSR-4 自动加载范围重叠。autoload.classmap扫描了内容重复的目录。- 项目中同时保留了旧文件和重命名后的新文件。
- 文件名只有大小写不同,例如
Acme.php和acme.php。 - Git 中的大小写重命名没有正确反映到部署环境。
- preload 脚本和运行时自动加载器载入了同一份类代码。
- 项目安装或复制了两份包含相同类名的包。
检查 Composer 自动加载配置
先查看 composer.json 中的自动加载配置,例如:
{
"autoload": {
"psr-4": {
"App\\": "app/"
},
"classmap": [
"database/seeders",
"database/factories"
],
"files": [
"app/helpers.php"
]
}
}
避免用多个重叠目录扫描同一组类。下面的配置就可能引起重复或歧义:
{
"autoload": {
"psr-4": {
"App\\": "app/"
},
"classmap": [
"app/"
]
}
}
重新生成自动加载文件时,可以开启详细输出:
composer dump-autoload -o -vvv
Composer 可能会报告 ambiguous class resolution。遇到多个文件定义同一个类的提示时,应删除重复文件,或调整 autoload、autoload-dev、exclude-from-classmap 等配置,确保该类只有一个来源。
也可以直接检查 Composer 生成的映射:
rg -n "Acme" vendor/composer/autoload_*.php
如果同一个类出现在多个映射或文件列表中,需要继续检查对应的 composer.json 配置和包目录。
修正配置或源文件后,再清理应用缓存并重建映射:
php artisan optimize:clear
composer dump-autoload -o
php artisan route:list
optimize:clear 只能清除应用缓存,dump-autoload 只能重建自动加载映射。它们无法解决源代码中存在两个同名类的问题。
根据堆栈找到第二次声明的位置
PHP 的致命错误通常会给出第二次声明所在的文件和行号。先打开该文件,确认完整命名空间:
<?php
namespace App\Example;
class Acme
{
}
这个类的完整名称是:
App\Example\Acme
接着搜索完整命名空间和类名:
rg -n -F 'namespace App\Example;' .
rg -n 'class\s+Acme\b' .
搜索整个项目会包含 vendor 和缓存目录,结果可能很多。可以先检查业务代码,再根据需要扩大范围:
rg -n --glob '*.php' 'class\s+Acme\b' app src packages modules
如果报错文件位于 Symfony 或 Laravel 生成的缓存目录,不要直接修改生成文件。应继续查找生成它的服务定义、代理类、路由缓存或容器配置。
注意类名大小写问题
PHP 比较类名时不区分大小写,而 Linux 文件系统通常区分文件名大小写。因此,下面两个声明仍会发生冲突:
class Acme {}
class ACME {}
如果只修改文件名的大小写,Git 在某些开发环境中可能无法按预期记录这次重命名。可以分两步操作:
git mv app/Acme.php app/Acme.tmp
git mv app/Acme.tmp app/Acme.php
实际操作时,应把目标替换成正确的最终文件名,并确保类名、命名空间、PSR-4 路径及所有引用的大小写一致。
推荐的排查顺序
先确认 Artisan 是否启用了 OPcache:
php -r 'var_dump(ini_get("opcache.enable"), ini_get("opcache.enable_cli"), ini_get("opcache.preload"));'
然后临时关闭 OPcache,再次运行命令:
php -d opcache.enable=0 -d opcache.enable_cli=0 artisan route:list
如果错误消失,再检查 CLI 配置、预加载脚本和异常的 OPcache 设置,并在重启相应进程后重新测试。
如果错误没有变化,按下面的顺序继续排查:
composer dump-autoload -o -vvv
rg -n --glob '*.php' 'class\s+Acme\b' app src packages modules
rg -n "Acme" vendor/composer/autoload_*.php
php artisan optimize:clear
最终应确保 Acme 在同一个 PHP 进程中只有一个明确的声明来源。只有确认错误确实由某项 OPcache 或 preload 配置触发,才需要考虑长期关闭相关功能。
备注:内容仅供参考。
