Search by

likun-mci / php-composer

likun.work

用原生 PHP 实现的 Composer 包管理器:依赖求解、下载安装、autoload 生成全程只走 HTTP,不调用任何 shell 命令。为禁用 exec/proc_open 的共享主机与受管控服务器而写,产出的 vendor 目录与官方 Composer 完全兼容,支持 HTTP/SOCKS5 代理。

Package info

github.com/likun-mci/php-composer

pkg:composer/likun-mci/php-composer

Statistics

Installs: 10

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.2.4 2026-09-07 06:59 UTC

This package is auto-updated.

Last update: 2026-09-07 07:01:37 UTC


README

用原生 PHP 实现的 Composer 包管理器。全程通过 HTTP 工作,不调用任何 shell 命令

很多共享主机、虚拟主机和受管控的生产服务器禁用了 exec/shell_exec/proc_open, 导致官方 composer 命令行完全无法运行。本项目把 composer 的核心能力(依赖求解、 下载安装、autoload 生成)用纯 PHP 重写,只依赖 HTTP 请求即可完成全部包管理工作。

产出的 vendor/ 目录与官方 composer 完全兼容:文件布局、composer.lock 格式、 autoload.php 结构、Composer\Autoload\ClassLoaderComposer\InstalledVersions 的命名空间都保持一致。两边可以互相接手同一个项目。

环境要求

项目 要求 说明
PHP >= 7.1 源码只用 7.1 语法;str_contains() 等新函数由 src/polyfill.php 补齐
ext-json / ext-mbstring 必需
ext-curl 建议 有则并行下载;没有则自动回退到 allow_url_fopen
ext-zip 建议 有则用 ZipArchive;没有则用内置的纯 PHP 解压
ext-openssl 必需 访问 HTTPS 包源

只要 curl 与 allow_url_fopen 至少有一个可用,就能工作。 网络受限环境可配置 HTTP / SOCKS5 代理,两条传输路径都支持,详见网络代理

安装

方式一:直接放到目标机器(推荐)

这个库本身零外部依赖,下载解压即可用——目标机器跑不了 composer 命令正是它存在的理由。

git clone https://github.com/likun-mci/php-composer.git
# 或下载 zip 解压到任意目录
require '/path/to/php-composer/autoload.php';

方式二:在能用 composer 的开发机上安装

composer require likun-mci/php-composer

快速开始

作为类库使用

require '/path/to/php-composer/autoload.php';   // 零外部依赖,不需要先跑 composer

use PhpComposer\Composer;

$composer = new Composer('/path/to/your/project');

// 创建 composer.json
$composer->init(['name' => 'acme/demo']);

// 添加依赖(不写版本则自动选取最新稳定版)
$composer->requirePackage('monolog/monolog', '^3.0');

// 按 composer.lock 安装
$composer->install();

// 之后正常使用
require '/path/to/your/project/vendor/autoload.php';

每个方法都返回结构化数组,便于对接:

[
    'ok'      => true,
    'message' => '依赖安装完成。',
    'data'    => [
        'summary'    => ['install' => 2, 'update' => 0, 'uninstall' => 0],
        'operations' => [ ['type' => 'install', 'package' => 'psr/log', ...], ... ],
        'packages'   => [ ['name' => 'psr/log', 'version' => '3.0.2', ...], ... ],
        'elapsed'    => 12.34,
    ],
    'log'     => ['安装 psr/log 3.0.2', ...],
]

作为 HTTP 接口使用

把项目放到 Web 可访问目录,配置环境变量后请求 api/index.php

export PHPCOMPOSER_PROJECT=/var/www/myapp   # 被管理的项目目录
export PHPCOMPOSER_TOKEN=your-secret-token  # 访问令牌(务必设置)
# 添加依赖
curl -X POST http://your-host/api/require \
     -H 'X-Auth-Token: your-secret-token' \
     -H 'Content-Type: application/json' \
     -d '{"packages": {"monolog/monolog": "^3.0"}}'

# 查看已安装
curl http://your-host/api/show -H 'X-Auth-Token: your-secret-token'

直接跑 composer 命令行(推荐)

已经有一条现成的 composer 命令时,不要自己把它翻译成方法调用 —— 交给 Application

use PhpComposer\Console\Application;

$app = new Application('/path/to/project', [], $io);

// 字符串或 argv 数组都行
$result = $app->run('update --prefer-dist --with-all-dependencies vendor/pkg');
$result = $app->run(['update', '--prefer-dist', '-W', 'vendor/pkg']);

返回结构与各 API 方法一致(ok / message / data / log)。

不认识的参数会直接报错,不会被忽略。 这一条是刻意的:手工映射参数时 漏掉一个开关,表现是「命令报成功但什么都没做」,比报错难查得多。

