今日もタスクリストを確認しながら、Laravelの勉強をしてました。
成果物:タスクリストを確認しながら作成したソースコード
https://github.com/rinne-grid/my-laravel-quick-restart
まとめ
- 5.1向けのドキュメント(中級者向けタスクリスト)と、5.5での表記で異なるところがあり、だいぶハマった。
- Laravelとは全く関係ないところで、時間を費やした(仮想環境のMySQLサーバに対して、GUIクライアントから接続したかったけど諦めた)
まとめについて
- ルートモデルバインディングの書き方が違った
<?php // app/Providers/RouteServiceProviders.php // 5.1 $router->model('task', 'App\Task'); // 5.5 Route::model('user', App\Task::class); // ↑で駄目だった。↓はOKだった Route::model('task', 'App\Task');
- モデルと、モデルの認可ポリシーを関連付けるコードの書き方が違った
<?php // app/Providers/AuthServiceProvider.php // 5.1 protected $policies = [ Task::class => TaskPolicy::class, ]; // 5.5 protected $policies = [ 'App\Task' => 'App\Policies\TaskPolicy', ];
備忘録やメモ
Eloquent ORMの複数代入について
フォームの入力名とデータベースのカラム名を同じにして、 入力値をデータベース(もしくはORMがインスタンスのモデル)へ一気に代入してしまおうと言うアイデア
引用: Eloquentの複数代入のリスク https://kore1server.com/336/Eloquent%E3%81%AE%E8%A4%87%E6%95%B0%E4%BB%A3%E5%85%A5%E3%81%AE%E3%83%AA%E3%82%B9%E3%82%AF
- 個人的なイメージ
- $fillableはホワイトリスト(ここに登録したプロパティは複数代入できる)
- $guardedはブラックリスト(ここに登録したプロパティには複数代入できない)
ルーティング
<?php Route::get('/tasks', 'TaskContrller@index'); Route::post('/task', 'TaskController@store');
コントローラでのオブジェクトの作成
- Laravelリレーションでは、createメソッドが準備されている
- このメソッドは、属性の配列を受け取り、データベースに保存する前に、関連するモデルの外部キー値を自動的に設定する
- 以下の場合は、$result->user()でアクセスできる現在の認証済みユーザのIDを指定したタスクのuser_idプロパティに自動的に設定する
<?php $request->user()->tasks()->create([ 'name' => $request->name, ]);
viewやredirectでレスポンス(ビュー)を返す
view: resources/views直下のディレクトリを基準としたViewのパスを指定する
- return view('tasks.index');
redirect: Routeに記載した、コントローラメソッドとURIの紐付けにおいて、URI部分を指定する
- return redirect('/tasks');
タスク削除の実装
- Routeでは、Route::delete('/task/{task}', 'TaskController@destroy')を定義した
- しかし、削除するボタンはpostで定義している
- HTMLフォームはPOSTとGETのみを許可しているため、フォームのDELETEリクエストを見せかける手段が必要になる
- method_field('DELETE')関数を出力した結果は下記のようになる
<input type="hidden" name="_method" value="DELETE">
ルートモデル結合
app/Providers/RouteServiceProviders.phpのbootメソッド内に、 Route::model('task', App\Task);を追加することで 「ルートの宣言に、{task}があれば、指定されたIDに対するTaskモデルを取得する」ように指示が可能
5.1の$router->modelでは動作しなかったため調査したところ・・・
5.3からRouteで指定するように変更になった様子。
<?php public function boot(Router $router) { parent::boot($router); $router->model('user', 'App\User'); }
<?php public function boot(Router $router) { parent::boot($router); $router->model('user', 'App\User'); }
<?php public function boot() { parent::boot(); Route::model('user', App\User::class); }
しかし、5.3の表記を試しても 「Class App\Providers\App\Task does not exist」 が発生してしまったため、最終的に下記を指定した。
<?php public function boot() { parent::boot(); Route::model('task', 'App\Task'); }
認可
認証済みユーザが任意のタスクIDを指定することで、他のユーザのタスクを削除してしまうという 悪意のあるリクエストを仕込むことができてしまう。 ルートに注入されたTaskインスタンスが実際に認証済みのユーザが所有していることを 確認するため、Laravelの認可機能を利用する。
php artisan make:policy TaskPolicy
app/PoliciesにTaskPolicy.phpファイルが作成される
- destroyに関するポリシーを記述
<?php /** * @param User $user * @param Task $task * @return bool */ public function destroy(User $user, Task $task) { return $user->id === $task->user_id; }
- TaskモデルとTaskPolicyを関連付ける
- app/Providers/AuthServiceProvider.phpファイルのpoliciesプロパティに加える
書き方が変わっていたこともあり、1時間くらいハマってしまった。
<?php protected $policies = [ 'App\Task' => 'App\Policies\TaskPolicy', ];
- コントローラのdestroyメソッド内から、authorizeメソッドを実行する
- $this->authorize('destroy', $task);
- 最初の引数:呼び出したいポリシー名、検証したいインスタンス
- 現在のユーザについては、自動的にポリシーメソッドに送られてくるので、指定不要
- アクションが非許可の場合は、403例外が投げられ、ユーザにエラーページが表示される
新しく学んだことの整理
- モデルからデータを取得するパターン等で、Route::post('/hoges/{hoge}', 'HogeController@detail')に
hogeインスタンスを割り当てたい場合
- ルートモデルバインディングを行い、IDをモデルに渡して取得を内部的に実行してくれる
- ルートモデルバインディングに関する設定はapp/Providers/RouteServiceProvidersのbootメソッド内に記述する
- ユーザから悪意のあるリクエスト等、別ユーザのデータを削除することを禁止するため、認可を利用する
- php artisan make:policy PolicyNameでポリシーの作成を行う
- app/Policies/PolicyName.phpファイルが作成される
- ポリシー用のメソッドを記述し、ユーザインスタンスと検証したいインスタンスを引数に指定する
- return $user.id=== $task.user_id;などを指定。もしfalseを返すと403例外を投げる
- AuthServiceProviderのpolicies配列の中に、モデル => モデルポリシーの形式で記述する