LaravelとS3の接続テストを行うコマンドを作る

## 1. 事前準備

### 1.1 S3バケットの作成
1. AWS Management Consoleにログイン
2. Amazon S3にアクセス
3. 「バケットを作成」を選択
4. リージョン(ap-northeast-1)とバケット名(任意だがグローバルで一意であるもの)を設定、他はデフォルトで一旦OK

### 1.2 IAMポリシーの作成
1. 「ポリシーの作成」を選択
2. 以下のポリシーを作成（名前: S3BucketCRUDPolicy）※ポリシー名は任意

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket",
                "s3:GetBucketLocation",
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws:s3:::your-bucket-name",
                "arn:aws:s3:::your-bucket-name/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "s3:ListAllMyBuckets",
            "Resource": "*"
        }
    ]
}
```

### 1.3 IAMユーザーの作成
1. ユーザー名: s3-test-user（任意）
2. ポリシーを直接アタッチ: S3BucketCRUDPolicy
3. アクセスキーの作成: ローカルコード用
4. アクセスキーとシークレットアクセスキーをメモしておく

## 2. Laravelプロジェクトの設定

### 2.1 AWS SDKのインストール
```bash
composer require aws/aws-sdk-php-laravel
```

### 2.2 設定ファイルの公開
```bash
php artisan vendor:publish --provider="Aws\Laravel\AwsServiceProvider"
```

### 2.3 環境設定
```env:.env
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_BUCKET=ap-northeast-1
AWS_BUCKET=your-bucket
```

### 2.4 AWS設定ファイル
```php:config/aws.php
<?php

use Aws\Laravel\AwsServiceProvider;

return [

    /*
    |--------------------------------------------------------------------------
    | AWS SDK Configuration
    |--------------------------------------------------------------------------
    |
    | The configuration options set in this file will be passed directly to the
    | `Aws\Sdk` object, from which all client objects are created. This file
    | is published to the application config directory for modification by the
    | user. The full set of possible options are documented at:
    | http://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/configuration.html
    |
    */
    'credentials' => [
        'key'    => env('AWS_ACCESS_KEY_ID', ''),
        'secret' => env('AWS_SECRET_ACCESS_KEY', ''),
    ],
    'region' => env('AWS_REGION', 'us-east-1'),
    'version' => 'latest',
    'ua_append' => [
        'L5MOD/' . AwsServiceProvider::VERSION,
    ],
];

```

## 3. テストコマンドの実装

### 3.1 コマンドの作成
```bash
php artisan make:command TestS3SimpleCommand
```

### 3.2 コマンドクラスの実装
```php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Aws\Laravel\AwsFacade as AWS;
use Aws\S3\Exception\S3Exception;
use Illuminate\Support\Facades\Log;

class TestS3SimpleCommand extends Command
{
    protected $signature = 'test:s3-simple';
    protected $description = 'S3接続テスト';
    private $s3;
    private $bucket;
    private $testPrefix = 'test-connection/';

    public function __construct()
    {
        parent::__construct();
        $this->bucket = env('AWS_BUCKET');
    }

    public function handle()
    {
        $this->info('S3接続テストを開始します...');

        try {
            // 設定の表示
            $this->showConfiguration();

            // S3クライアントの初期化
            $this->initializeS3Client();

            // バケットアクセステスト
            $this->testBucketAccess();

            // アップロードテスト
            $testFile = $this->createTestFile();
            $uploadResult = $this->testFileUpload($testFile);

            // ダウンロードテスト
            $this->testFileDownload($uploadResult['key']);

            // 削除テスト
            $this->testFileDeletion($uploadResult['key']);

            // テスト成功
            $this->showSuccessMessage();

            return 0;
        } catch (S3Exception $e) {
            $this->handleS3Error($e);
            return 1;
        } catch (\Exception $e) {
            $this->error("予期せぬエラーが発生しました: " . $e->getMessage());
            return 1;
        } finally {
            // 一時ファイルの削除
            $this->cleanup();
        }
    }

    private function showConfiguration()
    {
        $this->info('現在の設定:');
        $this->table(
            ['項目', '値'],
            [
                ['Region', env('AWS_DEFAULT_REGION')],
                ['Bucket', $this->bucket],
                ['Access Key', $this->maskString(env('AWS_ACCESS_KEY_ID'))],
            ]
        );
    }

    private function initializeS3Client()
    {
        $this->info('S3クライアントを初期化中...');
        $this->s3 = AWS::createClient('s3', [
            'version' => 'latest',
            'region'  => env('AWS_DEFAULT_REGION'),
            'http'    => [
                'verify' => true,
                'timeout' => 15
            ]
        ]);
    }

    private function testBucketAccess()
    {
        $this->info("バケット '{$this->bucket}' へのアクセスをテスト中...");

        $this->s3->headBucket([
            'Bucket' => $this->bucket
        ]);

        $this->info('[成功] バケットアクセス確認');
    }

