这个思考源自于一个事故. 让我对版本依赖重新思考了一下.
事故现象
一个线上的管理后台, 一个使用 Laravel 搭建的管理后台, 之前在线上跑的好好的, 今天 comopser install 之后, 出现错误信息:
[2019-02-25 16:00:33] production.ERROR: Parse error: syntax error, unexpected '?', expecting variable (T_VARIABLE) {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Parse error: syntax error, unexpected'?', expecting variable (T_VARIABLE) at /xxxx/application/estimate-admin/vendor/symfony/translation/Translator.PHP:89)
事故分析
这个是个底层库, 基本上, 一看就知道是版本兼容问题, 进去代码一看, 里面有行代码是 ?string, 这个是 php7.1 引入的一种新特性.
看了下我的 Composer.JSON, 里面主要引用的是 Laravel 的框架, 之前的 Laravel/framework 的版本是 "~5.5"
于是想当然以为是 Laravel 的版本升级导致的, 于是我把 Laravel 的版本固定到一个子版本
"laravel/framework": "5.5.21",
发现还是会出现这个错误. 估摸可能不是 Laravel 版本升级导致的. 于是从 Laravel 的版本依赖追到问题的包 "symfony/translation".
链条如下:
我的项目 "laravel/framework": "5.5.21",
- Laravel/framework "symfony/http-kernel": "~3.3",
- symfony/http-kernel(3.3.13 版本) "symfony/translation": "~2.8|~3.0",
- symfony/http-kernel(3.4 版本) "symfony/translation": "~2.8|~3.0|~4.0",
symfony/translation3.4 版本:
public function __construct($locale, $formatter = null, $cacheDir = null, $debug = false)
而在 4.0 的时候加入了 7.1 的特性
public function __construct(?string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false)
我机器上的版本是 PHP 7.0. 所以导致了在 Composer 升级的时候 symfony/http-kernel 也升级, 带来了 symfony/translation 升级到 4.x, 引入了 PHP7.1 的新特性.
解决方法
升级线上机器 PHP 版本是不可能的事情. 于是我只能强制限定版本号.
直接在最上层我的项目中 require symfony/translation, 并且指定版本号.
"symfony/translation" : "3.3.13"
重新 Composer update 就可以了.
思考
这是一个典型的依赖包升级导致的业务应用出错的案例. symfony/translation 从 3.3.13 升级到 4.*, 需要的 PHP 版本从 7.0 升级到 7.1. 这样的升级, Laravel/framework 版本 v5.5.21 是无感知的.
而我们看 Laravel/framework v5.5.21 的(comopser.JSON)[]
- {
- "name": "laravel/framework",
- "description": "The Laravel Framework.",
- ...
- "require": {
- "php": ">=7.0",
- "ext-mbstring": "*",
- "ext-openssl": "*",
- ...
- "symfony/http-kernel": "~3.3",
- },
- ...
- }
这里的 PHP>= 7.0 是不是格外扎眼, 根本已经不靠谱了.
真正解决办法
哈, 其实这里并没有结束. 这个问题包版本依赖其实各个包都没有问题.
其实这里有一个问题, 我打包机器的 PHP 版本是 7.1, 但是线上机器是 7.0.0, 所以会导致这个问题.
其实 Composer 比我们想象的更为强大. 它会根据你当前机器的 PHP 版本, 判断你的所有依赖分别使用什么版本, 在 Composer update 的时候, 会根据所有依赖的版本需求选择一个最好的版本.
所以我把我的打包机器上的 PHP 切换成 7.0, 查看生成的 Composer.lock, 里面的 symfony/translation 就限制到使用 3.3.x 版本 就不会出现这个问题了.
Composer 的正确使用姿势
是否要将 Composer.lock 加入到 Git 库
这个是我这次犯的一个错误, 没有将 Composer.lock 进入版本库, 打包机器 Composer install 的时候就相当于 update 操作了. 对于业务来说, 这个是不对的. 业务要做的事情是保证业务稳定性, 其实任何的库依赖的升级, 都需要经过业务的测试和验证才能上线. 所以, 这里强烈建议在业务项目里面, 将 Composer.lock 强制加入 Git 代码库中.
是否要使用自动升级
版本依赖的时候, 使用~,^ 符号会在 Composer udpate 的时候根据依赖包已经有的类库.
我理解自动升级的机制有好也有坏处, 这个就相当于把主动权 (这里已经说的是 update 的主动权) 放在哪里. 作为一个基础类库, 我当然希望你使用我的时候能相信我, 我的每次版本升级都是兼容的, 也不会引入 bug. 所以类库是会希望你会使用自动升级. 这样我的一些 bug 修复, 在你 update 的时候你就会自动下载并且修复了.
但是对于业务来说, 业务稳定是死要求. 一旦我 update 的时候, 我使用了你的新下载的包, 这个实际上就有可能引入一个 bug. 没有经过完整的测试, 是不应该做这种操作的.
但是实际上, 我们是无法完全杜绝这个情况, 比如你的一个 lib 包依赖了另外一个 lib 包的时候, 它如果使用了自动升级, 你是完全没有办法的.
所以一旦我们使用包依赖, 自动升级的事情, 是无法杜绝的.
慎用 update
使用 update 操作的时候, 必须想到会引发什么操作, 尽量将 Composer.lock 做下差异比对, 明白下前后两个依赖包差别在哪里.
总结
包依赖问题, 不仅 PHP 有, golang 也有, 基本注意点都是如上, 一样的.
来源: https://www.cnblogs.com/yjf512/p/10475938.html