PHP Slim 環境構築(8) S3
Introduction
前回は、ローカルDynamoDBにアクセスしてみました。
今回は、ローカル用のS3互換サービスMinIOを使って、S3へのアクセスを行います。
MinIO
MinIOは、エンタープライズレベルのオブジェクトストレージサービスです。
・・・なんですが、手軽に使えるS3互換ストレージとして、ローカル開発環境によく使用されているようです。
変更点
ソースツリー
前回からの変更・追加ソースは以下の通りです。
$(PROJECTROOT)
/compose
docker-compose.yml
/src
/hoge
/lib
/Controller
StorageController.php (NEW!)
/Model
/public
index.php
/test
RequestTest.php
composer.json
composer.lock
docker-compose.yml
新たにS3互換のminIO用コンテナを追加します。
なお、この記事を書いている途中でdocker-composeのlinksオプションはすでに時代遅れなのを知ってしまったので、今回からlinksは全て削除しています。すでにnetworksを使っているので、linksは冗長だったんですね。
..(略)..
storage:
image: minio/minio
volumes:
- storage-data:/data
container_name: storage
ports:
- "19000:9000"
environment:
MINIO_ACCESS_KEY: minio
MINIO_SECRET_KEY: miniminio
command: server /data
networks:
- local_net
index.php
DIにstorage用の設定とstorageオブジェクトの定義を行い、storage用のrouteを追加します。
なお、ローカルS3スタックを使用する際には、"use_path_style_endpoint"を有効にする必要があります。
このフラグが無効だと、S3ストレージへのアクセスポイントがサブドメインを使用した形式になるため、localhostを使った名前解決が面倒になるためです。
..(略)..
use Hoge\Controller\RedisController;
use Hoge\Controller\DynamodbController;
use Hoge\Controller\StorageController; (追加)
..(略)..
$containerBuilder = new ContainerBuilder();
$containerBuilder->addDefinitions([
'settings' => [
..(略)..
'storage' => [
'endpoint' => 'http://storage:9000',
'region' => 'ap-northeast-1'
]
],
..(略)..
'storage' => function (ContainerInterface $container) {
$settings = $container->get('settings')['storage'];
$sdk = new Aws\Sdk([
'endpoint' => $settings['endpoint'],
'region' => $settings['region'],
'version' => '2006-03-01',
'credentials' => [
'key' => 'minio',
'secret' => 'miniminio'
],
//'bucket_endpoint' => true,
'use_path_style_endpoint' => true
]);
$s3 = $sdk->createS3();
return $s3;
}
..(略)..
$app->group('/storage', function (RouteCollectorProxy $group) {
$group->get('/{filename}', StorageController::class . ':get');
$group->post('', StorageController::class . ':post');
});
$app->run();
### StorageController
ファイルのアップロードと、ファイルの表示(ダウンロードにするためにはcontent-typeとかいじればok)に対応しています。
```php:/src/hoge/lib/Controller/StorageController.php
<?php
namespace Hoge\Controller;
use Aws\S3\Exception\S3Exception;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Container\ContainerInterface;
class StorageController
{
/**
* @var ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
/** @var \Aws\S3\S3Client $s3 */
$s3 = $this->container->get('storage');
try {
// バケット確認
$s3->headBucket(['Bucket' => 'dummy']);
} catch (S3Exception $ex) {
$errorCode = $ex->getAwsErrorCode();
if ($errorCode === 'NotFound') {
// バケットが無ければ作る
$s3->createBucket([
'Bucket' => 'dummy',
'CreateBucketConfiguration' => [
'LocationConstraint' => 'ap-northeast-1'
]
]);
}
}
}
public function get(Request $request, Response $response, array $args) : Response
{
// parameters (id)
$key = $args['filename'];
/** @var \Aws\S3\S3Client $s3 */
$s3 = $this->container->get('storage');
try {
$result = $s3->getObject([
'Bucket' => 'dummy',
'Key' => $key
]);
} catch (S3Exception $ex) {
return $response->withStatus(404);
}
$body = $result->get('Body');
$newResponse = $response->withBody($body);
return $newResponse;
}
public function post(Request $request, Response $response, array $args) : Response
{
$files = $request->getUploadedFiles();
/** @var \Psr\Http\Message\UploadedFileInterface $uploadedFile */
$uploadedFile = $files['upload'];
$err = $uploadedFile->getError();
if ($err !== 0) {
return $response->withStatus(500);
}
$clientFileName = $uploadedFile->getClientFilename();
/** @var \Aws\S3\S3Client $s3 */
$s3 = $this->container->get('storage');
$result = $s3->putObject([
'Bucket' => 'dummy',
'Key' => $clientFileName,
'Body' => $uploadedFile->getStream()
]);
$url = $result->get('ObjectURL');
$response->getBody()->write($url);
return $response;
}
}
composer.json & composer.lock
今までのcontrollerをテストするためのコードを追加したのに伴い、それに使用したPHPUnitをcomposerに追加しました。
$ composer require phpunit/phpunit -dev
{
"require": {
"slim/slim": "4.2.0",
"slim/psr7": "0.5.0",
"php-di/php-di": "6.0.9",
"ext-pdo": "^7.2",
"ext-json": "^1.6",
"ext-redis": "^3.1",
"ext-curl": "*",
"aws/aws-sdk-php": "^3.112"
},
"require-dev": {
"phpunit/phpunit": "^8.4"
}
}
手動テスト
wgetでpost fileuploadをテストするために、以下のようなコマンドを実行しました。
--cuthere
Content-Disposition: form-data; name="upload"; filename="videodrome.txt"
Content-Type: text/plain
THIS IS YOUR TAPE.
--cuthere--
$ wget http://hoge.localhost/.storage --header='Content-Type: multipart/form-data; boundary=cuthere' --post-file=postdata.txt
全部テスト
今までのControllerをテストするために、/src/hoge/test/RequestTest.phpを追加しました。
長いのでソースはここでは割愛します(リポジトリを直接参照ください)。
ここまでのソース
こちらでどうぞ。
更新
2019/10/09
- use_path_style_endpointに関する説明を追加。
- RequestTest.php投入に伴う修正