Laravelのイベントとリスナを非同期で実行してみる
はじめに
イベントとリスナを実装するにあたって、具体例があった方がわかりやすいと思うので、今回は以下のような機能の実装を例にまとめていきたいと思います。
「画像レコード削除と同時に画像ファイルも削除する」
画像を扱うアプリケーションの場合、storageに画像ファイルを保存して、DBに画像のパスを保存する仕様が多いと思いますが、DBの方を削除すると同時に実データの画像ファイルも一緒に削除したい事があります。
今回はイベントとキューを使って画像ファイルを非同期で自動的に削除してみたいと思います。
前提
- Laravel7
- キューの実行環境ができている
- 画像のパスを含むテーブルが既にある
方法
レコード削除時deleting
というイベントが発生します。このイベントに独自のイベントを割り当てて、予め用意しておいた画像ファイル削除用クラス(リスナ)を呼び出します。この時、その場で実行せずキューへ登録します。
今回、キューについての詳細は省略します。(最後に簡単な実装方法あり)
イベントとリスナの関係
Laravelには、ある動作に対してイベントを発生させる仕組みがあります。そのイベントを受けて自動的に処理を実行するのがリスナです。
主な手順
- オリジナルのイベントとリスナの組み合わせを作る
- deletingイベント発生時に独自イベントを実行するようにする
イベント&リスナの作成
イベントとリスナをそれぞれ作っていきます。
イベント名をImageDeleted
リスナ名をDeleteImageFile
と仮定します。
EventServiceProviderにイベントとリスナを登録
まず、EventServiceProviderに以下のように登録します。
app/Providers/EventServiceProvider.php
protected $listen = [ Registered::class => [ SendEmailVerificationNotification::class, ], // これを追記 'App\Events\ImageDeleted' => [ // ←イベント 'App\Listeners\DeleteImageFile', // ←リスナ ], ];
作成コマンド実行
以下のコマンドを実行すると、app/Eventsとapp/Listenersにそれぞれファイルが作成されます。
$ php artisan event:generate
イベントファイルの編集
できたファイルを編集していきます。
対象テーブルのモデル名がImage
画像のパスのカラム名がImage_path
と仮定します。
app/Events/ImageDeleted.php
<?php namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; use App\Image; // ← 追記 class ImageDeleted { use Dispatchable, InteractsWithSockets, SerializesModels; public $image_path; // ← 追記 /** * Create a new event instance. * * @return void */ public function __construct(Image $image) // ← 追記 { $this->image_path = $image->image_path; // ← 追記 } /** * Get the channels the event should broadcast on. * * @return \Illuminate\Broadcasting\Channel|array */ public function broadcastOn() { return new PrivateChannel('channel-name'); } }
$image
から画像のパスを抜き出して$image_path
に入れておきます。(ここ重要)
通常は、$image
をそのまま$image
へ受け渡すだけでOKです。が、今回の場合、キューで処理する事と対象レコード自体が削除されるので、$image
をそのまま受け渡すと、キュー実行時「ModelNotFoundException」エラーが発生します。多分、実行時にはすでに対象レコードがないから?でしょうか。詳細は不明。
リスナファイルの編集
Storage
ファサードを使うのでuse
で読み込みます。
implements ShouldQueue
を追記します。(これがキューへ登録するための記述です。書かない場合は同期処理になります)
handle
関数内に削除処理を書きます。引数の$eventにイベントで定義した変数(画像パス)が入っています。
app/Listeners/DeleteImageFile.php
<?php
namespace App\Listeners;
use App\Events\ImageDeleted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Storage; // ← 追記
class DeleteImageFile implements ShouldQueue // ← 追記
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param ImageDeleted $event
* @return void
*/
public function handle(ImageDeleted $event)
{
Storage::delete($event->image_path); // ← 削除処理
}
}
‘image_path’には、storage/app以下のパスが入っているものとします。
削除イベントとの連携
イベントとリスナの組み合わせができたので、次に、deleting
イベントが発生したら先程作ったイベントを実行するようにします。
Imageモデルに次のように追記します。
app/Image.php
<?php namespace App; use Illuminate\Database\Eloquent\Model; use App\Events\ImageDeleted; // ← 追記 class Image extends Model { // これを追記 protected $dispatchesEvents = [ 'deleting' => ImageDeleted::class, ]; }
確認
以上でレコードの削除と同時に画像ファイルが削除されるはずです。
キューが実行待機状態になっている事を確認してから、レコードの削除をしてみてください。
$images = Image::where('id', 1)->get(); foreach ($images as $image) { $image->delete(); }
注意点
レコードの削除を実行する場合、delete()
の前にget()
をしないとdeleting
イベントが発生しません。削除するレコードのIDがわかっている場合は、delete()ではなくdestroy()を使えば常にイベントが発生します。
Image::where('id', 1)->delete(); // イベントが発生しない
まとめ
イベントとリスナを実装する具体例として「レコード削除と同時に画像ファイルを非同期で削除する」という少し特殊な場合を例にしてしまった為、わかりにくい部分もあったかもしれませんが、逆に理解が深まった部分もあったのではないでしょうか。
今回はキューの説明を含めると更に複雑になるので省略しましたが、最後に簡単な実装コマンドだけ載せておきます。
キューの実装方法
jobsテーブルを作る
$ php artisan queue:table $ php artisan migrate
.env の以下の箇所を編集
QUEUE_CONNECTION=sync ↓ QUEUE_CONNECTION=database
.envのキャッシュをクリア
$ php artisan config:clear
キューを実行
$ php artisan queue:work
-
前の記事
【Laravel】サーバ再起動でcronタスクスケジュールが実行されなくなった 2021.10.08
-
次の記事
Laravelでメール(IMAP)を受信して添付ファイルを抽出する 2022.02.15
コメントを書く