こんにちは、むちょこです。
今日はリクエストいただいたLaravelで画像をアップロードする方法について解説したいと思います☆
環境はPHP7、Laravel5.6です。
1. ルートを定義
今回は、/profileでプロフィール画像を登録する想定で進めます。
Route::get('/profile', 'ProfileController@index');
Route::post('/profile', 'ProfileController@store');
Route::get()がフォームを表示するルート定義、
Route::post()が送信された値を受け取るためのルート定義です。
今回はフォームの表示と送信された値の受け取りを同じURLで行います。
もしこれらを別のURLにしたい場合は、それぞれの第一引数を適宜変更してください。
本題から少し逸れますが、今回はログイン済みのユーザが本人のプロフィールを変更することを前提としているので、認証を必要とするグループ内でルート定義を行いました。
Route::group(['middleware' => 'auth'], function () {
//ここでルート定義
}
2. アップロード用のフォームを作成
まずプロフィールを操作するコントローラを生成し、編集します。
$ php artisan make:controller ProfileController
Controller created successfully.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ProfileController extends Controller
{
/**
* プロフィール登録フォームの表示
*
* @return Response
*/
public function index()
{
return view('profile/index');
}
}
次にbladeファイルにアップロード用のフォームを用意します。
<form method="POST" action="/profile" enctype="multipart/form-data" >
{{ csrf_field() }}
<input type="file" name="photo">
<input type="submit">
</form>
画像投稿に関わらず、フォームを利用するときは{{ csrf_field() }}でCSRFトークンの隠しフィールドを生成する必要があります。これはCSRFからアプリケーションを保護するために使われます。
formのenctype属性にmultipart/form-dataを指定することも忘れずに!
これがないと、ファイルデータをアップロードすることができません。
これでフォームの準備ができました✨
3. 画像ファイルのバリデーション
バリデーションは、コントローラに書く方法とカスタムリクエストクラスに書く方法の2種類があります。
どちらに書いても間違いではありませんが、私は特別な理由がない限りカスタムリクエストクラスを利用する方をオススメします☆
webアプリケーションは後から機能を追加することが少なからずあり、なるべく予め分離し可読性を上げておいた方が良いという考えです。
$ php artisan make:request ProfileRequest
Request created successfully.
コマンド実行で生成されたリクエストクラスを編集します。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ProfileRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'photo' => 'required|file|image|mimes:jpeg,png,jpg,gif|max:2048'
];
}
}
authorizeメソッドでは、ユーザがデータを更新するための権限を持っているかどうかを確認するために使います。今回は特にないので何もせずtrueを返しています。
rulesメソッドにはバリデーションルールを記載します。
ルールの詳細はこんな感じです。
required
https://readouble.com/laravel/5.7/ja/validation.html#rule-required
フィールドが入力データに存在しており、かつ空でないことをバリデートします。フィールドは以下の条件の場合、「空」であると判断されます。
値がnull
である。
値が空文字列である。
値が空の配列か、空のCountable
オブジェクトである。
値がパスのないアップロード済みファイルである。
file
フィールドがアップロードに成功したファイルであることをバリデートします。
https://readouble.com/laravel/5.7/ja/validation.html#rule-file
image
https://readouble.com/laravel/5.7/ja/validation.html#rule-image
フィールドで指定されたファイルが画像(jpg、png、bmp、gif、svg)であることをバリデートします。
mimes:foo,bar,…
https://readouble.com/laravel/5.7/ja/validation.html#rule-mimes
フィールドで指定されたファイルが拡張子のリストの中のMIMEタイプのどれかと一致することをバリデートします。
max:値
https://readouble.com/laravel/5.7/ja/validation.html#rule-max
フィールドが最大値として指定された値以下であることをバリデートします。size
ルールと同様の判定方法で、文字列、数値、配列、ファイルが評価されます。
次に、今編集したProfileRequestクラスをコントローラに適用させましょう。
値を受け取るstoreメソッドの第一引数としてProfileRequestのインスタンスを指定するだけで、先ほどのバリデーションに通過した場合のみstoreメソッド内の処理が実行されるようになります。
use App\Http\Requests\ProfileRequest;
class ProfileController extends Controller
{
/**
* プロフィールの保存
*
* @param ProfileRequest $request
* @return Response
*/
public function store(ProfileRequest $request)
{
}
}
4. エラーを表示
エラーは自分で定義する必要はなく、Laravel側で処理してくれるので私たちは表示部分を書くだけでOKです。
バリデーションが通らないと、エラーのフラッシュメッセージが保存された状態で前の画面が表示されます。
エラーメッセージを表示したい部分にこんな感じで書いてください。
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
メッセージの日本語化は、こちらを利用させていただいています☆
minoryorg/laravel-resources-lang-ja
5. 画像ファイルの保存
今回はローカルのstorageディレクトリにprofile_imagesディレクトリを作成(以下のプログラム実行時に自動で作成されます)し、”(ユーザID).jpg”というファイル名で登録することにします。
use Illuminate\Support\Facades\Auth;
class ProfileController extends Controller
{
/**
* プロフィールの保存
*
* @param ProfileRequest $request
* @return Response
*/
public function store(ProfileRequest $request)
{
$request->photo->storeAs('public/profile_images', Auth::id() . '.jpg');
return redirect('profile')->with('success', '新しいプロフィールを登録しました');
}
}
storeAsメソッドの第一引数に保存先のパス(/storage/appからの相対パス)、第二引数にファイル名を指定することで任意の場所・名前でファイルを保存することができます。
処理後は登録フォームがあるページにリダイレクトさせました。
一意のファイル名を自動で生成する場合は、storeAsメソッドの代わりに
storeメソッドを使用してください:)
$request->photo->store('public/profile_images');
返り値でディスクルートからの相対ファイルパスを取得できます。
withメソッドの引数に指定したメッセージは、フラッシュ―データとしてセッションに保存されます。
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
6. 保存した画像を表示
最後に保存した画像を表示してあげましょう。
storage/appにアクセスするために、シンボリックリンクを貼ります。
$ php artisan storage:link
プログラムは、単純にファイルが存在すれば表示するという形にします。
use Illuminate\Support\Facades\Storage;
class ProfileController extends Controller
{
/**
* プロフィール登録フォームの表示
*
* @return Response
*/
public function index()
{
$is_image = false;
if (Storage::disk('local')->exists('public/profile_images/' . Auth::id() . '.jpg')) {
$is_image = true;
}
return view('profile/index', ['is_image' => $is_image]);
}
}
existsメソッドを使ってファイルの存在を確認し、その結果をテンプレートに渡しています。
ここでは分かりやすさを優先してパスやファイル名をそのまま書いていますが、2箇所以上で使用する場合は変数や関数でまとめてしてしまうのもオススメです。
class ProfileController extends Controller
{
public static function getImagePath()
{
return 'public/profile_images';
}
public static function getImageFileName()
{
return Auth::id() . '.jpg';
}
}
ファイルが存在していれば画像を表示します。
@if ($is_image)
<figure>
<img src="/storage/profile_images/{{ Auth::id() }}.jpg" width="100px" height="100px">
<figcaption>現在のプロフィール画像</figcaption>
</figure>
@endif
[…] ➡Laravelで画像をアップロードする方法 […]