Packagist 发布
Packagist 是 PHP 生态系统的官方包仓库,类似于 Node.js 的 npm registry 或 Python 的 PyPI。将你的 PHP 包发布到 Packagist 后,其他开发者就可以通过 composer require vendor/package 来安装使用。本节将完整介绍从创建可发布的包到在 Packagist 上发布的全流程。
基础概念
Packagist 的角色
你的 GitHub/GitLab 仓库
↓
Packagist(包索引/元数据)
↓
Composer 客户端(搜索、解析、安装)
↓
你的项目(使用 composer require 安装)Packagist 本身不存储代码,它是一个元数据索引服务。代码仍然存储在 Git 仓库中(GitHub、GitLab、Bitbucket 等)。
包名规范
vendor/package
│ │
│ └── 包名(小写字母、数字、连字符)
└── 命名空间(对应 GitHub/GitLab 用户名或组织名)示例:
monolog/monologsymfony/consoleguzzlehttp/guzzlespatie/laravel-permissionmyorg/http-client
包名规范
- 全部小写
- 只包含字母、数字和连字符
- - 不能以连字符开头或结尾
- vendor 部分建议与 GitHub/GitLab 用户名一致
详细说明
第一步:创建符合 Composer 规范的库
my-http-client/
├── composer.json # 包的核心配置
├── src/
│ └── HttpClient.php # 库的源代码
├── tests/
│ └── HttpClientTest.php
├── .gitignore
├── LICENSE
└── README.mdcomposer.json 配置
json
{
"name": "myorg/http-client",
"description": "A lightweight HTTP client wrapper for PHP 8.1+",
"version": "1.0.0",
"type": "library",
"keywords": ["http", "client", "guzzle", "wrapper", "php"],
"license": "MIT",
"authors": [
{
"name": "Zhang San",
"email": "zhangsan@example.com",
"homepage": "https://example.com",
"role": "Developer"
}
],
"support": {
"email": "support@example.com",
"issues": "https://github.com/myorg/http-client/issues",
"forum": "https://github.com/myorg/http-client/discussions",
"wiki": "https://github.com/myorg/http-client/wiki",
"source": "https://github.com/myorg/http-client",
"docs": "https://github.com/myorg/http-client/blob/main/README.md"
},
"require": {
"php": "^8.1",
"guzzlehttp/guzzle": "^7.5",
"psr/http-message": "^1.0 || ^2.0",
"psr/http-client": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^10.0",
"phpstan/phpstan": "^1.10",
"nyholm/psr7": "^1.5"
},
"suggest": {
"ext-curl": "For better HTTP performance"
},
"autoload": {
"psr-4": {
"MyOrg\\HttpClient\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"MyOrg\\HttpClient\\Tests\\": "tests/"
}
},
"minimum-stability": "stable",
"prefer-stable": true,
"config": {
"sort-packages": true,
"allow-plugins": {}
},
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
}
}包的核心字段说明
| 字段 | 是否必须 | 说明 |
|---|---|---|
name | 必须 | 包名,格式 vendor/package |
description | 推荐 | 包的简短描述 |
version | 可选 | 通常由 Git tag 自动推断 |
type | 推荐 | library(包)或 project(项目) |
keywords | 可选 | 搜索关键词,有助于被发现 |
license | 推荐 | 许可证标识 |
authors | 推荐 | 作者信息 |
support | 可选 | 获取支持的渠道 |
require | 必须 | 依赖声明 |
autoload | 必须 | 自动加载配置 |
第二步:编写源代码
php
<?php
declare(strict_types=1);
namespace MyOrg\HttpClient;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\ResponseInterface;
class HttpClient
{
private Client $client;
public function __construct(
?string $baseUri = null,
float $timeout = 30.0,
array $headers = []
) {
$config = array_filter([
'base_uri' => $baseUri,
'timeout' => $timeout,
'headers' => $headers,
], fn($v) => $v !== null);
$this->client = new Client($config);
}
/**
* 发送 GET 请求
*/
public function get(string $uri, array $query = [], array $headers = []): ResponseInterface
{
return $this->request('GET', $uri, [
'query' => $query,
'headers' => $headers,
]);
}
/**
* 发送 POST 请求
*/
public function post(string $uri, array $data = [], array $headers = []): ResponseInterface
{
return $this->request('POST', $uri, [
'json' => $data,
'headers' => $headers,
]);
}
/**
* 发送 PUT 请求
*/
public function put(string $uri, array $data = [], array $headers = []): ResponseInterface
{
return $this->request('PUT', $uri, [
'json' => $data,
'headers' => $headers,
]);
}
/**
* 发送 DELETE 请求
*/
public function delete(string $uri, array $query = [], array $headers = []): ResponseInterface
{
return $this->request('DELETE', $uri, [
'query' => $query,
'headers' => $headers,
]);
}
/**
* 发送通用请求
*/
public function request(string $method, string $uri, array $options = []): ResponseInterface
{
try {
return $this->client->request($method, $uri, $options);
} catch (GuzzleException $e) {
throw new HttpException(
sprintf('HTTP 请求失败: %s', $e->getMessage()),
(int) $e->getCode(),
$e
);
}
}
/**
* 获取原始 Guzzle 客户端(高级用途)
*/
public function getGuzzleClient(): Client
{
return $this->client;
}
}php
<?php
declare(strict_types=1);
namespace MyOrg\HttpClient;
use RuntimeException;
class HttpException extends RuntimeException
{
}第三步:编写测试
php
<?php
declare(strict_types=1);
namespace MyOrg\HttpClient\Tests;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use MyOrg\HttpClient\HttpClient;
use PHPUnit\Framework\TestCase;
class HttpClientTest extends TestCase
{
private HttpClient $client;
protected function setUp(): void
{
$mock = new MockHandler([
new Response(200, [], '{"status":"ok"}'),
new Response(200, [], '{"created":true}'),
]);
$handler = HandlerStack::create($mock);
$this->client = new HttpClient(null, 30.0, [
'handler' => $handler,
]);
}
public function testGetRequest(): void
{
$response = $this->client->get('/api/test');
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('{"status":"ok"}', (string) $response->getBody());
}
public function testPostRequest(): void
{
$response = $this->client->post('/api/users', ['name' => 'Test']);
$this->assertSame(200, $response->getStatusCode());
}
}第四步:创建 Git 仓库和标签
bash
# 初始化 Git 仓库
cd my-http-client
git init
# 创建 .gitignore
cat > .gitignore << 'EOF'
/vendor/
.phpunit.result.cache
.php-cs-fixer.cache
composer.lock
*.cache
EOF
# 添加远程仓库
git remote add origin https://github.com/myorg/http-client.git
# 提交代码
git add .
git commit -m "Initial release v1.0.0"
# 推送到 GitHub
git push -u origin main
# 创建版本标签
git tag -a v1.0.0 -m "Release 1.0.0"
git push origin v1.0.0版本号与 Git 标签
Packagist 从 Git 标签读取版本号。标签格式必须符合语义化版本规范:
v1.0.0(推荐,带 v 前缀)1.0.0(也可以,不带前缀)1.0.0-beta.1(预发布版本)2.0.0-RC.1(发布候选)
第五步:在 Packagist 上注册
方式一:通过网站注册
- 访问 https://packagist.org/
- 点击 "Submit" 按钮
- 登录(使用 GitHub 账号)
- 输入仓库 URL:
https://github.com/myorg/http-client - 点击 "Check" 然后点击 "Submit"
方式二:使用 GitHub 集成(推荐)
- 登录 Packagist
- 进入 Settings → GitHub
- 授权 Packagist 访问你的 GitHub 仓库
- 之后每次推送 tag 时自动更新
bash
# 启用 GitHub 自动更新后
# 推送新版本标签时 Packagist 会自动更新
git tag -a v1.1.0 -m "Release 1.1.0"
git push origin v1.1.0
# Packagist 自动检测并更新包信息第六步:验证发布
bash
# 搜索你的包
composer search myorg/http-client
# 查看包信息
composer show myorg/http-client
# 安装测试
composer require myorg/http-client:^1.0
# 验证自动加载
php -r "
require 'vendor/autoload.php';
\$client = new MyOrg\HttpClient\HttpClient();
echo get_class(\$client) . PHP_EOL;
"实战示例
场景一:发布新版本
bash
#!/bin/bash
# release.sh — 发布新版本脚本
VERSION=$1
if [ -z "$VERSION" ]; then
echo "用法: ./release.sh <version>"
echo "示例: ./release.sh 1.2.0"
exit 1
fi
# 1. 运行测试
echo "=== 运行测试 ==="
composer test
if [ $? -ne 0 ]; then
echo "错误: 测试失败,中止发布"
exit 1
fi
# 2. 运行代码检查
echo "=== 代码检查 ==="
composer check
if [ $? -ne 0 ]; then
echo "错误: 代码检查未通过,中止发布"
exit 1
fi
# 3. 更新版本号
echo "=== 更新版本号 ==="
# 更新 composer.json 中的 version 字段
composer config version $VERSION
git add composer.json
git commit -m "chore: bump version to $VERSION"
# 4. 创建标签
echo "=== 创建标签 ==="
git tag -a "v$VERSION" -m "Release $VERSION"
# 5. 推送
echo "=== 推送到远程仓库 ==="
git push origin main
git push origin "v$VERSION"
echo "=== 版本 $VERSION 发布完成 ==="
echo "Packagist 将自动更新(如已启用 GitHub 集成)"场景二:维护 CHANGELOG
markdown
# Changelog
## [1.2.0] - 2024-06-15
### Added
- Added retry mechanism for failed requests
- Added support for custom middleware
- Added `HttpClient::patch()` method
### Changed
- Improved error messages for connection timeouts
### Fixed
- Fixed memory leak when handling large responses
- Fixed header case sensitivity issue
### Deprecated
- Deprecated `HttpClient::rawRequest()` in favor of `HttpClient::request()`
## [1.1.0] - 2024-05-01
### Added
- Added `HttpClient` constructor options
- Added PSR-17 factory support
## [1.0.0] - 2024-04-01
### Added
- Initial release
- GET, POST, PUT, DELETE methods
- Guzzle integration场景三:多许可证选择
json
{
"license": "MIT"
}常用许可证:
| 许可证 | 类型 | 特点 |
|---|---|---|
| MIT | 宽松 | 最自由,几乎无限制 |
| Apache-2.0 | 宽松 | 含专利授权 |
| GPL-3.0 | 传染性 | 衍生作品必须开源 |
| LGPL-3.0 | 弱传染性 | 库文件可闭源使用 |
| BSD-2-Clause | 宽松 | 类似 MIT |
json
{
"license": [
"MIT",
"Apache-2.0"
]
}注意事项
1. 不要在 composer.json 中硬编码版本号
json
{
"name": "myorg/http-client",
// 不要写 version 字段,让 Packagist 从 Git 标签推断
// "version": "1.0.0" ← 可选,通常省略
}版本号推断
Packagist 通过 Git 标签推断版本号。如果 composer.json 中有 version 字段,优先使用该值。通常推荐省略 version 字段,让 Git 标签作为唯一版本来源。
2. composer.lock 不应纳入库的版本控制
gitignore
# 库项目的 .gitignore
/vendor/
composer.lock
.phpunit.result.cache库 vs 项目
- 库(library):
composer.lock不纳入版本控制。使用者会根据composer.json的约束自行解析版本。 - 项目(project):
composer.lock必须纳入版本控制。确保所有环境使用相同版本。
3. Packagist 更新延迟
bash
# 如果 Packagist 没有自动更新
# 手动触发更新(通过 API 或网站)
curl -X POST https://packagist.org/api/update-package?username=XXX&apiToken=XXX \
-F 'repository[repository_url]=https://github.com/myorg/http-client'
# 或者在 Packagist 网站上点击 "Force Update"4. 包的弃用
如果不再维护某个包,可以在 Packagist 上标记为弃用:
- 登录 Packagist
- 进入包设置页面
- 点击 "Abandon package"
- 可选:设置替代包(
"replace": {"old/package": "self.version"})
最佳实践
1. 完整的 composer.json 模板
json
{
"name": "myorg/my-package",
"description": "A short description of the package",
"type": "library",
"keywords": ["keyword1", "keyword2"],
"license": "MIT",
"authors": [
{
"name": "Your Name",
"email": "your@email.com",
"homepage": "https://yourwebsite.com",
"role": "Developer"
}
],
"support": {
"issues": "https://github.com/myorg/my-package/issues",
"source": "https://github.com/myorg/my-package"
},
"require": {
"php": "^8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"autoload": {
"psr-4": {
"MyOrg\\MyPackage\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"MyOrg\\MyPackage\\Tests\\": "tests/"
}
},
"minimum-stability": "stable",
"prefer-stable": true,
"config": {
"sort-packages": true,
"allow-plugins": {}
}
}2. 持续集成(GitHub Actions)
yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.1', '8.2', '8.3']
steps:
- uses: actions/checkout@v4
- name: Setup PHP ${{ matrix.php-version }}
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
extensions: mbstring, curl
coverage: xdebug
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run tests
run: composer test
- name: Run PHPStan
run: composer phpstan3. 发布检查清单
下一节
继续学习:PSR-1 编码标准 — 了解 PHP 编码标准基础规范。