应使用接口+工厂模式替代version_compare()硬编码分支,将版本差异抽象为实现类(如LegacyJsonEncoder/NativeJsonEncoder),由工厂一次性判断后返回实例,避免逻辑撕裂、测试难覆盖及静态分析失效。

PHP 版本分支判断(如 if (version_compare(PHP_VERSION, '8.0', '>=')))不是临时方案,而是架构腐化的明确信号——它意味着同一份逻辑被硬编码撕裂,维护成本随版本增长非线性上升。
用接口 + 工厂替代 if/else 版本判断
直接在业务逻辑里写版本判断,会导致测试难覆盖、IDE 无法跳转、静态分析失效。核心思路是把“行为差异”抽象为接口,让工厂按运行时环境返回具体实现。
- 定义统一接口,比如
JsonEncoderInterface,声明encode()和decode() - 为 PHP 7.4 写
LegacyJsonEncoder(用json_encode($data, JSON_THROW_ON_ERROR)模拟异常) - 为 PHP 8.0+ 写
NativeJsonEncoder(直接调用json_encode(),依赖原生错误抛出) - 工厂类
JsonEncoderFactory::create()里只做一次version_compare(),返回对应实例,后续全走接口调用
避免在 Composer autoloader 中触发版本判断
很多人把版本判断塞进 autoload.php 或服务提供者构造函数里,结果导致 CLI 命令、单元测试、甚至 composer dump-autoload 都被迫加载不相关逻辑。
- 版本适配代码必须延迟加载:只在第一次调用
JsonEncoderFactory::create()时执行判断 - 不要在
composer.json的autoload或autoload-dev里 require 任何含版本分支的文件 - 如果必须兼容 PHP 7.3–8.3,用 Composer 的
platform配置锁死最低要求,而非运行时降级
警惕 function_exists() 和 class_exists() 的隐式分支
这类检查看似轻量,但会掩盖真正的兼容性问题,且无法被 Psalm/PHPStan 推导类型流。
-
function_exists('str_contains')不等于 “可用”,因为 PHP 8.0 才引入该函数,但它的语义(返回 bool)和 polyfill(如 symfony/polyfill-php80)的行为可能不一致 - 优先使用
use function引入 polyfill 函数,并在composer.json中显式 requiresymfony/polyfill-php80,而不是运行时探测 - 对
class_exists('Stringable')这类判断,应直接依赖php >=8.0或改用interface_exists()+ 显式实现,而非动态判断
重构后仍需验证的三个真实断点
架构再优雅,没过这几个点就等于没落地。
- CLI 环境下执行
php -d extension=opcache.so -d opcache.enable_cli=1 artisan optimize:clear,确认无版本判断提前触发 - 在 PHP 7.4 容器中跑单元测试,确保
LegacyJsonEncoder被调用且异常路径覆盖完整 - 用
phpstan analyse --level max检查,所有JsonEncoderInterface使用处不能出现混合类型警告
最易被忽略的是「工厂缓存」——JsonEncoderFactory::create() 必须静态缓存实例,否则每次调用都重复 version_compare(),既无意义又干扰性能分析。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











