composer.json 不只是“装依赖的清单”,它同时决定了项目如何声明自身、如何约束运行环境、怎样暴露命名空间,以及安装流程里能执行哪些脚本。很多初学者能照着示例写出文件,却不容易分清哪些字段影响发布,哪些字段只服务开发测试。下面按实际使用顺序拆开来看,读完后你可以快速判断一个 composer.json 是否完整、是否写得合理。
项目基础信息:先把包的身份说明白
一个典型的 composer.json,通常先从项目元数据开始:
{
"name": "phpoffice/phpspreadsheet",
"description": "Read, Create and Write Spreadsheet documents in PHP",
"keywords": ["PHP", "OpenXML", "Excel", "xlsx", "xls"],
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
}
]
}
这里最先要看的是 name。它是包名,一般由“作者名/项目名”组成,用 / 分隔。包名本身可以包含各种字符,甚至空格,而且不区分大小写;但如果考虑到安装、检索和后续协作,仍然建议使用简洁、稳定、尽量只含字母和数字的命名方式。
description 和 keywords 主要服务可读性与可搜索性。前者负责一句话说清项目用途,后者帮助用户在包平台中检索和筛选。对于要公开发布的库,这两项虽然不影响代码运行,但对可发现性很重要。
type 用来说明这个包是什么类型。默认值是 library,也是大多数包最常见的选择。Composer 原生支持的 4 种类型包括:
library:默认类型,通常安装到vendor目录中。project:表示当前包是一个完整项目,而不是供别人依赖的库。metapackage:只声明依赖关系,本身不包含文件,也不会向系统写入实际内容。composer-plugin:用于扩展 Composer 行为,可提供自定义安装逻辑或 installer。
如果你的包没有特殊安装需求,通常不必自定义类型,直接使用默认的 library 就够了。只有安装过程中确实需要额外逻辑时,才有必要使用更特殊的类型。
license 表示许可证。对于开源项目,这一项通常不能省略,常见写法如 MIT。选择时一般会参考 SPDX License List 中的标准标识符。
authors 则用于记录作者信息,常见字段包括 name、email、homepage、role。它不会影响依赖解析,但有助于项目维护和对外展示。
依赖项怎么分:运行必须与开发专用要分开写
很多人第一次接触 Composer,注意力都放在依赖安装上。这部分确实是 composer.json 的核心:

{
"require": {
"php": "^7.2",
"markbaker/complex": "^1.4",
"markbaker/matrix": "^1.2",
"psr/simple-cache": "^1.0"
},
"require-dev": {
"dompdf/dompdf": "^0.8.5",
"friendsofphp/php-cs-fixer": "^2.16",
"jpgraph/jpgraph": "^4.0",
"mpdf/mpdf": "^8.0",
"phpcompatibility/php-compatibility": "^9.3",
"phpunit/phpunit": "^8.5",
"squizlabs/php_codesniffer": "^3.5",
"tecnickcom/tcpdf": "^6.3"
}
}
require 表示运行项目必须安装的依赖。只要这里声明的条件不满足,Composer 就不会完成安装。除了第三方包,这里也经常直接约束运行环境,比如示例里的 "php": "^7.2",就是明确指定项目所需的 PHP 版本范围。
require-dev 则用于开发阶段和测试阶段的依赖。它们通常不会参与生产环境运行,但会在本地开发、代码规范检查、单元测试、文档生成等环节发挥作用。像示例中的 phpunit/phpunit、friendsofphp/php-cs-fixer、squizlabs/php_codesniffer,都属于非常典型的开发工具链依赖。
这两个字段分开的意义在于:运行环境只安装真正必需的内容,避免把测试框架、格式化工具之类一起带进生产环境。对于库项目来说,这种区分尤其重要,因为它直接影响安装体积、依赖树复杂度和下游使用体验。
自动加载映射:决定类名如何找到源码
依赖声明之外,另一个经常被忽视但非常关键的部分是自动加载配置:
{
"autoload": {
"psr-4": {
"PhpOffice\PhpSpreadsheet\": "src/PhpSpreadsheet"
}
},
"autoload-dev": {
"psr-4": {
"PhpOffice\PhpSpreadsheetTests\": "tests/PhpSpreadsheetTests"
}
}
}
autoload 用来告诉 Composer,项目中的类该如何映射到具体目录。它支持 PSR-4、PSR-0、classmap 以及 files 等方式,其中最推荐的仍然是 PSR-4。因为它结构清晰、约定明确,也是现代 PHP 项目里最主流的组织方式。
以上面这段配置为例,命名空间前缀 PhpOffice\PhpSpreadsheet\ 会映射到 src/PhpSpreadsheet 目录。也就是说,类名与目录结构之间存在明确对应关系,Composer 才能在运行时自动定位并加载相应文件。
autoload-dev 的定位和 require-dev 类似,主要服务开发和测试场景。示例里把测试相关的命名空间 PhpOffice\PhpSpreadsheetTests\ 映射到了 tests/PhpSpreadsheetTests,这样正式发布时可以把业务代码和测试代码的加载规则区分开,不会混在一起。
实际维护中,如果项目类找不到、命名空间混乱,问题往往不在 PHP 语法本身,而在这里的映射规则没有和目录结构保持一致。因此,自动加载配置最好尽早定型,不要等项目变大后再回头补。
脚本配置:把常用命令收进 Composer 入口
除了声明数据和加载规则,composer.json 还可以作为统一命令入口。Composer 允许在安装过程的不同阶段执行脚本,也支持自定义脚本:

{
"scripts": {
"test": "phpunit",
"fix": [
"php-cs-fixer fix --ansi"
]
}
}
这里的脚本既可以是 PHP 回调(通常定义为静态方法),也可以直接写命令行可执行命令。这样做的好处是,把团队常用操作统一收口到 composer 命令之下,减少额外记忆成本。
例如示例里定义了一个 test 脚本,那么执行:
composer test
就等同于直接运行 phpunit。同理,fix 也可以用来包装代码格式修复命令。对团队协作来说,这种写法的价值不只在“少敲几个字”,更在于让所有成员使用同一套约定好的入口。
需要注意的是,脚本机制适合承载项目内约定好的流程,例如测试、格式化、构建等;如果只是一次性的本地命令,没有必要为了“看起来完整”把所有操作都堆进 scripts。
读懂一个 composer.json,至少先检查这四类信息
如果你接手一个现成项目,想快速判断它的 composer.json 是否写得规范,可以先按四个方向检查:
- 项目身份是否清楚:
name、description、license、authors是否完整。 - 依赖边界是否明确:运行依赖放在
require,开发工具放在require-dev。 - 自动加载是否匹配目录:尤其是
PSR-4命名空间前缀和源码目录是否一一对应。 - 脚本入口是否实用:是否真的为测试、修复、构建等高频操作提供了统一命令。
从用途上看,composer.json 远不只是一个安装清单,而是 PHP 项目的基础配置说明书。把这些核心字段看懂之后,再去阅读更完整的官方文档,会更容易建立整体认识,也更容易看出别人项目中的配置意图。













