2016年,Composer已经成为PHP生态中最主流的依赖管理工具,几乎所有现代PHP项目都在使用。

说起来,最早接触Composer,是在2014年左右,那时候,Composer还不是特别流行,很多PHP项目还是手动管理依赖,或者用PEAR。但是,随着PHP生态的发展,特别是Laravel、Symfony等现代PHP框架的流行,Composer逐渐成为了PHP依赖管理的标准。

刚开始用Composer的时候,觉得它很神奇,一行命令,就能把需要的依赖包下载下来,自动加载,很方便。但是,用着用着,就踩了很多坑,也积累了一些实战经验。

今天,就来聊聊Composer依赖管理的踩坑总结与实战经验。

一、Composer是什么

先简单介绍一下Composer是什么。

Composer是PHP的依赖管理工具,类似于Node.js的npm,Python的pip,Ruby的bundler。它可以帮我们管理项目的依赖包,声明项目需要哪些依赖包,然后自动下载、安装、更新这些依赖包,以及它们的依赖。

Composer的核心是两个文件:composer.json和composer.lock。

composer.json是项目的依赖声明文件,里面声明了项目需要哪些依赖包,以及版本约束,还有项目的自动加载配置、脚本、仓库地址等信息。

composer.lock是依赖锁定文件,里面记录了项目实际安装的依赖包的精确版本,以及它们的依赖关系。有了composer.lock,团队成员就能安装完全相同版本的依赖包,保证开发环境的一致性。