    private function createTestFile(): string
    {
        $content = "Test content - " . date('Y-m-d H:i:s');
        $tempFile = tempnam(sys_get_temp_dir(), 's3-test-');
        file_put_contents($tempFile, $content);
        return $tempFile;
    }

    private function testFileUpload(string $filePath): array
    {
        $key = $this->testPrefix . 'test-' . time() . '.txt';
        $this->info("テストファイルをアップロード中... ({$key})");

        $result = $this->s3->putObject([
            'Bucket' => $this->bucket,
            'Key'    => $key,
            'SourceFile' => $filePath,
            'ServerSideEncryption' => 'AES256',
            'Metadata' => [
                'uploaded-by' => 'connection-test',
                'timestamp'   => time()
            ]
        ]);

        $this->info('[成功] アップロード完了');
        return ['key' => $key, 'result' => $result];
    }

    private function testFileDownload(string $key)
    {
        $this->info('ファイルのダウンロードをテスト中...');

        $result = $this->s3->getObject([
            'Bucket' => $this->bucket,
            'Key'    => $key
        ]);

        $content = (string) $result['Body'];
        $this->info('[成功] ダウンロード完了 (サイズ: ' . strlen($content) . ' bytes)');
    }

    private function testFileDeletion(string $key)
    {
        $this->info('テストファイルを削除中...');

        $this->s3->deleteObject([
            'Bucket' => $this->bucket,
            'Key'    => $key
        ]);

        $this->info('[成功] 削除完了');
    }

    private function handleS3Error(S3Exception $e)
    {
        $this->error('S3エラーが発生しました:');
        $this->error($e->getMessage());

        $this->table(
            ['エラー詳細', '値'],
            [
                ['Error Code', $e->getAwsErrorCode()],
                ['Error Type', $e->getAwsErrorType()],
                ['Request ID', $e->getAwsRequestId()],
                ['Status Code', $e->getStatusCode()],
            ]
        );

        $this->showTroubleshootingGuide($e->getAwsErrorCode());

        Log::error('S3 Connection Test Failed', [
            'error_code' => $e->getAwsErrorCode(),
            'message' => $e->getMessage(),
            'request_id' => $e->getAwsRequestId()
        ]);
    }

    private function showTroubleshootingGuide(string $errorCode)
    {
        $this->info("\nトラブルシューティングガイド:");

        $guides = [
            'AccessDenied' => [
                'IAMユーザーの権限を確認してください',
                'バケットポリシーを確認してください',
                '必要な権限：s3:ListBucket, s3:PutObject, s3:GetObject, s3:DeleteObject'
            ],
            'NoSuchBucket' => [
                'バケット名が正しいか確認してください',
                'リージョンの設定を確認してください'
            ],
            'InvalidAccessKeyId' => [
                'AWS_ACCESS_KEY_IDが正しいか確認してください',
                'IAMユーザーが有効か確認してください'
            ]
        ];

        foreach ($guides[$errorCode] ?? ['AWS Management Consoleで詳細を確認してください'] as $tip) {
            $this->line("- {$tip}");
        }
    }

    private function showSuccessMessage()
    {
        $this->newLine();
        $this->info('全てのテストが成功しました');
        $this->table(
            ['テスト項目', '結果'],
            [
                ['バケットアクセス', '[成功]'],
                ['ファイルアップロード', '[成功]'],
                ['ファイルダウンロード', '[成功]'],
                ['ファイル削除', '[成功]']
            ]
        );
    }

    private function cleanup()
    {
        // 一時ファイルのクリーンアップ
        $tempFiles = glob(sys_get_temp_dir() . '/s3-test-*');
        foreach ($tempFiles as $file) {
            unlink($file);
        }
        $this->info('一時ファイルのクリーンアップが完了しました');

        // S3ファイルのクリーンアップ
        $this->s3->deleteMatchingObjects($this->bucket, $this->testPrefix);
        $this->info('S3ファイルのクリーンアップが完了しました');
    }

    private function maskString(string $string): string
    {
        if (strlen($string) <= 8) {
            return str_repeat('*', strlen($string));
        }
        return substr($string, 0, 4) . str_repeat('*', strlen($string) - 8) . substr($string, -4);
    }
}

```

### 3.3 コマンドの実行
```bash
# キャッシュのクリア
php artisan config:clear

# テストコマンドの実行
php artisan test:s3-simple
```

### 3.4 設定の確認
```bash
# 利用可能なコマンドの確認
php artisan list

# AWS設定の確認
php artisan config:show aws
```

## 4. テスト機能

このコマンドは以下のテストを実行します：
1. S3接続の確認
2. バケットへのアクセス確認
3. テストファイルのアップロード
4. テストファイルのダウンロード
5. テストファイルの削除

## 5. エラーハンドリング

主なエラーケース：
- AccessDenied: IAMユーザーの権限確認
- NoSuchBucket: バケット名とリージョンの確認
- InvalidAccessKeyId: アクセスキーの確認