支持的选项

命令 选项
install --dry-run --no-dev --no-autoloader -o/--optimize-autoloader -a/--classmap-authoritative --apcu-autoloader --ignore-platform-reqs --ignore-platform-req=X --prefer-dist --prefer-source --prefer-install= --download-only
update install 的全部,外加 -w/--with-dependencies -W/--with-all-dependencies --prefer-stable --prefer-lowest --lock --no-install --root-reqs
require --dev --no-update --no-install --update-no-dev --fixed --sort-packages -w/-W --prefer-stable --prefer-lowest --dry-run --ignore-platform-reqs
remove --dev --no-update --no-install --update-no-dev --unused --dry-run
show -D/--direct -t/--tree -l/--latest -o/--outdated -P/--path -N/--name-only -p/--platform -s/--self --locked --ignore=
outdated -D/--direct --strict -m/--minor-only -p/--patch-only -M/--major-only --ignore=
dump-autoload -o/--optimize -a/--classmap-authoritative --apcu --no-dev
validate --strict --no-check-all --no-check-lock --no-check-publish
search -N/--only-name -t/--type=

全局选项 -n/--no-interaction--no-ansi-q/--quiet-v/-vv/-vvv--no-plugins--no-scripts--no-cache-d/--working-dir= 各命令都接受 (前几个只影响命令行观感,对库没有作用,接受但不做事)。

命令别名与 composer 一致:i u/upgrade req rm info cc 等。

不支持的--prefer-source 会退回 dist 并给出提示(源码安装要跑 git); composer 本身那些需要执行外部命令的命令(exec run-script self-update 等)不在范围内, 调用会明确报错而不是静默跳过。

类库 API

每个方法最后都接一个可选的 array $options,键名与命令行长选项同名 (with-all-dependenciesno-installignore ……)。走 Application 时它由命令行自动填好。

