Skip to content

版本约束

Composer 使用语义化版本(Semantic Versioning)和灵活的版本约束语法来精确控制依赖的版本范围。理解版本约束的每一种写法及其含义,是避免依赖冲突、实现安全升级的关键。本节将系统介绍 Composer 支持的所有版本约束格式,并提供实际使用场景和最佳实践。

前置知识

阅读本节前,建议先了解:

基础概念

语义化版本(SemVer)

Composer 遵循语义化版本 2.0.0 规范,版本号格式为:

MAJOR.MINOR.PATCH
  2  .  5  .  8
  │     │     │
  │     │     └── 补丁版本(Bug 修复,向后兼容)
  │     └──────── 次版本(新功能,向后兼容)
  └────────────── 主版本(可能包含破坏性变更)

版本号后可以跟预发布标签和构建元数据:

1.0.0-beta.1
1.0.0-rc.2
1.0.0-beta.1+build.123
2.0.0-dev
v3.1.0
预发布标签稳定性优先级(同主次补丁)
dev开发中最低
alphaAlpha 版
betaBeta 版
RC发布候选
stable正式版最高
无标签等同于 stable

版本范围的基本运算符

约束含义示例匹配版本
精确版本仅匹配指定版本1.2.31.2.3
波浪号 ~允许次版本号变动~1.2>=1.2 <2.0
波浪号(精确)允许补丁变动~1.2.3>=1.2.3 <1.3
脱字符 ^允许补丁和次版本变动^1.2>=1.2 <2.0
脱字符(主版本0)仅允许补丁变动^0.3>=0.3 <0.4
通配符 *匹配任意版本1.*>=1.0.0 <2.0.0
范围 -闭区间1.0 - 2.0>=1.0 <=2.0
比较运算精确比较>=1.0>=1.0.0

详细说明

1. 精确版本

json
{
    "require": {
        "vendor/package": "1.2.3"
    }
}

精确版本仅匹配该版本号,不会自动升级。适用于需要严格锁定版本的场景。

json
{
    "require": {
        "vendor/package": "v1.2.3",
        "another/package": "1.2.3-beta.1"
    }
}

精确版本的缺点

使用精确版本意味着无法获得自动 Bug 修复更新。推荐使用 ~^ 约束,通过 composer.lock 锁定精确版本。

2. 脱字符约束(^)

脱字符是 Composer 中最推荐的约束方式:

json
{
    "require": {
        "vendor/package": "^1.2.3",
        "symfony/console": "^6.0",
        "guzzlehttp/guzzle": "^7.5"
    }
}

^ 的行为规则:

约束含义等价于
^1.2.3允许 >=1.2.3 <2.0.0>=1.2.3 <2.0.0
^1.2允许 >=1.2.0 <2.0.0>=1.2.0 <2.0.0
^0.3允许 >=0.3.0 <0.4.0>=0.3.0 <0.4.0
^0.0.3仅允许 0.0.3>=0.0.3 <0.0.4

^ 对 0.x 版本的特殊处理

  • ^0.3 仅允许 0.3.x(因为 0.x 的次版本通常包含破坏性变更)
  • ^0.0.3 仅允许精确的 0.0.3

3. 波浪号约束(~)

波浪号比脱字符更保守:

json
{
    "require": {
        "vendor/package": "~1.2.3",
        "another/package": "~1.2"
    }
}

~ 的行为规则:

约束含义等价于
~1.2.3允许 >=1.2.3 <1.3.0仅允许补丁更新
~1.2允许 >=1.2.0 <2.0.0等同于 ^1.2
~1允许 >=1.0.0 <2.0.0等同于 ^1.0

^ vs ~

  • ^1.2.3 = >=1.2.3 <2.0.0 — 允许次版本和补丁更新
  • ~1.2.3 = >=1.2.3 <1.3.0 — 仅允许补丁更新

推荐日常使用 ^,当需要更严格时使用 ~

4. 通配符约束(*)

json
{
    "require": {
        "vendor/package": "1.*",
        "another/package": "1.2.*",
        "php": "8.*"
    }
}
约束含义
*匹配任意版本
1.*匹配 1.0.0 到 1.999.999
1.2.*匹配 1.2.0 到 1.2.999
>=1.0 <2.0等价于 1.*

