はじめに
こんにちは。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 Package manifest 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 | namespace App; use QCod\ImageUp\HasImageUploads; class User extends Authenticatable { use HasImageUploads; protected $fillable = [ 'name', 'email', 'password', 'avatar', ]; // ライブラリ用に追記するプロパティ protected static $imageFields = [ 'avatar' ]; } |
User
テーブルにレコードを作成する処理ところで、
avatar
カラムに値を入れるためのコードを追記します。
1 2 3 4 5 6 7 8 9 | protected function create(array $data) { return User::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 | protected static $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 */ public function uploadImage($imageFile, $field = null) { ... ... |
これは
uploadImage
をオーバーライドするか、モデルクラスに適当なメソッドを作って、その中で
uploadImage
を呼ぶようにしましょう。
さいごに
少々面倒なファイルアップロード処理を少ないコードで実装できる
laravel-imageup
を紹介しました。中身とその挙動を把握した上で使えばかなり便利だと思います。また、Laravel本体のバージョンに追随している点も地味に長所だと思います。