方法 对应命令 说明
init(array $data, bool $overwrite) composer init 创建 composer.json
install(bool $dev, bool $dryRun, array $options) composer install 按 lock 精确安装;无 lock 时自动转 update
update(array $only, bool $dev, bool $dryRun, array $options) composer update 重新求解;$only 可做部分更新,支持 vendor/* 通配
requirePackage($packages, $constraint, bool $dev, bool $dryRun, array $options) composer require 添加依赖并安装
removePackage($packages, bool $dev, bool $dryRun, array $options) composer remove 移除依赖
show(?string $package, bool $onlyDirect, array $options) composer show 列出已安装 / 单包详情
outdated(bool $onlyDirect, bool $compatible, array $options) composer outdated 列出可升级的包
search(string $query, int $limit, ?string $type, array $options) composer search 搜索包
versions(string $package, int $limit) composer show -a 查询包的全部可用版本
dumpAutoload(?bool $optimize, bool $dev, array $options) composer dump-autoload 重新生成自动加载文件
validate(array $options) composer validate 校验 composer.json
status() composer status 项目与运行环境状态
clearCache() composer clear-cache 清空缓存
useMirror(string $name) composer config repo 切换包源镜像

HTTP 接口

所有接口返回 {ok, message, data, log} 结构。鉴权用 X-Auth-Token 头 (也接受 Authorization: Bearer <token>)。

方法 路径 主要参数
GET /
GET /status
POST /init name, description, type, license, overwrite
POST /install dev, dry-run
POST /update packages, dev, dry-run
POST /require packages, version, dev, dry-run
POST /remove packages, dry-run
GET /show package, direct
GET /outdated direct, compatible
GET /search q, limit, type
GET /versions package, limit
POST /dump-autoload optimize, dev
GET /validate
POST /clear-cache
POST /mirror name
POST /run command(一整行命令,或 argv 列表)

除表里列的参数外,各接口还接受与命令行长选项同名的参数 (with-all-dependenciesno-installprefer-lowestignore ……), 下划线写法也认(with_all_dependencies)。也可以统一塞进 options 对象里。

/run 直接吃一整行 composer 命令,参数不必逐个映射:

curl -X POST http://127.0.0.1:8080/run \
  -H 'X-Auth-Token: <token>' \
  -d 'command=update --prefer-dist --with-all-dependencies vendor/pkg'

packages 参数接受三种写法:

{"packages": "monolog/monolog"}
{"packages": ["monolog/monolog", "psr/log"]}
{"packages": {"monolog/monolog": "^3.0", "psr/log": "^3.0"}}

配置

配置来自三处,优先级由低到高:内置默认值 → 项目 composer.jsonconfig 段 → 构造 Composer 时传入的覆盖项。

$composer = new Composer('/path/to/project', [
    'repo-url'             => 'https://mirrors.aliyun.com/composer',
    'timeout'              => 60,
    'concurrency'          => 8,      // 并行下载数
    'optimize-autoloader'  => true,
    'sort-packages'        => true,
    'preferred-install'    => 'dist',
    'disable-curl'         => false,  // 强制走 stream
    'secure-http'          => true,   // 拒绝明文 HTTP
    'platform'             => ['php' => '8.1.0'],  // 按目标服务器求解,见下
]);

内置镜像

$composer->useMirror('aliyun');  // packagist / aliyun / tencent / huawei / ustc

按目标服务器求解

开发机和生产服务器 PHP 版本不同时,用 config.platform 让求解按生产环境进行:

{
    "config": {
        "platform": {
            "php": "8.1.0",
            "ext-redis": false
        }
    }
}

false 表示「假装这个扩展不存在」。

网络代理

支持 HTTP / HTTPS / SOCKS4 / SOCKS4a / SOCKS5 / SOCKS5h 六种代理, 且 有无 curl 扩展都能用——没有 curl 时由内置的 socket 传输层自己完成 SOCKS 握手与 HTTP CONNECT 隧道(PHP 自带的 stream 代理选项做不到这两件事)。

new Composer($dir, [
    // 统一代理
    'proxy' => 'socks5h://127.0.0.1:1080',

    // 或按协议分别指定(优先于 proxy)
    'http-proxy'  => 'http://127.0.0.1:8080',
    'https-proxy' => 'socks5://127.0.0.1:1080',

    // 例外名单:支持精确主机、子域、CIDR、带端口、通配符
    'no-proxy' => 'localhost, .internal.corp, 10.0.0.0/8, example.com:8080',

    // 凭据也可从地址里拆出来单独写
    'proxy-user'      => 'username',
    'proxy-password'  => 'password',
    'proxy-auth-type' => 'basic',   // basic(默认) / digest / ntlm / negotiate / any
]);

凭据也可以直接内嵌在地址里:socks5h://user:pass@127.0.0.1:1080

socks5 与 socks5h 的区别socks5 在本地解析域名,socks5h 把域名交给代理解析。 内网 DNS 不可用时(受限主机的常见情况)必须用 socks5h

环境变量也会被读取,优先级低于上面的配置项:

export HTTPS_PROXY=socks5h://127.0.0.1:1080
export HTTP_PROXY=http://127.0.0.1:8080
export ALL_PROXY=socks5://127.0.0.1:1080     # 前两者的兜底
export NO_PROXY=localhost,.internal.corp

不想读环境变量就设 'use-env-proxy' => false

安全提示:在 CGI/FPM 下运行时,请求头 Proxy: 会被转成 HTTP_PROXY 环境变量,任何访问者都能借此劫持出站流量(httpoxy / CVE-2016-5385)。 本项目在非 CLI 环境下会忽略 HTTP_PROXY;确需在 Web 环境用它, 请改设 CGI_HTTP_PROXYHTTPS_PROXY 等其它变量不受此影响。

排查代理问题时,status() 会回显当前生效的代理(密码已打码):

$composer->status()['data']['proxy'];
// ['http' => 'socks5h://user:***@127.0.0.1:1080', 'https' => ..., 'no_proxy' => ...]

HTTP API 的每个接口也都接受 proxyno-proxyproxy-user 等参数按请求覆盖。

私有仓库与认证

new Composer($dir, [
    'auth' => [
        'satis.example.com' => ['type' => 'basic', 'username' => 'u', 'password' => 'p'],
        'gitlab.example.com' => ['type' => 'bearer', 'token' => 'xxx'],
    ],
]);

composer.json 里的 repositories 支持 composer(Satis / 私有 Packagist) 和 package(内联声明)两种类型。

设计说明与已知边界

只走 dist,不走 source。 source 安装需要 git/svn 命令,与本项目的前提冲突。 因此 repositories 里的 vcs/git/path 类型会被跳过并给出提示,请改用 composer 类型的仓库(如 Satis)。绝大多数 Packagist 包都提供 dist,不受影响。

不执行 scripts。 composer.jsonscripts 段会被原样保留但不会执行—— 执行它们恰恰需要本项目所不具备的命令执行能力。若依赖安装后的脚本步骤,需自行处理。

不加载插件。 composer-plugin 类型的包会被正常安装,但其插件逻辑不会生效 (如 composer/installers 的自定义安装路径)。

ZIP64 需要 zip 扩展。 内置的纯 PHP 解压不支持 ZIP64 格式(单文件 >4GB 或 条目数 >65535)。遇到时会明确报错提示启用 zip 扩展。实际的 composer 包不会触及这个边界。

依赖求解用回溯搜索,不是官方的 SAT 求解器。对常见依赖图(包括需要多层降级的场景) 结果一致;对人为构造的病态依赖图,回溯次数上限为 20000 次,超出会明确报错而不是卡死。

目录结构

src/
  Composer.php            对外门面
  Config.php              配置(含内置镜像表)
  Api/Router.php          HTTP 路由(与传输层解耦,可直接当函数调用)
  Semver/                 版本规范化与约束解析(^ ~ * || - 全套语义)
  Package/                包实体
  Json/                   composer.json 读写;JsonManipulator 做保留格式的最小化修改
  Http/                   curl / stream 双实现,含并行请求与并行下载
  Http/Proxy/             代理解析、no-proxy 匹配、SOCKS/CONNECT 传输层
  Repository/             Packagist v2、已安装、平台包仓库
  DependencyResolver/     候选池、回溯求解器、安装事务
  Downloader/             dist 下载与校验
  Installer/              安装、卸载、vendor/bin 入口生成
  Autoload/               autoload 生成器与类映射扫描
  Lock/                   composer.lock 读写与 content-hash
  Util/                   文件系统、缓存、ZIP、平台探测
res/
  ClassLoader.php         生成到 vendor/composer/ 的类加载器
  InstalledVersions.php   生成到 vendor/composer/ 的运行时查询 API
api/index.php             HTTP 入口
autoload.php              类库入口(bootstrap.php 是等价别名)
tests/                    测试脚本

参与开发

启用 git hooks

仓库自带两个钩子,克隆后执行一次即可启用:

git config core.hooksPath .githooks
  • pre-commit(秒级):对暂存文件做 PHP 语法检查、JSON 合法性校验, 拦截误混入的西里尔/希腊/亚美尼亚字符(这类字符是合法的常量名,php -l 查不出来, 要到运行时才抛 Undefined constant);改动 composer.json 时还会做 composer validate --strict 并核对包名与 GitHub 仓库路径是否一致。
  • pre-push(约 15 秒):并行做全量语法检查,并跑完离线测试套件。

确认无误要跳过时用 git commit --no-verify / git push --no-verify

测试

composer run test           # 离线测试,pre-push 与 CI 都跑这一组
composer run test-network   # 需要联网:真实下载安装包,验证完整生命周期

离线测试:

php tests/semver_test.php              # 版本约束语义(81 个用例)
php tests/manipulator_test.php         # composer.json 保留格式的修改(12 个用例)
php tests/solver_backtrack_test.php    # 依赖求解与回溯(14 个场景)
php tests/proxy_test.php               # 代理解析、no-proxy、环境变量、httpoxy 防护(21 个用例)
php tests/classmap_test.php            # 类名提取与 classmap 生成(23 个用例)
php tests/polyfill_test.php            # polyfill 与原生实现对拍(66 个用例)
php tests/php71_compat_test.php        # PHP 7.1 语法兼容性(防止用上 7.2+ 语法)
php tests/console_test.php             # 命令行解析,重点是未知参数必须报错(29 个用例)
php tests/options_test.php             # 命令行选项的端到端语义(42 个用例)
php tests/update_test.php              # update 不被 lock 里的旧版本堵死(9 个用例)

需要联网的放在 tests/network/,刻意不参与 tests/*_test.php 的匹配—— 它们要真实访问 Packagist 并下载包,耗时且会被网络波动干扰, 不适合卡在提交与 CI 路径上:

php tests/network/lifecycle_test.php   # 完整生命周期:init→require→install→remove(30 项)
php tests/network/solver_test.php      # 对真实依赖树求解

发布到 Packagist

  1. 包名必须与 GitHub 仓库路径一致。本仓库是 likun-mci/php-composercomposer.jsonname 也必须是它,否则 Packagist 会拒绝收录。 pre-commit 钩子与发布 workflow 都会核对这一点。

  2. 首次收录:登录 packagist.org → Submit → 填 https://github.com/likun-mci/php-composer

  3. 配置自动同步(二选一即可,建议都配):

    • Packagist 的 GitHub 集成:在 Packagist 个人设置里授权 GitHub, 之后推送会自动触发抓取;
    • 仓库 Secrets:在 GitHub 仓库的 Settings → Secrets 里添加 PACKAGIST_USERNAMEPACKAGIST_API_TOKEN(Token 在 Packagist 个人资料页获取),.github/workflows/publish-packagist.yml 会在打标签时 主动通知 Packagist,并轮询确认该版本确实被收录。
  4. 发版:

git tag v1.0.0
git push origin v1.0.0     # 触发 publish-packagist workflow

未配置 Secrets 时 workflow 不会失败,只会提示跳过主动通知, 然后照常校验 Packagist 是否已收录该标签——收录与否由实际查询结果说了算。