Symfony3でS3に画像をアップロード

## 概要
- SymfonyでGaufretteを使ってS3に画像をアップロードする

## 動作環境
- Symfony → v3.4.15
- knp-gaufrette-bundle → v0.5.3
- aws-sdk-php → v3.67.22

## 全体の流れ
1. 各種パッケージのインストール
1. serviceに登録
1. configで設定 
1. 画像アップロードクラスの作成
1. DI設定
1. 画像アップロードクラスをserviceに登録
1. 呼び出して使う

## 各種パッケージのインストール
```
$ composer require knplabs/knp-gaufrette-bundle
$ composer require aws/aws-sdk-php:^3.0
```

## Serviceに登録
```app/config/services.yml
# %hoge% はparameters.ymlで定義した値を使用している
services:
    app.aws_s3.client:
        class: Aws\S3\S3Client
        factory: [Aws\S3\S3Client, 'factory']
        arguments:
            -
                version: 'latest'
                region: '%amazon_s3.region%'
                credentials:
                    key: '%amazon_s3.key%'
                    secret: '%amazon_s3.secret%'

```
詳しくは下記で確認  
https://github.com/KnpLabs/KnpGaufretteBundle/blob/master/Resources/docs/adapters/awss3.md

## configでGaufretteの設定
```app/config/config.yml
# %hoge% はparameters.ymlで定義した値を使用している
knp_gaufrette:
    adapters:
        photo_storage:
            aws_s3:
                service_id: 'app.aws_s3.client'
                bucket_name: '%amazon_s3.bucket_name%'
                detect_content_type: true
                options:
                    directory: ''
                    create: true
                    acl: 'public-read'
    # service_idに別名をつけたい場合などに使う
    filesystems:
        photo_storage:
            adapter: 'photo_storage'
            alias: 'photo_storage_filesystem'
```

## 画像アップロードクラスの作成
```src/AppBundle/Service/PhotoUploader.php
<?php

namespace AppBundle\Service;

use Gaufrette\Adapter\AwsS3;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Gaufrette\Filesystem;

/**
 * Class PhotoUploader
 * @package AppBundle\Service
 */
class PhotoUploader
{
    private static $allowedMimeTypes  = ['image/jpeg', 'image/png', 'image/gif'];

    private $filesystem;
    private $path;

    /**
     * PhotoUploader constructor.
     * @param Filesystem $filesystem
     */
    public function __construct(Filesystem $filesystem)
    {
        $this->filesystem = $filesystem;
    }

    /**
     * Pathのセット(必須)
     * @param $path
     * @return $this
     */
    public function setPath($path)
    {
        $this->path = $path;

        return $this;
    }

    /**
     * @return mixed
     */
    public function getPath()
    {
        return $this->path;
    }

    /**
     * @param UploadedFile $file
     * @return bool
     */
    public function upload(UploadedFile $file)
    {
        // 拡張子判定
        if (!in_array($file->getClientMimeType(), self::$allowedMimeTypes)) {
            throw new \InvalidArgumentException(sprintf('%sは許可されていない形式です', $file->getClientMimeType()));
        }

        if (!$this->getPath()) {
            throw new \UnexpectedValueException('pathがセットされていません');
        }

        $uploadPath = $this->getPath();

        /** @var AwsS3 $adapter */
        $adapter = $this->filesystem->getAdapter();
        $adapter->setMetadata($uploadPath, array('contentType' => $file->getClientMimeType()));
        $result = $adapter->write($uploadPath, file_get_contents($file->getPathname()));

        // 戻り値統一のため
        return ($result !== false);
    }

    /**
     * @return bool
     */
    public function delete()
    {

        if (!$this->getPath()) {
            throw new \UnexpectedValueException('pathがセットされていません');
        }

        $filePath = $this->getPath();

        /** @var AwsS3 $adapter */
        $adapter = $this->filesystem->getAdapter();
        $result  = $adapter->delete($filePath);

        return $result;
    }

}

```

## サービス登録して使うためにDI設定
既存のservices.ymlはcomposerでインストールしたパッケージ用として使う

```src/AppBundle/DependencyInjection/AppExtension.php
<?php
namespace AppBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

class AppExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.yml');
    }
}

```

## 画像アップロードクラスをサービスに登録
```src/AppBundle/Resources/config/services.yml
services:
    app.photo_uploader:
        class: AppBundle\Service\PhotoUploader
        # Aliasを指定(app.aws_s3.clientでもよい)
        arguments: ['@photo_storage_filesystem']

```

## コントローラーでの利用サンプル
※ 色々と省略しています(validateとか)

```php
    /**
     * 製品登録
     * @Route("/new", name="product_new")
     * @Method({"GET", "POST"})
     * @param Request $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function sampleAction(Request $request)
    {
        $product = new Product();
        $form = $this->createForm(ProductType::class, $product);
        $form->handleRequest($request);
        
        /** @var Product $product */
        $product = $form->getData();
        
        // S3に保存するための画像path設定
        /** @var PhotoUploader $uploader */
        $uploader = $this->get('app.photo_uploader');
        
        $photo = $product->getPhoto();
        $photoPath = 'uploads/photo/product/sample.' . $photo->getClientOriginalExtension();

        // upload処理
        if ($photo && $photoPath) {
            $uploader->setPath($photoPath)->upload($photo);
        }
        
        // 以下省略

    }

```