2016年,我已经用Composer三年了。
2013年,我第一次接触Composer。那时候,PHP的依赖管理还是一片混乱,每个项目手动下载库文件,手动require,版本升级全靠复制粘贴。Composer的出现,简直是PHP开发者的福音。
从那以后,Composer就成了我PHP开发中不可或缺的工具。每个新项目,第一件事就是composer init,然后composer require各种库。三年下来,我用Composer管理过几十个项目,上百个依赖。
但是,用了三年,我也踩了不少坑。今天,就来聊聊这些坑,以及我总结出来的经验。
一、Composer是什么
先简单介绍一下Composer,给还没用过的朋友。
Composer是PHP的依赖管理工具,类似于Node.js的npm,Python的pip,Ruby的bundler。它可以帮你:
- 管理项目的依赖库,自动下载和安装
- 处理依赖之间的版本关系,自动解决冲突
- 自动生成autoloader,不用手动require
- 管理项目的自动加载
用Composer之前,PHP项目引入第三方库,通常是这样的:
- 去官网下载库的zip包
- 解压到项目的某个目录
- 在代码里require库的入口文件
- 如果这个库还依赖其他库,还要手动下载那些库
- 升级的时候,重新下载,重新覆盖
这个过程非常痛苦,特别是依赖多的时候,版本冲突、文件覆盖、路径问题,各种麻烦。
用了Composer之后,这些问题都解决了。你只需要在composer.json里声明你需要哪些库,然后运行composer install,Composer就会自动帮你下载所有依赖,处理版本关系,生成autoloader。你只需要在代码里require 'vendor/autoload.php',就能用所有的库了。
Composer的出现,是PHP生态的一个里程碑。它让PHP的依赖管理变得和其他现代语言一样简单,也促进了PHP社区的繁荣。现在,几乎所有的PHP框架和库,都支持Composer。
二、版本约束的正确姿势
用Composer,最容易踩的坑,就是版本约束。
很多人写版本约束,喜欢写"vendor/package": "dev-master",或者"vendor/package": "*"。这样写,看起来很方便,永远用最新版。但是,这是非常危险的。
因为,库的新版本,可能会有不兼容的改动(Breaking Change)。如果你用dev-master或者*,每次composer update,都会拉取最新的代码,可能突然就不兼容了,项目就挂了。
我刚用Composer的时候,就踩过这个坑。有一个项目,依赖写的是"monolog/monolog": "dev-master"。有一天,我运行composer update,Monolog升级了,API变了,我的代码里用的方法被移除了,项目直接500错误。查了半天才发现是Monolog升级的问题。
从那以后,我再也不用dev-master和*了。
那么,版本约束应该怎么写呢?
Composer支持多种版本约束格式:
1. 精确版本 "vendor/package": "1.2.3" — 只用1.2.3这个版本,不会升级。最安全,但是太死板,不能享受bug修复和小版本更新。
2. 通配符 "vendor/package": "1.2.*" — 用1.2.x的最新版本,不会升级到1.3。比较安全,能享受1.2系列的bug修复。
3. 波浪号(~) "vendor/package": "~1.2" — 等价于>=1.2,<2.0,会升级到1.x的最新版本,但不会升级到2.0。"~1.2.3"等价于>=1.2.3,<1.3。
4. 脱字符(^) "vendor/package": "^1.2.3" — 等价于>=1.2.3,<2.0,和~1.2.3类似,但是更遵循语义化版本。对于1.0以下的版本,^0.3等价于>=0.3.0,<0.4。
推荐用法:
- 对于遵循语义化版本的库,用脱字符
^,比如"^1.2.3"。这样能升级到1.x的最新版本,享受新功能和bug修复,又不会升级到不兼容的2.0。 - 对于不遵循语义化版本的库,用通配符
或者波浪号~,限制在小版本内,比如"1.2."或者"~1.2.3"。 - 绝对不要用
dev-master和*,除非你明确知道自己在做什么。
另外,还有一个重要的点:composer.json里的版本约束,是"可以接受的版本范围",而实际安装的版本,是由composer.lock文件锁定的。这就引出了下一个坑——lock文件。
三、composer.lock的重要性
很多人,特别是新手,会忽略composer.lock文件,甚至把它加到.gitignore里。这是一个非常大的错误。
composer.lock文件,记录了项目当前实际安装的所有依赖的精确版本号。有了这个文件,任何人在任何环境运行composer install,都会安装和你完全一样的版本,不会出现"我这里能跑,你那里不能跑"的问题。
如果没有composer.lock,运行composer install的时候,Composer会根据composer.json里的版本约束,解析出最新的符合条件的版本,然后安装。这就意味着,不同时间、不同环境安装的版本可能不一样,可能会出现版本不一致导致的问题。
我踩过这个坑。以前有个项目,团队里有人把composer.lock加到了.gitignore里。结果,我本地开发的时候,依赖的版本是A,测试环境部署的时候,依赖的版本是B,因为B比A新,有个小改动,导致测试环境出了bug,而我本地复现不了。查了很久,才发现是版本不一致的问题。
从那以后,我所有的项目,都会把composer.lock提交到版本库,绝对不会忽略它。
最佳实践:
composer.json和composer.lock都要提交到版本库- 团队成员拉取代码后,运行
composer install(不是update),安装lock文件里锁定的版本 - 只有当你明确要升级某个依赖的时候,才运行
composer update vendor/package,然后提交更新后的composer.lock - 不要直接运行
composer update(不带参数),那样会升级所有依赖,可能会引入不兼容的改动
记住:install是按照lock文件安装,保证版本一致;update是按照json文件解析最新版本,会升级依赖。日常开发用install,只有明确要升级的时候才用update。
四、autoload的原理
Composer的另一个强大功能,是自动生成autoloader。你只需要require 'vendor/autoload.php',就能自动加载所有的类,不用手动require。
但是,很多人不知道autoload的原理,也踩过一些坑。
Composer的autoload,支持四种方式:
1. PSR-4 最推荐的方式。按照命名空间和目录的映射关系,自动加载类。比如:
"autoload": {
"psr-4": {
"App\\": "src/"
}
}这表示,App命名空间下的类,都在src/目录下。比如App\Controllers\HomeController,对应的文件就是src/Controllers/HomeController.php。
2. PSR-0 旧的标准,已经不推荐了。和PSR-4类似,但是命名空间的分隔符会被转换成目录分隔符,而且支持下划线转换。
3. classmap 扫描指定的目录和文件,生成类名到文件路径的映射表。这种方式加载速度快,但是每次新增类都要重新生成映射。
4. files 手动指定需要加载的文件,每次请求都会加载。适合加载一些全局函数文件。
常见的坑:
坑1:改了autoload配置不生效 很多人改了composer.json里的autoload配置,但是没有运行composer dump-autoload,导致配置不生效,类加载不到。
记住:每次改了autoload配置,都要运行composer dump-autoload重新生成autoloader。
坑2:生产环境不用优化 默认情况下,Composer的autoloader是用PSR-4的规则,每次加载类的时候,都要去文件系统里找文件,性能不是最优的。在生产环境,可以用优化模式,生成classmap,提高加载速度。
运行composer dump-autoload --optimize,或者在安装的时候加--optimize-autoloader参数,就能生成优化的autoloader。
我以前的项目,生产环境没有优化autoloader,后来加了优化之后,接口响应速度提升了10%左右。虽然不是很多,但是对于高并发的项目,还是有意义的。
坑3:开发环境用了优化模式 和上面相反,开发环境不要用优化模式。因为优化模式生成了classmap,如果你新增了类,没有重新生成classmap,类就加载不到。开发环境用默认模式就行,改了autoload配置再运行composer dump-autoload。
五、其他常见的坑
除了上面说的版本约束、lock文件、autoload,用Composer还有一些其他的坑。
坑1:内存不足 Composer在解析依赖的时候,会占用很多内存。特别是依赖多的项目,运行composer update的时候,可能会出现内存不足的错误。
解决方法:
- 增加PHP的内存限制:
php -d memory_limit=-1 composer.phar update - 用
composer require添加单个依赖,而不是修改json后运行composer update - 定期清理Composer的缓存:
composer clear-cache
我遇到过很多次内存不足的问题,特别是在依赖比较多的大项目里。后来我都是用php -d memory_limit=-1来运行Composer,就再也没遇到过了。
坑2:国内镜像慢 Composer默认的Packagist仓库在国外,国内访问很慢,有时候甚至连不上。运行composer install,可能要等几十分钟,甚至失败。
解决方法:用国内镜像。
目前比较稳定的国内镜像有:
- 阿里云Composer镜像:
https://mirrors.aliyun.com/composer/ - 腾讯云Composer镜像:
https://mirrors.cloud.tencent.com/composer/ - 华为云Composer镜像:
https://mirrors.huaweicloud.com/repository/php/
配置方法:
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/这样配置之后,全局都会用阿里云镜像,速度快很多。
我从2014年开始用国内镜像,那时候Packagist在国内访问特别慢,经常超时。用了国内镜像之后,composer install从几十分钟变成了几分钟,体验好了很多。
坑3:版本冲突 有时候,两个依赖需要同一个库的不同版本,就会出现版本冲突。Composer会报错,告诉你哪个依赖需要哪个版本,无法解决。
解决方法:
- 仔细看错误信息,找到冲突的依赖和版本
- 升级或降级其中一个依赖,让它们需要的版本兼容
- 如果实在无法解决,可以用
conflict或者replace来强制指定版本(不推荐,可能会有问题)
版本冲突是比较头疼的问题,特别是依赖多的项目。我的经验是,尽量用比较新的、维护活跃的库,这些库通常会及时更新,和其他库的兼容性也比较好。
坑4:require和require-dev搞混 composer.json里有两个部分:require和require-dev。require是生产环境需要的依赖,require-dev是开发环境需要的依赖(比如测试框架、代码检查工具)。
很多人把所有依赖都写在require里,包括开发工具。这样,生产环境也会安装这些开发工具,不仅浪费空间,还可能有安全隐患。
正确的做法是:
- 生产环境需要的库,写在
require里 - 只有开发环境需要的库(phpunit、phpcs、php-cs-fixer等),写在
require-dev里 - 生产环境部署的时候,用
composer install --no-dev,不安装开发依赖
我以前的项目,就把phpunit写在了require里,生产环境也安装了phpunit,虽然没什么大问题,但是不规范。后来改到了require-dev里,部署的时候加--no-dev,就规范了。
坑5:不看changelog就升级 很多人运行composer update,升级依赖,但是不看changelog(更新日志)。结果,升级之后,API变了,代码不兼容了,项目挂了。
正确的做法是:升级依赖之前,先看一下这个库的changelog,了解新版本有哪些改动,有没有不兼容的地方。如果有不兼容的改动,要先修改你的代码,再升级。
特别是大版本升级(比如从1.x升到2.x),通常都会有不兼容的改动,一定要仔细看changelog,做好测试再升级。
六、团队协作和部署中的最佳实践
最后,总结一下团队协作和部署中的最佳实践。
团队协作:
composer.json和composer.lock都要提交到版本库- 团队成员拉取代码后,运行
composer install,不要运行composer update - 新增依赖,用
composer require vendor/package,不要手动修改json - 升级依赖,用
composer update vendor/package,然后提交更新后的lock文件 - 代码审查的时候,也要审查composer.json和composer.lock的改动
部署:
- 生产环境用
composer install --no-dev --optimize-autoloader,不安装开发依赖,优化autoloader - 部署脚本里,不要运行
composer update,只运行composer install - 如果部署环境不能联网,可以在本地打包vendor目录,然后上传到服务器
- 部署前,在测试环境测试通过,再部署到生产环境
日常维护:
- 定期运行
composer outdated,查看哪些依赖有新版本 - 有新版本的时候,评估是否需要升级,看changelog,做好测试
- 定期清理不用的依赖,保持依赖列表干净
- 关注依赖的安全公告,有安全漏洞及时升级
七、写在最后
Composer用了三年,这些依赖管理的坑你踩过吗?
三年里,我踩了很多坑,也学到了很多东西。从最开始的dev-master乱用,到现在的版本约束、lock文件、autoload优化,我对Composer的理解越来越深,用得也越来越熟练。
Composer是一个伟大的工具,它彻底改变了PHP的依赖管理,促进了PHP生态的繁荣。作为PHP开发者,我们应该掌握好这个工具,了解它的原理,避开那些坑,让它更好地为我们服务。
希望这篇文章,能帮到正在用Composer的你。如果你也踩过什么坑,或者有什么经验,欢迎在评论区分享。
最后,用一句话总结:Composer虽好,但是不要乱用。版本约束要合理,lock文件要提交,autoload要优化,升级要看changelog。做到这些,你就能用好Composer,避开那些坑。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录