PHPackages                             hlkaiyan/min-s3 - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [File &amp; Storage](/categories/file-storage)
4. /
5. hlkaiyan/min-s3

ActiveLibrary[File &amp; Storage](/categories/file-storage)

hlkaiyan/min-s3
===============

轻量 S3 客户端，面向自建 S3 服务器（MinIO / Ceph / 其他 S3 兼容存储），零第三方依赖

v1.0.0(today)02↑2900%Apache-2.0PHPPHP &gt;=8.1CI passing

Since Aug 28Pushed todayCompare

[ Source](https://github.com/hlkaiyan/min-s3)[ Packagist](https://packagist.org/packages/hlkaiyan/min-s3)[ Docs](https://github.com/hlkaiyan/min-s3)[ RSS](/packages/hlkaiyan-min-s3/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (2)Used By (0)

min-s3
======

[](#min-s3)

[![测试](https://github.com/hlkaiyan/min-s3/actions/workflows/tests.yml/badge.svg)](https://github.com/hlkaiyan/min-s3/actions/workflows/tests.yml)[![最新版本](https://camo.githubusercontent.com/f67259ca431b10fa077424d4d7dfc5e609e8500b90802826b0c610972abf54e9/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f686c6b616979616e2f6d696e2d7333)](https://packagist.org/packages/hlkaiyan/min-s3)[![PHP 版本](https://camo.githubusercontent.com/dd0594eff34e9e792e92eb5c5a7e82225cf543aaa5e70d027282ef8aeabbbe30/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f686c6b616979616e2f6d696e2d73332f706870)](https://packagist.org/packages/hlkaiyan/min-s3)[![许可](https://camo.githubusercontent.com/6e9b859d749dab035aba9e9f19cdab8b3146cdc383a15d34808251e08a0ef8d1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f686c6b616979616e2f6d696e2d7333)](LICENSE)

面向自建 S3 服务器（MinIO / Ceph RGW / SeaweedFS / 其他 S3 兼容存储）的轻量 PHP 客户端。

**零第三方依赖**，只用 PHP 内置扩展。用法与 `aws/aws-sdk-php` 的 `S3Client` 保持一致，从原 SDK 迁移基本不用改调用代码。

```
$s3 = new MinS3\S3Client([
    'endpoint'    => 'http://127.0.0.1:9000',
    'region'      => 'us-east-1',
    'credentials' => ['key' => 'minioadmin', 'secret' => 'minioadmin'],
]);

$s3->putObject(['Bucket' => 'my-bucket', 'Key' => 'a.txt', 'Body' => 'hello']);
echo $s3->getObject(['Bucket' => 'my-bucket', 'Key' => 'a.txt'])['Body'];
```

---

为什么不直接用 aws-sdk-php
-------------------

[](#为什么不直接用-aws-sdk-php)

以下是 `src/` 目录的实测数据：

aws/aws-sdk-php只抽 S3 的版本**min-s3**自身文件数3478412**51**自身体积47.2 MB3.4 MB**529 KB**第三方依赖包660第三方依赖体积2.0 MB2.0 MB0装完的总体积49.2 MB5.4 MB**529 KB**支持的 S3 操作116116**116**操作数一个没少：min-s3 复用了 aws-sdk-php 的 S3 接口模型文件（`src/data/api-2.json.php`，267 KB）， 由它驱动请求的序列化与响应解析。因此 116 个 S3 操作的**参数名、类型、位置全部与官方 SDK 一致**， 不是手写的一小撮常用接口。

去掉的是 AWS 专有设施：区域端点表、凭证链（IMDS / ECS / SSO / AssumeRole）、 接入点 ARN 寻址、S3 Express、传输加速、双栈与 FIPS 端点、客户端加密。 这些在自建 S3 场景下用不到。

---

环境要求
----

[](#环境要求)

- PHP &gt;= 8.1
- 扩展：`curl`、`simplexml`、`json`、`xmlwriter`（一般都是默认启用的）

安装
--

[](#安装)

```
composer require hlkaiyan/min-s3
```

本地目录安装（调试时用，改动立即生效）：

```
{
    "repositories": [
        { "type": "path", "url": "./min-s3" }
    ],
    "require": {
        "hlkaiyan/min-s3": "*"
    }
}
```

### 不使用 Composer

[](#不使用-composer)

把整个 `min-s3` 目录放进项目，引入自带的加载器即可。因为没有第三方依赖， 不需要额外准备任何东西：

```
require __DIR__ . '/min-s3/autoload.php';

$s3 = new MinS3\S3Client([...]);
```

---

对接自建 S3 服务器
-----------

[](#对接自建-s3-服务器)

```
use MinS3\S3Client;

$s3 = new S3Client([
    'endpoint'    => 'http://127.0.0.1:9000',   // 必填
    'region'      => 'us-east-1',               // 必填，见下方说明
    'credentials' => [
        'key'    => 'minioadmin',
        'secret' => 'minioadmin',
    ],
]);
```

**关于 `region`**：自建服务通常不校验区域，但它是 SigV4 签名范围的一部分， 客户端和服务端算出来的必须一致，所以不能省略。服务端没特殊配置就填 `us-east-1`。

**关于寻址方式**：min-s3 默认用**路径式**（`http://host/bucket/key`）， 这与 aws-sdk-php 的默认值相反 —— 自建服务多数不支持虚拟主机式寻址。 如果你的服务配了泛域名解析，可以切换：

```
'use_path_style_endpoint' => false,   // 变成 http://bucket.host/key
```

端点是 IP 地址时会自动退回路径式（`bucket.127.0.0.1` 无法解析），无需手动处理。

**关于自签名证书**：内网 HTTPS 常用自签证书，可以指定 CA 或关闭校验：

```
'http' => [
    'verify' => '/path/to/ca-bundle.crt',   // 指定 CA
    // 'verify' => false,                   // 关闭校验，仅限可信内网
],
```

---

常用操作
----

[](#常用操作)

### 上传

[](#上传)

```
// 字符串
$s3->putObject(['Bucket' => 'b', 'Key' => 'a.txt', 'Body' => 'hello']);

// 本地文件（不读进内存）
$s3->putObject(['Bucket' => 'b', 'Key' => 'a.txt', 'SourceFile' => '/path/a.txt']);

// 流
$s3->putObject([
    'Bucket' => 'b', 'Key' => 'a.bin',
    'Body'   => MinS3\Http\Stream::open('/path/a.bin', 'r'),
]);

// 附带元数据与其他参数
$s3->putObject([
    'Bucket'      => 'b',
    'Key'         => 'report.pdf',
    'SourceFile'  => '/path/report.pdf',
    'ContentType' => 'application/pdf',
    'ACL'         => 'public-read',
    'Metadata'    => ['author' => 'zhang', 'version' => '2'],
    'CacheControl'=> 'max-age=3600',
]);
```

`upload()` 会按大小自动选择整体上传或分片上传（默认阈值 16 MB）：

```
$s3->upload('my-bucket', 'big.zip', fopen('/path/big.zip', 'r'));
```

### 大文件分片上传

[](#大文件分片上传)

```
use MinS3\Multipart\MultipartUploader;

$uploader = new MultipartUploader($s3, '/path/big.bin', [
    'bucket'      => 'my-bucket',
    'key'         => 'big.bin',
    'part_size'   => 8 * 1024 * 1024,   // 分片大小，最小 5 MB
    'concurrency' => 4,                 // 并发分片数
]);

$result = $uploader->upload();
```

分片是真并发（`curl_multi`），且源文件按需分段读取，内存占用与文件大小无关。

**断点续传**：失败时从异常里取出状态，之后接着传，已完成的分片不会重传。

```
use MinS3\Exception\MultipartUploadException;

try {
    $uploader->upload();
} catch (MultipartUploadException $e) {
    $state = $e->getState();
    file_put_contents('/tmp/upload.state', serialize($state));

    // 稍后续传
    $state = unserialize(file_get_contents('/tmp/upload.state'));
    (new MultipartUploader($s3, '/path/big.bin', [
        'bucket' => 'my-bucket', 'key' => 'big.bin', 'state' => $state,
    ]))->upload();
}
```

放弃上传时记得清理，否则残留分片会一直占用空间：

```
$uploader->abort();
```

### 下载

[](#下载)

```
// 读进内存
$body = (string) $s3->getObject(['Bucket' => 'b', 'Key' => 'a.txt'])['Body'];

// 直接落盘，不经过内存
$s3->getObject(['Bucket' => 'b', 'Key' => 'big.bin', 'SaveAs' => '/path/big.bin']);

// 流式分段读
$stream = $s3->getObject(['Bucket' => 'b', 'Key' => 'big.bin'])['Body'];
while (!$stream->eof()) {
    echo $stream->read(8192);
}

// 断点续传 / 部分下载
$part = $s3->getObject(['Bucket' => 'b', 'Key' => 'big.bin', 'Range' => 'bytes=0-1023']);
```

### 列举与翻页

[](#列举与翻页)

单次列举最多返回 1000 条，用分页器自动翻页：

```
// 逐页
foreach ($s3->getPaginator('ListObjectsV2', ['Bucket' => 'b', 'Prefix' => 'logs/']) as $page) {
    foreach ($page['Contents'] ?? [] as $object) {
        echo $object['Key'], ' ', $object['Size'], "\n";
    }
}

// 逐个对象，省掉一层循环
foreach ($s3->getIterator('ListObjectsV2', ['Bucket' => 'b']) as $object) {
    echo $object['Key'], "\n";
}

// 只取前 100 个
foreach ($s3->getIterator('ListObjectsV2', ['Bucket' => 'b', '@limit' => 100]) as $object) {
    echo $object['Key'], "\n";
}
```

按"目录"列举：

```
$result = $s3->listObjectsV2(['Bucket' => 'b', 'Prefix' => 'docs/', 'Delimiter' => '/']);

foreach ($result['CommonPrefixes'] ?? [] as $prefix) {
    echo "子目录: ", $prefix['Prefix'], "\n";
}
foreach ($result['Contents'] ?? [] as $object) {
    echo "文件: ", $object['Key'], "\n";
}
```

### 删除

[](#删除)

```
$s3->deleteObject(['Bucket' => 'b', 'Key' => 'a.txt']);

// 批量删除，自动按 1000 个一批
use MinS3\BatchDelete;

BatchDelete::fromKeys($s3, 'b', ['a.txt', 'b.txt', 'c.txt'])->delete();
BatchDelete::fromListObjects($s3, 'b', ['Prefix' => 'tmp/'])->delete();

// 按正则删除
$s3->deleteMatchingObjects('b', 'logs/', '/\.log$/');
```

### 复制

[](#复制)

```
$s3->copy('src-bucket', 'src-key', 'dst-bucket', 'dst-key');
```

超过 50 MB 自动改用分片复制（S3 单次复制上限 5 GB）。数据在服务端流转，不经过本机。

### 预签名 URL

[](#预签名-url)

生成临时链接，不暴露密钥。生成过程是纯本地计算，不发请求：

```
// 下载链接
$url = $s3->createPresignedUrl('b', 'a.txt', '+20 minutes');

// 强制浏览器下载并指定文件名
$url = $s3->createPresignedUrl('b', 'a.pdf', '+1 hour', [
    'ResponseContentDisposition' => 'attachment; filename="报告.pdf"',
]);

// 上传链接：前端可以直接 PUT 到这个地址
$command = $s3->getCommand('PutObject', ['Bucket' => 'b', 'Key' => 'up.bin']);
$url = (string) $s3->createPresignedRequest($command, '+30 minutes')->getUri();
```

有效期上限是 7 天（SigV4 的规定）。

### 浏览器表单直传

[](#浏览器表单直传)

文件从浏览器直接传到 S3，不经过你的服务器：

```
use MinS3\PostObjectV4;

$post = new PostObjectV4($s3, 'my-bucket',
    ['key' => 'uploads/${filename}', 'acl' => 'private'],
    [
        ['bucket' => 'my-bucket'],
        ['starts-with', '$key', 'uploads/'],
        ['content-length-range', 0, 10 * 1024 * 1024],   // 限制 10 MB
    ],
    '+1 hours'
);
?>