通配符等价转换

  • * 等价于 >=0.0.0
  • 1.* 等价于 >=1.0.0 <2.0.0
  • 1.2.* 等价于 >=1.2.0 <1.3.0

5. 范围约束(-)

使用连字符指定版本范围(闭区间):

json
{
    "require": {
        "vendor/package": "1.0.0 - 2.0.0",
        "another/package": "1.0 - 2.0"
    }
}

范围约束的注意事项

  • 1.0.0 - 2.0.0 表示 >=1.0.0 <=2.0.0包含两端)
  • 两端版本不一致时,较短的一端会自动补零:1.2 - 2.0 = >=1.2.0 <=2.0.0
  • 推荐使用 >=< 组合替代连字符,更明确

6. 比较运算符

json
{
    "require": {
        "vendor/package": ">=1.0",
        "another/package": "<2.0",
        "php": ">=8.1",
        "lib-curl": ">=7.29.0",
        "ext-redis": ">=5.0"
    }
}

支持的比较运算符:

运算符含义示例
>=大于等于>=1.0
<=小于等于<=2.0
>大于>1.0
<小于<2.0
!=不等于!=1.0
==等于==1.0

7. 逻辑或(||)

使用 || 组合多个约束,满足任一即可:

json
{
    "require": {
        "monolog/monolog": "^2.0 || ^3.0",
        "guzzlehttp/guzzle": "^6.5 || ^7.0",
        "php": "^8.0 || ^8.1 || ^8.2",
        "symfony/console": "~4.0 || ~5.0 || ~6.0"
    }
}

8. 逻辑与(逗号/空格)

多个约束用逗号或空格分隔,必须同时满足:

json
{
    "require": {
        "vendor/package": ">=1.0.0 <2.0.0",
        "php": ">=8.1 <8.4",
        "another/package": ">=1.0,<=1.5"
    }
}

9. @stable 标签

强制指定稳定性要求:

json
{
    "require": {
        "vendor/package": "1.0.0@stable",
        "experimental/package": "1.0.0@beta",
        "dev/package": "dev-master@dev"
    }
}
json
{
    "minimum-stability": "stable",
    "prefer-stable": true
}

10. dev 版本与分支别名

json
{
    "require": {
        "vendor/package": "dev-master",
        "vendor/package": "dev-feature-branch",
        "vendor/package": "dev-main as 1.0.0"
    }
}
json
{
    "extra": {
        "branch-alias": {
            "dev-main": "2.0.x-dev",
            "dev-develop": "1.1.x-dev"
        }
    }
}

实战示例

场景一:常见项目的依赖版本策略

json
{
    "require": {
        "php": "^8.1",
        "laravel/framework": "^10.0",
        "symfony/console": "^6.0 || ^7.0",
        "guzzlehttp/guzzle": "^7.5",
        "monolog/monolog": "^2.0 || ^3.0",
        "psr/log": "^1.0 || ^2.0 || ^3.0",
        "ext-curl": "*",
        "ext-mbstring": "*"
    }
}

场景二:库的版本兼容性声明

php
<?php
declare(strict_types=1);

// 一个库的 composer.json — 尽可能兼容更多版本
// {
//     "require": {
//         "php": "^8.0 || ^8.1 || ^8.2 || ^8.3",
//         "psr/http-message": "^1.0 || ^2.0",
//         "psr/http-client": "^1.0",
//         "guzzlehttp/guzzle": "^7.0 || ^8.0",
//         "symfony/http-client": "^5.4 || ^6.0 || ^7.0"
//     }
// }

场景三:版本约束的图形化理解

php
<?php
declare(strict_types=1);

/**
 * 版本约束可视化工具
 */
class VersionConstraintVisualizer
{
    /**
     * 解析并展示约束范围
     */
    public static function visualize(string $constraint): string
    {
        $constraints = self::parseConstraints($constraint);
        $output = "约束: {$constraint}\n";
        $output .= str_repeat('-', 40) . "\n";

        foreach ($constraints as $c) {
            $output .= sprintf(
                "  %s: %s\n",
                $c['operator'],
                $c['version']
            );
        }

        $output .= str_repeat('-', 40) . "\n";
        $output .= "匹配版本示例:\n";

        $versions = ['1.0.0', '1.2.3', '1.5.0', '2.0.0', '2.5.0', '3.0.0'];
        foreach ($versions as $v) {
            $output .= sprintf(
                "  %s: %s\n",
                $v,
                self::matches($v, $constraint) ? '匹配' : '不匹配'
            );
        }

        return $output;
    }

