みなさん、こんにちは。

最終回となる今回は、削除機能についてです。初心者向けのチュートリアルでは、削除ボタンが押されたら動作するという単純な機能でした。

しかしユーザーという概念があるので、削除するタスクは作成したユーザーのみが行えるようにしないといけません。ですので、認可という機能を使うようにします。

それと、蛇足ですが論理削除にします。初心者向けのチュートリアルでは単純故にDBレコード自体を削除する物理削除でしたが、この方法では元に戻せません。

完全にデータを消すのはよろしくありませんので、論理削除を使いましょう。

削除用ルートの確認と概要

まずは削除するボタンを用意します。

削除用のルートはRoutes.phpに書きましたね。

Route::delete('/task/{task}', 'TaskController@destroy');

この{task}は、初心者向けの時ではタスクのIDとして利用していましたが、中級者向けでももちろん使います。そして{task}にはIDが入ることが想定されますが、もう少しテクニカルな使い方をします。

ユーザーという概念がある以上誰が作ったタスクなのかという情報、ユーザーID が必要なのです。

何も考えなければ、指定されたidのタスクを取得するSQLを一回発行すれば取得できます。まぁ、これでも問題ないっちゃないですがLaravelはもっとスマートに書けます。

このSQLを自動で発行し、取得結果をインスタンスとして受け取るということします。そのためにルートモデルの結合を行いましょう。

ルートとモデルの結合

この結合はどういった時にできるのかを説明します。

$ php artisan route:list

ルートの一覧を表示した時、task/{task}のように{ }で囲われたものを見かけると思います。

この{ }で囲われたものはアクションメソッドの引数になるものです。ここにインスタンスをLaravelが勝手に注入してくれるようになるので、アクションメソッドで受け取りたいインスタンスをタイプヒントするだけで指定のidのインスタンスを取得できます。

では、さっそく結合させてみましょう。サービスプロバイダーの機能になりますんで、公式のサービスプロバイダーのページを見ておくと理解しやすいと思います。

app/Providers/RouteServieProvider.phpboot()があります。

ここに、Taskインスタンス、つまりTaskモデルを結合させるように次のコードを記述します。

これにより、ルートで出てくる{task}で指定されたidに対応するTaskモデルを取得することができるようになります。

ここで注意することがあります。インスタンスを取得するのはいいけど、取得条件はどのカラムを見るのかということです。/vendor/laravel/framework/src/Illuminate/Routing/Router.phpmodel()を見ればわかります。

プライマリーキーを取得条件としていますので、例えば{task}に1を渡したとしましょう。すると発行されるSQLは次のようになります。

SELECT * FROM Tasks WHERE id = 1 AND deleted_at IS NULL LIMIT 1

ちなみに、指定のidのインスタンスが取得できなかった時はNotFoundHttpExceptionという例外が投げられます。

以上を踏まえて、destory()はどう定義できるかというと次のようになります。

ポリシーの作成

EloquentORMのdelete()を使えば、この時点でも削除することができます。

だがしかし!待ってください!

引数に適当な数字を指定すると、自分以外のタスクも消せてしまいますね。これはよくない。

なのでどうするかというと、そのタスクの作成者が誰なのかを確認する必要があります。タスク作成時にユーザーIDを登録しているので、このIDとログインしているユーザーのIDが一致すればいいわけです。

そこで、Laravelの認可(Policy)という機能を利用します。

そして、やはりLaravelにはPolicyクラスを作成するコマンドがあります。以下を実行しましょう。

$ php artisan make:policy TaskPolicy

各ポリシーはモデルに対応するのが基本らしいので、Taskモデルに関するポリシーはTaskPolicyとなります。ちなみに、Policiesディレクトリがなくても勝手に作られ、その下に各ポリシーが作成されます。

では、destroy()に対してポリシーを追加してみましょう。単純にログインユーザーのIDをタスクのユーザーIDが一致しているかを調べます。

ポリシーの実行

作成したポリシーはモデルと関連付けられる必要があります。

公式のチュートリアルではapp/Providers/AuthServiceProvider.phpを編集していますが、composerでLaravel5.1をインストールしてもこのファイルがないことがあります。

ですが、サービスプロバイダのクラスはコマンドでも作成できるので作りましょう。

$ php artisan make:provider AuthServiceProvider

これを以下のように編集します。

$policiesプロパティは連想配列になります。キーにモデル、値にポリシーを指定しましょう。

Githubのlaravel/quickstart-intermediateにソースコードがあるので詳しくはそちらをご覧ください。

関連付けができればあとはコントローラーのdestroyを以下のように書き換えて、ポリシーを実行しましょう。

authorize()の第1引数は、先ほど作成したポリシークラスで定義したメソッドを指定します。第2引数は、関心を向けているモデルインスタンスです。このモデルインスタンスはポリシーと関連付けられてるはずなので、どのポリシーのメソッドを起動するのかはLaravelが勝手に解釈してくれます。またユーザーのインスタンスは自動的に送られます。

ポリシーメソッドはtruefalseを返す必要があります。trueが返れば許可されたとみなし、その後のコードの処理は続きます。falseが返るとそのアクションは許可されなかったということになるので、403の例外が投げられます。

ポリシーは様々な使い方ができるので、公式の認可のページを一読するといいと思います。

さて、ここまでで削除機能は完成しましたが、もしかするとMethod [authorize] does not exist.というエラーがでるかもしれないです。

このエラーが出たなら、継承したControllerクラスでAuthorizesRequestトレイトを読み込んでいないと思われます。

このトレイトは通常、全コントローラーで読み込まれるものです。なので、app/Http/Controllers/Controller.phpを次のように編集して、トレイトを読み込むようにしましょう。

論理削除

ここまでで、削除処理に関しては完了してます。

しかし、このままではデータ自体を完全に削除してしまう物理削除になっています。元に戻せなくなったり、何がデータベースに登録されていたのかということが起きます。

それを防ぐために、削除されたことにする論理削除という方法を利用します。Laravelはとても簡単に論理削除にすることができます。

論理削除の仕組み

  • マイグレーションファイルにdeteled_atを設定する
  • モデルクラスで論理削除用のトレイトを指定する
  • 削除用のメソッドを実行すると、deleted_atに実行した時間がtimestampで挿入される
  • 取得する時は、deleted_atがnullかどうかをLaravelが判断する

第1回Taskテーブルのカラムの設定をする時に、$table->softDeletes()を利用してdeleted_atというカラムを追加したのを覚えていますか?そうです、ここで使われます。

では、Taskモデルを次のように編集します。

たったこれだけです。これだけで論理削除になりました。実際に削除を実行するとdeleted_atに削除時間が入っていると思います。

そして、取得する時はwhere句にdeleted_at IS NULLが自動的にSQLに入っているので、ちゃんと削除されたことになっています。

終わりに

さて、以上ですべてが終わったわけですが、いかがでしたでしょうか。

おそらく1回で完全に理解はできないと思います。理解を深めるために何回も繰り返しましょう。また、効率良く理解するために、コピペではなく自分で書いてみることが重要です。

もしわからない部分や間違って説明している部分があれば、お問い合わせページからメールを送信してください。わかる範囲で返信します。TwitterやFaceBookでもいいですよ!

これにて中級者向けチュートリアル、完!