はじめに
こんにちは。webアプリケーションを作る際にファイルアップロードはごく当然の機能で、Laravelでももちろん標準でサポートされています。が、今回は要件次第では実装がほとんど不要になるライブラリlaravel-imageup
を紹介します。
(要件次第で、と前置きをつけた理由は後述します)
環境
- PHP 7.3.3
- Laravel 5.8.4
- MariaDB 10.3.13
導入
公式のREADMEに従って、composerでサクッとインストールします。
なお、依存先のライブラリであるIntervention Image
がPHPのGD拡張もしくはImagick拡張に依存するので、あらかじめ入れておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # インストール $composer require qcod/laravel-imageup Installing qcod/laravel-imageup(1.0.6):Downloading(100%) ... ... Writing lock file Generating optimized autoload files ... ... Discovered Package:qcod/laravel-imageup Packagemanifest generated successfully. # 設定ファイル生成 $php artisan vendor:publish--provider="QCod\ImageUp\ImageUpServiceProvider"--tag="config" Copied File[/vendor/qcod/laravel-imageup/config/imageup.php]To[/config/imageup.php] Publishing complete. # /storageを公開するためのシンボリックリンク作成(laravel-imageupはデフォルトでstorageを使う) $php artisan storage:link The[public/storage]directory has been linked. |
あとはライブラリを/config/app.php
に登録して導入完了です。
1 2 3 4 5 6 7 8 9 10 11 | 'providers'=>[ /* * Laravel Framework Service Providers... */ ... ... /* * Package Service Providers... */ Illuminate\View\ViewServiceProvider::class, ] |
実装
チュートリアルでお馴染みのmake:auth
でログイン機能を生成し、登録画面に画像アップロードのフォームを追加します。
なおマイグレーション処理は省略しますがUser
テーブルにファイル名を保存するためのavatar
カラムを追加済みとします。
1 2 | $php artisan make:auth Authentication scaffolding generated successfully. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <p>次に<code>register.blade.php</code>を編集します。form要素に<code>enctype="multipart/form-data"</code>を追加するのをお忘れなく。</p> <div class="form-group row"> <label for="avatar"class="col-md-4 col-form-label text-md-right">Avatar</label> <div class="col-md-6"> <input id="avatar"type="file"class="form-control{{ $errors->has('avatar') ? ' is-invalid' : '' }}"name="avatar"value=""required> @if($errors->has('name')) <span class="invalid-feedback"role="alert"> <strong>{{$errors->first('avatar')}}</strong> </span> @endif </div> </div> |
モデルクラス/app/User.php
にプロパティを追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespaceApp; useQCod\ImageUp\HasImageUploads; classUserextendsAuthenticatable { useHasImageUploads; protected$fillable=[ 'name','email','password','avatar', ]; // ライブラリ用に追記するプロパティ protectedstatic$imageFields=[ 'avatar' ]; } |
User
テーブルにレコードを作成する処理ところで、avatar
カラムに値を入れるためのコードを追記します。
1 2 3 4 5 6 7 8 9 | protectedfunctioncreate(array$data) { returnUser::create([ 'name'=>$data['name'], 'email'=>$data['email'], 'password'=>Hash::make($data['password']), 'avatar'=>sprintf('%s/%s',config('imageup.upload_directory'),$data['avatar']->hashName()),// 追記 ]); } |
実際にはRequestクラスでのバリデーションなどありますが、これで最低限の実装完了です。フォームを送信してみましょう。
アップロードした画像が/app/storage/app/public/uploads
に保存されていれば成功です。
個人的にハマったこと
配列はダメ
私がこのライブラリを使った時、実装の要件として「複数のファイルを一度にアップロードできる」というのがありました。当初は特に深く考えず以下のようなhtmlを書いてましたが、ここで軽くハマりました。
1 2 3 | <input type="file"name="avatar[]"> <input type="file"name="avatar[]"> <input type="file"name="avatar[]"> |
この状態のままだと、前述の$data['avatar']
に配列で格納され、ライブラリの自動アップロードが効かなくなるばかりかエラーを吐いてしまいます。
そもそもライブラリ側がIlluminate\Http\UploadedFile
を受け取ることが前提なので、この場合は以下2つの解決策があります。
- inputのname要素を
name="avatar_1"
name="avatar_2"
というようにユニークな値で記述する - 自動アップロードを無効化して(デフォルトでは有効)、foreachなどでひとつずつ手動でアップロードする
私は後者で解決しました。
自動アップロードを無効化する場合は設定ファイルで'auto_upload_images' => false
としてもよいのですが、アプリケーション全体に影響してしまうので、モデルクラスで以下のようにプロパティを設定します。
1 2 3 4 5 6 | protectedstatic$imageFields=[ 'avatar'=>[ // override global auto upload setting coming from config('imageup.auto_upload_images') 'auto_upload'=>false, ], ]; |
あとは$model->uploadImage($uploadedFile, 'avatar');
で、配列に格納されているUploadedFile
オブジェクトをひとつずつ渡せばOKです。
PHPStanで引っかかる
uploadImage
メソッドが、PHPコードを静的解析するツールであるPHPStanでエラーを吐きます。
ライブラリのPHPDocが以下のようになっているため「第二引数$field
がNULLのみ受け付ける」という意味になってしまっています。
1 2 3 4 5 6 7 8 9 10 11 | /** * Upload and resize image * * @param $imageFile * @param null $field * @throws InvalidUploadFieldException|\Exception */ publicfunctionuploadImage($imageFile,$field=null) { ... ... |
これはuploadImage
をオーバーライドするか、モデルクラスに適当なメソッドを作って、その中でuploadImage
を呼ぶようにしましょう。
さいごに
少々面倒なファイルアップロード処理を少ないコードで実装できるlaravel-imageup
を紹介しました。中身とその挙動を把握した上で使えばかなり便利だと思います。また、Laravel本体のバージョンに追随している点も地味に長所だと思います。