Symfony4でAmazon S3に画像をアップロードするコマンドを作る方法

Symfony4でAmazon S3に画像をアップロードするコマンドを作る方法です。

AWS CLIでアップロードできるのですが、今回はSymfonyのコマンドからのアップロード機能を実装してみました。

AWSのIAMでユーザーを作成してポリシー設定

AWSのIAMでユーザーを作成してアクセスキーとシークレットアクセスキーを取得して下さい。

その後、プログラムから画像をアップロードできるよう以下のようにポリシーを設定してユーザーにアタッチして下さい。

以下はインラインポリシーの追加からJSONで設定して下さい。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:ListBucket",
                "s3:DeleteObject",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        }
    ]
}

バケット名の部分はS3で登録したバケット名に変更して下さい。

S3でバケットを作成

S3でバケットを作成して下さい。

プログラムから画像をアップロードできるようにするためブロックパブリックアクセスは無効にして下さい。

作成したバケット名は上記のポリシーに設定して下さい。

各種パッケージのインストール

composer require knplabs/knp-gaufrette-bundle
composer require aws/aws-sdk-php:^3.0

S3Clientをサービスに登録

config/services.yamlにS3Clientを登録して下さい。

# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
    # 東京リージョン指定
    amazon_s3.region: 'ap-northeast-1'
    amazon_s3.key: 'AWSアクセスキー'
    amazon_s3.secret: 'AWSシークレットアクセスキー'

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/*'
        exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones

    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%'
                    
    App\Service\S3Uploader:
        arguments:
            - '@file_storage_filesystem'

Gaufretteの設定

以下のようにconfig/packages/knp_gaufrette.yamlを作成して下さい。

knp_gaufrette:
    adapters:
        file_storage:
            aws_s3:
                service_id: 'app.aws_s3.client'
                bucket_name: 'バケット名'
                detect_content_type: true
                options:
                    directory: 'uploads'
                    acl: 'public-read'
    
    filesystems:
        file_storage:
            adapter: 'file_storage'
            alias: 'file_storage_filesystem'

S3へ画像をアップロードするサービスクラスを作成

以下のようにsrc/Service/S3Uploader.phpを作成して下さい。

<?php

/*
 * Copyright (C) 2019 Akira Kurozumi <info@a-zumi.net>.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */

namespace App\Service;

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

/**
 * Class S3Uploader
 *
 * @author Akira Kurozumi <info@a-zumi.net>
 */
class S3Uploader {

    private $allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif'];
    
    private $filesystem;
    
    private $key;
    
    public function __construct(Filesystem $filesystem)
    {
        $this->filesystem = $filesystem;
    }
    
    public function setKey($key)
    {
        $this->key = $key;
        
        return $this;
    }
    
    public function getKey()
    {
        return $this->key;
    }
    
    public function setAllowedMimeTypes(array $mimeTypes)
    {
        $this->allowedMimeTypes = array_merge($this->allowedMimeTypes, $mimeTypes);
        
        return $this;
    }
    
    public function getAllowedMimeTypes()
    {
        return $this->allowedMimeTypes;
    }
    
    public function upload(UploadedFile $file)
    {
        if(!in_array($file->getClientMimeType(), $this->getAllowedMimeTypes())) {
            throw new UnsupportedMediaTypeHttpException(sprintf("%sは許可されていない形式です", $file->getClientMimeType()));
        }
        
        $key = $file->getClientOriginalName();
        
        $adapter = $this->filesystem->getAdapter();
        
        $adapter->setMetadata($key, ["contentType" => $file->getClientMimeType()]);
        $result = $adapter->write($key, file_get_contents($file->getPathname()));
        
        if($result) {
            return $adapter->getUrl($key);
        }
        
        return false;
    }
    
    public function delete()
    {
        if(!$this->getKey()) {
            throw new \UnexpectedValueException("keyがセットされていません");
        }
        
        $key = $this->getKey();
        
        $adapter = $this->filesystem->getAdapter();
        
        return $adapter->delete($key);
    }
}

画像アップロードクラスをサービスに登録

すでに上記でservices.yamlに画像アップロードクラスを登録していますが、登録方法は以下のようになります。

services:
    App\Service\S3Uploader:
        arguments:
            - '@file_storage_filesystem'

S3へ画像をアップロードするコマンドを作成

以下のようにsrc/Command/UploadCommandを作成して下さい。

<?php

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use App\Service\S3Uploader;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class UploadCommand extends Command
{
    protected static $defaultName = 's3:upload';
    
    private $uploader;
    
    public function __construct(S3Uploader $uploader)
    {
        parent::__construct();
        
        $this->uploader = $uploader;
    }

    protected function configure()
    {
        $this
            ->setDescription('Add a short description for your command')
            ->addArgument('arg1', InputArgument::OPTIONAL, 'Argument description')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);
        $arg1 = $input->getArgument('arg1');

        if ($arg1) {
            $info = new \SplFileInfo($arg1);
            
            if(($info->isFile())) {
                // MIMEタイプ取得用
                $getMimeType = new \finfo(FILEINFO_MIME_TYPE);
                
                $file = new UploadedFile($info->getPathname(), $info->getFilename(), $getMimeType->file($info->getPathname()));
                
                if($url = $this->uploader->upload($file)) {
                    $io->success(sprintf('アップロードしました。URL:%s', $url));
                }else{
                    $io->error(sprintf('%sはアップロードできませんでした', $arg1));
                }
            }else{
                $io->error('ファイルを指定して下さい');
            }
        }else{
            $io->error('ファイルを指定して下さい');
        }
        
    }
}

S3の画像を削除するコマンドを作成

以下のようにsrc/Command/DeleteCommandを作成して下さい。

<?php

namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use App\Service\S3Uploader;

class DeleteCommand extends Command
{
    protected static $defaultName = 's3:delete';
    
    private $uploader;
    
    public function __construct(S3Uploader $uploader)
    {
        parent::__construct();
        
        $this->uploader = $uploader;
    }

    protected function configure()
    {
        $this
            ->setDescription('Add a short description for your command')
            ->addArgument('arg1', InputArgument::OPTIONAL, 'Argument description')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = new SymfonyStyle($input, $output);
        $arg1 = $input->getArgument('arg1');

        if ($arg1) {
            $info = new \SplFileInfo($arg1);
            
            if(($info->isFile())) {
                $this->uploader->setKey($arg1);
                if($this->uploader->delete()) {
                    $io->success(sprintf('%sを削除しました', $arg1));
                }else{
                    $io->error(sprintf('%sを削除出来ませんでした', $arg1));
                }
            }else{
                $io->error('削除するファイルを指定して下さい。');
            }
        }else{
            $io->error('削除するファイルを指定して下さい。');
        }
        
    }
}

以上で完成です。

これでS3にファイルをアップロードしたり削除したりできるコマンドが追加されます。

S3にアップロードするコマンドは以下のとおりです。

bin/console s3:upload sample.jpg

S3のファイルを削除するコマンドは以下のとおりです。

bin/console s3:delete sample.jpg

 

お気軽にコメントをどうぞ

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください