はじめに
以前、以下の記事を書きました。
今回はこれらの記事の発展形として、「オリジナル画像」と「トリミング+リサイズした画像」の2種類を、開発環境ならローカルストレージに保存し、本番環境ならAmazon S3に保存する方法を解説します。
今回の方針は以下の通りです。
- 開発環境+画像がGIF形式の場合
- ローカルストレージにオリジナル画像を保存
- 開発環境+画像がGIF形式でない場合
- ローカルストレージにオリジナル画像を保存
- ローカルストレージにトリミング+リサイズした画像を保存
- 本番環境+画像がGIF形式の場合
- Amazon S3にオリジナル画像を保存
- 本番環境+画像がGIF形式でない場合
- Amazon S3にオリジナル画像を保存
- Amazon S3にトリミング+リサイズした画像を保存
画像のトリミング+リサイズにはIntervention Imageというライブラリを使います。Intervention Imageは現在のところGIF形式には対応していないため、画像がGIF形式の場合はIntervention Imageを使わずにオリジナル画像のみを保存します。
¿Animated GIF support? · Issue #176 · Intervention/image
準備
Intervention Image
以下のコマンドを実行します。
$ php composer require intervention/image
config/app.php
に以下を追記します。
'providers' => [
...
Intervention\Image\ImageServiceProvider::class,
...
],
'aliases' => [
...
'Image' => Intervention\Image\Facades\Image::class,
...
],
テーブル
保存した画像の参照URIを保存するためのカラムを用意しておきます。
今回は、posts.image
、posts.image_320_180
という2つのカラムがあるという状態とします。
ビュー
画像を添付できるフォームを持つビューを用意しておきます。
今回は、ビューからimage
という名前で画像ファイルがコントローラーに渡されてくる状態とします。
コントローラーの実装
まずはじめに、store
メソッドの全文を記載します。
use Image;
class PostsController extends Controller
{
public function store(CreatePost $request)
{
if (!is_null($request->image)) {
# 画像がNULLでない場合
$hash = $request->file('image')->hashName();
if ( app()->isLocal() || app()->runningUnitTests() ) {
# 開発環境の場合
if ($request->image->getClientOriginalExtension() === 'gif') {
# GIF形式の場合
$request->file('image')->storeAs('public/images/gif', $hash);
$post->image = Storage::url('public/images/gif/' . $hash);
}
else {
# GIF形式でない場合
$image = Image::make($request->file('image'));
$image_320_180 = Image::make($request->file('image'))->fit(320, 180);
$image->save(storage_path() . '/app/public/images/' . $hash);
$image_320_180->save(storage_path() . '/app/public/images/320-180/' . $hash);
$post->image = Storage::url('public/images/' . $hash);
$post->image_320_180 = Storage::url('public/images/320-180/' . $hash);
}
}
else {
# 本番環境の場合
if ($request->image->getClientOriginalExtension() === 'gif') {
# GIF形式の場合
Storage::disk('s3')->put('/images/gif/' . $hash, file_get_contents($request->file('image')), 'public');
$post->image = Storage::disk('s3')->url('images/gif/' . $hash);
}
else {
# GIF形式でない場合
$image = Image::make($request->file('image'))->stream();
$image_320_180 = Image::make($request->file('image'))->fit(320, 180)->stream();
Storage::disk('s3')->put('/images/' . $hash, $image->__toString(), 'public');
Storage::disk('s3')->put('/images/320-180/' . $hash, $image_320_180->__toString(), 'public');
$post->image = Storage::disk('s3')->url('images/' . $hash);
$post->image_320_180 = Storage::disk('s3')->url('images/320-180/' . $hash);
}
}
}
else {
# 画像がNULLの場合
$post->image = '/images/noimage.png';
}
Auth::user()->posts()->save($post);
return redirect()->route('posts.index');
}
}
画像のハッシュネームを取得します。このハッシュネームは保存するときのファイル名に使います。保存ファイル名は一意であればなんでも構いません(処理日時+ファイル名など)。
$hash = $request->file('image')->hashName();
開発環境+画像がGIF形式の場合
保存先ディレクトリはstorage/app/public/images/gif/
です。
$request->file('image')->storeAs('public/images/gif', $hash);
$post->image = Storage::url('public/images/gif/' . $hash);
画像がGIF形式の場合はIntervention Imageを使わず、普通に保存します。
開発環境+画像がGIF形式でない場合
保存先ディレクトリはstorage/app/public/images/
、storage/app/public/images/320-180/
です。
$image = Image::make($request->file('image'));
$image_320_180 = Image::make($request->file('image'))->fit(320, 180);
$image->save(storage_path() . '/app/public/images/' . $hash);
$image_320_180->save(storage_path() . '/app/public/images/320-180/' . $hash);
$post->image = Storage::url('public/images/' . $hash);
$post->image_320_180 = Storage::url('public/images/320-180/' . $hash);
1〜2行目でIntervention Imageのインスタンスを作成しています。1行目はオリジナル画像、2行目はトリミング+リサイズした画像です。トリミング+リサイズにはfit
メソッドを使います。
4〜5行目でローカルストレージに保存しています。
7〜8行目で保存先URIをDBに保存しています。保存した画像をビューで表示したいときは、これらのカラムをimg
タグに指定することになります。
本番環境+画像がGIF形式の場合
保存先ディレクトリはimages/gif/
です。
Storage::disk('s3')->put('/images/gif/' . $hash, file_get_contents($request->file('image')), 'public');
$post->image = Storage::disk('s3')->url('images/gif/' . $hash);
画像がGIF形式の場合はIntervention Imageを使わず、普通に保存します。put
メソッドの第2引数は、必ずfile_get_contents
メソッドを使うようにしてください。
本番環境+画像がGIF形式でない場合
保存先ディレクトリはimages/
、images/320-180/
です。
$image = Image::make($request->file('image'))->stream();
$image_320_180 = Image::make($request->file('image'))->fit(320, 180)->stream();
Storage::disk('s3')->put('/images/' . $hash, $image->__toString(), 'public');
Storage::disk('s3')->put('/images/320-180/' . $hash, $image_320_180->__toString(), 'public');
$post->image = Storage::disk('s3')->url('images/' . $hash);
$post->image_320_180 = Storage::disk('s3')->url('images/320-180/' . $hash);
1〜2行目でIntervention Imageのインスタンスを作成しています。基本的に開発環境の場合と同じですが、stream
メソッドを使ってストリーム形式に変換していることに注意してください。
4〜5行目でローカルストレージに保存しています。put
メソッドの第2引数は、ストリーム形式に変換したIntervention Imageインスタンスを__toString
メソッドで文字列化しています。
stream
メソッドと__toString
メソッドは特に大事なので忘れないようにしてください。
7〜8行目で保存先URIをDBに保存しています。
Herokuへのデプロイ
Herokuにデプロイし画像を保存しようとすると、以下のエラーが出るかと思います。ひょっとしたらローカル環境でも出た人がいるかも知れません。
GD Library extension not available with this PHP installation.
実は、Intervention Imageは内部でGDライブラリを使用しているため、GDライブラリがないという内容のエラーが出ています。
エラーを解消するには。デプロイ先のHerokuにGDライブラリを入れればOKです。HerokuにGDライブラリを入れるには以下の手順を行います。
composer.json
に以下を追記します。
{
...
"require": {
...
// 以下を追記
"ext-gd": "*"
}
...
}
次に、以下のコマンドを実行します。
# composer update
後は、HerokuにデプロイしたタイミングでGDライブラリがインストールされます。
まとめ
いかがでしたでしょうか。この記事でLaravelの画像アップロード周りはだいたい網羅できるようになったかと思います。
Intervension Imageにはfit
メソッド以外にもたくさんの便利なメソッドがあるので、ぜひいろいろと試してみてください。