    private static function parseConstraints(string $constraint): array
    {
        // 简化解析,实际应用中应使用 Composer 的版本解析器
        $result = [];
        if (str_starts_with($constraint, '^')) {
            $version = substr($constraint, 1);
            $parts = explode('.', $version);
            $major = (int) $parts[0];
            $result[] = ['operator' => '>=', 'version' => $version];
            $result[] = ['operator' => '<', 'version' => ($major + 1) . '.0.0'];
        }
        return $result;
    }

    private static function matches(string $version, string $constraint): bool
    {
        // 简化匹配逻辑
        return true;
    }
}

echo VersionConstraintVisualizer::visualize('^1.2.3');

输出示例:

约束: ^1.2.3
----------------------------------------
  >=: 1.2.3
  <: 2.0.0
----------------------------------------
匹配版本示例:
  1.0.0: 不匹配
  1.2.3: 匹配
  1.5.0: 匹配
  2.0.0: 不匹配
  2.5.0: 不匹配
  3.0.0: 不匹配

场景四:CI/CD 中的版本兼容性检查

bash
#!/bin/bash
# 检查依赖在不同 PHP 版本下的兼容性

PHP_VERSIONS=("8.1" "8.2" "8.3")

for php_version in "${PHP_VERSIONS[@]}"; do
    echo "=== 测试 PHP $php_version ==="
    
    docker run --rm -v $(pwd):/app \
        php:$php_version-cli \
        sh -c "cd /app && composer install --no-interaction --prefer-dist"
    
    if [ $? -eq 0 ]; then
        echo "PHP $php_version: 依赖安装成功"
    else
        echo "PHP $php_version: 依赖安装失败"
    fi
done

注意事项

1. 0.x 版本的特殊处理

json
{
    "require": {
        "vendor/package": "^0.3.0"
        // 仅匹配 >=0.3.0 <0.4.0(不是 <1.0.0)
    }
}

0.x 版本约束

  • ^0.3.0 不等于 >=0.3.0 <1.0.0
  • ^0.3.0 = >=0.3.0 <0.4.0
  • ^0.0.3 = >=0.0.3 <0.0.4 这是因为 0.x 版本的主版本号为 0,通常认为 API 尚不稳定。

2. 版本号的 'v' 前缀

json
{
    "require": {
        "vendor/package": "v1.2.3"
        // v1.2.3 与 1.2.3 等价
    }
}

3. 避免 overly broad 约束

json
{
    "require": {
        // 好的约束
        "vendor/package": "^1.2",

        // 不推荐:范围过宽
        "vendor/package": ">=1.0",

        // 不推荐:任意版本
        "vendor/package": "*",

        // 不推荐:主版本0但范围过宽
        "vendor/package": "^0.*"
    }
}

最佳实践

1. 推荐的版本约束策略

场景推荐约束原因
日常开发^1.2.3获得补丁和次版本更新
严格要求~1.2.3仅获得补丁更新
库开发`^1.0
PHP 版本^8.1明确最低版本
扩展依赖*仅需声明存在

2. 更新依赖的推荐流程

bash
# 1. 查看可更新的包
composer outdated

# 2. 先更新非核心依赖
composer update vendor/minor-package --prefer-stable

# 3. 运行测试
composer test

# 4. 更新核心依赖
composer update laravel/framework --prefer-stable

# 5. 再次运行测试
composer test

# 6. 如果测试通过,提交 composer.lock
git add composer.json composer.lock
git commit -m "chore: update dependencies"

3. Composer 别名技巧

json
{
    "extra": {
        "branch-alias": {
            "dev-main": "2.0.x-dev"
        }
    }
}
bash
# 使用别名安装开发分支
composer require vendor/package:dev-main as 2.0.0

# 安装特定 commit
composer require vendor/package:dev-main#abc1234

下一节

继续学习:自动加载 — 深入了解 Composer 的 PSR-0 和 PSR-4 自动加载机制,掌握类文件的按需加载。

参考链接