Composer的包仓库主要是Packagist(https://packagist.org),上面有大量的PHP开源包,我们可以从上面搜索、下载需要的包。当然,也可以配置私有仓库,或者使用本地路径、Git仓库等作为依赖来源。

二、基本使用

Composer的基本使用很简单。

1. 初始化项目

在项目根目录下,运行composer init,按照提示填写项目信息,就能生成composer.json文件。

当然,也可以手动创建composer.json文件,内容大概是这样的:

{
    "name": "vendor/project-name",
    "description": "项目描述",
    "type": "project",
    "require": {
        "php": ">=7.0",
        "monolog/monolog": "^1.0"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

2. 安装依赖

运行composer install,Composer会读取composer.json,解析依赖,下载安装需要的包,生成composer.lock文件,以及vendor目录(依赖包都安装在这里),还有autoload.php(自动加载文件)。

如果项目已经有composer.lock文件,那么composer install会按照composer.lock里记录的精确版本安装,保证版本一致。

3. 添加依赖

运行composer require vendor/package,就能添加一个新的依赖包,Composer会自动更新composer.json和composer.lock,下载安装这个包。

也可以指定版本,比如composer require vendor/package:^1.0

4. 更新依赖

运行composer update,会更新所有依赖包到符合版本约束的最新版本,同时更新composer.lock。

也可以只更新某个包,比如composer update vendor/package

注意:composer updatecomposer install的区别是,install是按照composer.lock安装,不会更新版本;update是更新到最新版本,会修改composer.lock。

5. 自动加载

安装完依赖后,在项目的入口文件里,引入vendor/autoload.php,就能自动加载所有依赖包的类,以及自己项目的类(按照composer.json里的autoload配置)。

require __DIR__ . '/vendor/autoload.php';

这样,就可以直接use依赖包的类,或者自己项目的类,不用手动require了。

三、版本约束

Composer的版本约束是一个很重要的概念,也是容易踩坑的地方。

Composer支持多种版本约束方式:

1. 精确版本

直接写版本号,比如1.0.0,表示只安装1.0.0这个版本。

2. 范围版本

用比较运算符指定范围,比如>=1.0.0>=1.0.0 <2.0.0>=1.0.0 <1.1.0 || >=1.2.0

3. 通配符版本

作为通配符,比如1.0.,表示1.0.x的所有版本。

4. 波浪号版本(~)

~1.2等价于>=1.2.0 <2.0.0~1.2.3等价于>=1.2.3 <1.3.0

波浪号的意思是,允许最后一位指定的部分升级。

5. 脱字符版本(^)

^1.2.3等价于>=1.2.3 <2.0.0^0.3.2等价于>=0.3.2 <0.4.0

脱字符的意思是,允许不修改第一位非零部分的升级,也就是遵循语义化版本(SemVer)的兼容更新。

语义化版本的格式是主版本号.次版本号.修订号,主版本号升级表示不兼容的API修改,次版本号升级表示向后兼容的功能新增,修订号升级表示向后兼容的问题修复。

所以,^1.2.3表示可以升级到1.x.x的最新版本,因为1.x.x都是向后兼容的;^0.3.2表示可以升级到0.3.x的最新版本,因为0.x.x还在开发阶段,次版本号升级可能不兼容。

脱字符版本约束是Composer默认使用的版本约束方式,也是推荐使用的方式,因为它遵循语义化版本,既能获得兼容更新,又不会引入不兼容的版本。

四、自动加载

Composer的自动加载也是一个重要的功能,也是容易踩坑的地方。

Composer支持多种自动加载方式:

1. PSR-4

PSR-4是PHP Standards Recommendations 4,是PHP-FIG制定的自动加载规范,也是现在最主流的自动加载方式。

PSR-4的配置方式是,命名空间前缀对应目录路径,比如:

"autoload": {
    "psr-4": {
        "App\\": "src/",
        "App\\Models\\": "src/models/"
    }
}

这样,App\User类就会从src/User.php加载,App\Models\User类就会从src/models/User.php加载。

PSR-4的好处是,命名空间和目录结构对应,清晰规范,而且支持多个目录映射同一个命名空间前缀,灵活方便。

2. PSR-0

PSR-0是旧的自动加载规范,现在已经被废弃,不推荐使用。PSR-0和PSR-4类似,但是命名空间的下划线会被转换为目录分隔符,而且命名空间前缀需要对应目录。

3. Classmap

Classmap是通过扫描指定目录或文件,生成类映射表,实现自动加载。比如:

"autoload": {
    "classmap": ["src/", "lib/"]
}

Classmap的好处是,不管类的命名空间和目录结构是否规范,都能自动加载,适合一些老项目或者不规范的项目。但是,每次新增类都需要运行composer dump-autoload重新生成映射表。

4. Files

Files是直接加载指定的文件,适合加载一些全局函数或者配置文件。比如:

"autoload": {
    "files": ["src/helpers.php", "src/constants.php"]
}

这些文件会在自动加载时被直接require,适合定义全局函数。

5. 开发环境自动加载

除了autoload,还有autoload-dev,用于开发环境的自动加载,比如测试类、开发工具类等,这些只在开发环境加载,生产环境不加载。

"autoload-dev": {
    "psr-4": {
        "Tests\\": "tests/"
    }
}

运行composer install --no-dev就不会安装开发依赖,也不会加载autoload-dev。

五、常见踩坑

在使用Composer的过程中,我踩了很多坑,总结一下常见的坑。

1. 版本约束太松或太紧

版本约束太松,比如用*或者dev-master,可能会安装到不兼容的版本,导致项目出错。版本约束太紧,比如精确版本,可能会导致依赖冲突,或者无法获得安全更新。

建议使用脱字符版本约束(^),遵循语义化版本,既能获得兼容更新,又不会引入不兼容的版本。

2. 忽略composer.lock

很多人不重视composer.lock,甚至把它加入.gitignore,这是不对的。composer.lock记录了项目实际安装的依赖版本,有了它,团队成员和部署环境就能安装完全相同版本的依赖,保证环境一致。

如果没有composer.lock,每个人运行composer install安装的版本可能不同,导致"在我机器上能跑"的问题。

所以,composer.lock一定要提交到版本库,不要忽略。

3. 直接修改vendor目录

有些人会直接修改vendor目录里的依赖包代码,这是非常不好的习惯。vendor目录是Composer管理的,运行composer install或update会覆盖vendor目录,修改会丢失。

如果需要修改依赖包的代码,正确的做法是:

  • 给依赖包提PR,贡献代码
  • Fork依赖包,维护自己的版本,然后通过Composer的仓库配置引用自己的版本
  • 使用Composer的patches功能,打补丁

4. 自动加载不生效

有时候,新增了类,但是自动加载不生效,找不到类。这通常是因为:

  • 命名空间和目录不对应(PSR-4)
  • 类名和文件名不对应
  • 用了classmap,但是没有运行composer dump-autoload重新生成映射表
  • 没有引入vendor/autoload.php

解决方法:检查命名空间和目录是否对应,类名和文件名是否对应,如果用了classmap,运行composer dump-autoload

5. 内存不足

运行composer install或update的时候,有时候会报内存不足的错误,特别是依赖比较多的项目。

解决方法:

  • 增加PHP的内存限制,比如php -d memory_limit=-1 composer.phar install
  • 使用Composer的缓存,避免重复下载
  • 优化依赖,减少不必要的依赖

6. 国内访问Packagist慢

在国内,访问Packagist有时候很慢,甚至无法访问,导致composer install或update很慢,或者失败。

解决方法:

  • 使用国内镜像,比如阿里云Composer镜像、腾讯云Composer镜像等
  • 配置Composer的仓库地址,指向国内镜像

配置方法:

composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

这样,全局配置Packagist的镜像地址,以后安装依赖就会从国内镜像下载,速度快很多。

7. 依赖冲突

有时候,添加新依赖的时候,会报依赖冲突的错误,因为新依赖需要的某个包的版本,和现有依赖需要的版本不兼容。

解决方法:

  • 升级或降级现有依赖,找到兼容的版本
  • 使用Composer的composer why-not命令,查看为什么不能安装
  • 如果实在无法解决,可以考虑替换依赖包

8. 生产环境安装开发依赖

部署到生产环境的时候,如果运行composer install,会安装开发依赖(require-dev里的包),比如PHPUnit、调试工具等,这些在生产环境不需要,会增加代码量,甚至可能有安全风险。

解决方法:生产环境运行composer install --no-dev --optimize-autoloader,不安装开发依赖,并且优化自动加载。

六、最佳实践

总结一些Composer的最佳实践。

1. 遵循语义化版本

自己的项目和包,遵循语义化版本(SemVer),版本号格式为主版本号.次版本号.修订号,主版本号升级表示不兼容的API修改,次版本号升级表示向后兼容的功能新增,修订号升级表示向后兼容的问题修复。

这样,别人依赖你的包的时候,就能用脱字符版本约束,安全地获得兼容更新。

2. 使用脱字符版本约束

依赖包的版本约束,推荐使用脱字符(^),遵循语义化版本,既能获得兼容更新,又不会引入不兼容的版本。

避免使用*dev-master等太松的版本约束,也避免使用精确版本等太紧的版本约束。

3. 提交composer.lock

composer.lock一定要提交到版本库,不要忽略,保证团队成员和部署环境安装相同版本的依赖。

4. 不要修改vendor目录

不要直接修改vendor目录里的依赖包代码,如果需要修改,用正确的方式(提PR、Fork、打补丁)。

5. 使用国内镜像

在国内,配置Composer使用国内镜像,提高安装速度。

6. 生产环境优化

生产环境运行composer install --no-dev --optimize-autoloader,不安装开发依赖,优化自动加载,提高性能。

7. 定期更新依赖

定期运行composer update,更新依赖到最新兼容版本,获得安全更新和bug修复。但是,更新后要充分测试,确保没有兼容性问题。

8. 使用composer scripts

Composer支持scripts配置,可以定义一些脚本命令,比如测试、构建、部署等,方便统一管理项目的常用命令。

"scripts": {
    "test": "phpunit",
    "build": "php build.php",
    "deploy": "php deploy.php"
}

然后运行composer test就能执行测试,composer build就能执行构建,很方便。

七、高级技巧

再分享一些Composer的高级技巧。

1. 私有仓库

如果公司有私有包,可以配置私有仓库,比如使用Satis、Toran Proxy、Private Packagist等,搭建自己的Composer私有仓库。

配置方法:

"repositories": [
    {
        "type": "composer",
        "url": "https://packagist.example.com"
    }
]

2. 路径依赖

开发的时候,可以使用本地路径作为依赖,方便调试。

"repositories": [
    {
        "type": "path",
        "url": "../my-package"
    }
]

这样,Composer会从本地路径安装依赖,修改本地路径的代码,项目里就能立即生效,方便开发调试。

3. Git依赖

也可以直接使用Git仓库作为依赖,指定分支或标签。

"repositories": [
    {
        "type": "git",
        "url": "https://github.com/vendor/package.git"
    }
],
"require": {
    "vendor/package": "dev-master"
}

4. 替换依赖

如果某个依赖包有问题,或者想用自己的版本替换,可以使用replace配置。

"replace": {
    "vendor/package": "self.version"
}

这样,Composer会认为你的项目提供了这个包,不会再安装它。

5. 提供依赖

如果你的项目提供了某个虚拟包(比如接口、规范),可以使用provide配置。

"provide": {
    "psr/log-implementation": "1.0.0"
}

6. 建议依赖

如果某个依赖是建议安装的,不是必须的,可以使用suggest配置。

"suggest": {
    "ext-memcached": "需要使用Memcached缓存时安装",
    "ext-redis": "需要使用Redis缓存时安装"
}

安装的时候,Composer会提示这些建议的依赖。

7. 平台配置

可以配置平台版本,模拟特定的PHP版本或扩展版本,用于兼容性检查。

"config": {
    "platform": {
        "php": "7.0.0",
        "ext-mbstring": "1.0.0"
    }
}

这样,Composer会按照配置的平台版本解析依赖,即使实际环境版本不同。

八、写在最后

Composer依赖管理:踩坑总结与实战经验。

Composer是PHP生态中非常重要的工具,它彻底改变了PHP项目的依赖管理方式,让PHP项目的依赖管理变得简单、规范、高效。

但是,Composer虽然好用,也有很多需要注意的地方,只有理解了它的原理和机制,才能用好它,避免踩坑,提高开发效率。

希望我的这些踩坑总结和实战经验,能对大家有所帮助,让大家在使用Composer的时候,少踩坑,更高效。

最后,用一句话结尾:

"工具是手段,不是目的。理解工具的原理,才能真正用好工具。"

愿大家都能用好Composer,写出更优雅、更高效的PHP代码。