Laravel – データ削除

delete~データの削除

データを削除するルーティングとしてdeleteが準備されている。

前提

モデルデータの操作で準備した枠組みを使う。ユーザー認証機能を持ち、ユーザーごとに入力されたシンプルな投稿記事Postを扱う。

流れ

  1. DELETEメソッドでリクエスト
  2. posts.deleteルートでPostsControllerdestroyアクションを実行
  3. destroyアクションで指定されたデータを削除

destroyアクションの呼び出し

リソースルーティングで以下のように設定されていて、DELETEメソッドによるリクエストの場合、ドメイン/posts/(id)からPostControllerdestroyアクションにルーティングされる。ルーティング名はposts.delete

このルーティングにリクエストする場合、たとえばページの中にフォーム要素とINPUT(type="submit")要素を配置することが考えられる。

ここでルーティングに従えばDELETEメソッドを明示する必要があるが、現在のブラウザーではGETとPOSTしか認識しない。

フォームからDELETEメソッドをリクエストする場合、以下のようにフォームのmethodではPOSTを指定し、Laravelの@methodディレクティブでDELETEを指定する。

この例では、フォームのaction先にroute()メソッドを使い、第一引数でposts.daestroyのルーティング名を、第2引数で削除対象のデータ$postを渡している。これによってDELETEメソッドでURIとしてドメイン名/posts/(id)が渡される

なお@methodディレクティブは、以下のようなhidden要素を生成する。

コントローラー~destroyアクション

ルーティングに従って、PostControllerdestroyアクションを以下のように記述する。

ここでの要点は以下の通り。

  • destroy()メソッドの引数で、URLに埋め込まれたid$idとして受け取られている
  • Postクラスのfind()メソッドの引数に$idを渡して、指定したidの記事インスタンスを$postで参照している
  • 削除対象記事のインスタンス$postdelete()メソッドの引数に、更新対象の要素を渡している
  • データ削除を反映した一覧表示のため、indexにリダイレクトしている

まとめ

以上の設定により、たとえばフォームに配置された削除ボタンを押すと、その記事のidがURLに埋め込まれてDELTEメソッドでリクエストされ、destroyアクションでデータが削除される。

 

Laravel – データ編集・更新

create, store~データの入力と登録

リソースルーティングでは、以下を意図している。

edit
データを編集する。フォームで編集する場合、フォームを持つビューを表示する。
update
編集後の内容でデータベースのデータを更新する。editがフォームでの編集の場合、フォームのaction先をこのルートにする。

前提

モデルデータの操作で準備した枠組みを使う。ユーザー認証機能を持ち、ユーザーごとに入力されたシンプルな投稿記事Postを扱う。

流れ

  1. 編集する記事の$idを指定してドメイン名/posts/(id)/editをGETリクエスト
  2. posts.editルートでPostsControllereditアクションを実行
  3. editアクションで編集フォームを含むビューを表示
  4. 編集フォームのactionからposts.updateへルーティング
    methodPATCH
  5. posts.updateルートでPostControllerupdateアクションを実行
  6. updateアクションでデータベース上のデータを更新

editアクションの呼び出し

リソースルーティングで以下のように設定されていて、ドメイン/posts/(id)/editからPostControllereditアクションにルーティングされる。ルーティング名はposts.edit

編集対象の記事を指定してposts.editにGETリクエストでルーティングする例として、以下のようなアンカー要素がある。

route()メソッドの第2引数でモデルデータを指定すると、自動的にルーティングに沿った形でデータのidがURLに埋め込まれる。

上記の例ではルーティングがposts.editでURLがposts/{post}/editとなっているので、例えば記事idが3の記事の場合は以下のようなURLとなり、これがブラウザーにも表示される。

ドメイン名/posts/3/edit

コントローラー~editアクション

コントローラーでは、ビューの$titleと編集対象の記事のインスタンスを設定してresources/views/posts/edit.blade.phpを表示させる。

editアクションに来る際にURLに埋め込まれたidが、editアクションメソッドの引数$idとして受け取られる。この$idPostモデルのfind()メソッドの引数に与えて、指定したidの記事データを取得する。

編集フォームビュー

bladeテンプレート

投稿を編集するフォームはposts/edit.blade.phpに以下の要領で記述。

ここでHTTPのメソッドはPOSTだが、ルーティングではPATCHメソッドを期待しているので、@method('patch')ディレクティブが必要。

actionで指定しているroute('posts.update', $post)はデータベースの更新アクションupdateへのルーティング。

PATCHに対するルーティングのURLはドメイン名/posts/(id)となる。

フォームリクエスト~バリデーション

編集フォームのバリデーションは入力フォームのところで作成したフォームリクエストを使う。

コントローラー~updateアクション

use指定

PostモデルとPostRequestに対するuse指定はフォーム入力で指定済み

updateアクション

入力フォームのaction先で指定されたアクションメソッドで、編集後の内容でデータベースを更新する。

ここでの要点は以下の通り。

  • フォームリクエストのバリデーションを有効にするため、update()メソッドのインジェクションをPostRequestにしている
  • update()メソッドの第2引数で、URLに埋め込まれたid$idとして受け取られている
  • Postクラスのfind()メソッドの引数に$idを渡して、指定したidの記事インスタンスを$postで参照している
  • 編集対象記事のインスタンス$postupdate()メソッドの引数に、更新対象の要素を渡している
    • 更新対象の要素は、引数のリクエストで得られたフォーム入力のcommentonly()で限定している
  • 更新後のデータを含めた一覧表示のため、indexにリダイレクトしている

モデルでの$fillable設定

update()メソッドでのフォームリクエストによる登録はマスアサインメントになるので、モデルに$fillableの定義が必要(フォーム入力のところで定義済み)。

確認

ユーザーログイン状態で<a href="{{ route('posts.edit', $post) }}">編集</a>などでGETリクエストすると編集フォームのページが表示され、更新ボタンを押すとデータが更新される。

 

Laravel – データ表示

index~全データ表示

データの一覧を表示させるルーティングとしてindexを使う。

前提

モデルデータの操作で準備した枠組みを使う。ユーザー認証機能を持ち、ユーザーごとに入力されたシンプルな投稿記事Postを扱う。

流れ

  1. ドメイン名/postsをGETリクエスト
  2. posts.indexルートでPostsControllerindexアクションを実行
  3. indexアクションでログインユーザーの投稿記事を表示

indexアクションの呼び出し

リソースルーティングで以下のように設定されていて、GETメソッドによるリクエストの場合、ドメイン/postsからPostControllerindexアクションにルーティングされる。ルーティング名はposts

コントローラー~indexアクション

ルーティングに従って、PostControllerindexアクションを以下のように記述する。

all()メソッドは、Postモデルのデータが保存されているpostsテーブルから全データを取得して配列として返す。つまりこの段階では、すべてのログインユーザーが他のユーザーのものも含めた全投稿を見られることになる。

Postクラスや継承元のModelクラスはall()メソッドを持たないが、store()アクションで見たのと同じようにModelクラスのマジックメソッドから呼び出していると思われる。

投稿記事の配列はresource/posts/index.blade.php'posts'として渡され、これがindexビューで$postsとして利用される。

表示ビュー

投稿表示はindex.blade.phpに以下の要領で記述。

ここでは@forelseディレクティブ$postsに格納された投稿記事群を一つずつ取り出して、commentcreated_at(datetime型)を表示させている。

$postsindexアクションで読み込まれた投稿記事を格納した配列。

@forelseを使っているので、$postsが空の場合、つまり投稿が一つもされていない場合は、それに対応したメッセージが表示される。

Laravel – モデルデータの操作

概要

ここではモデルを使った基本の流れを整理する。

  1. フォームで入力したデータをデータベースに登録
  2. データベースのデータを表示
  3. 登録されたデータを編集・更新
  4. 登録されたデータの削除

例として、短いコメントを書き込む掲示板を想定する。

設定

ユーザー認証機能を実装。

データとしてユーザーが入力した投稿記事を扱う。

  • モデルは投稿記事を表すPost
  • コントローラーはPostController
  • postsに対するリソースルーティングを使う

準備

モデル

artisanでモデルを作成

マイグレーションファイルを編集し、マイグレート。

  • 投稿本文はcommentとし、200文字の文字列
  • 自動生成されるtimestampsdatetime型のcreated_atupdated_atに変更(2038問題対応)
  • user_idusersidを外部キーとして持つ
    外部キー参照

コントローラー

artisanでコントローラーを作成

ルーティング

postsに対してPostControllerを割り当てるリソースルーティングを記述。

Postモデルに関するリソースルーティングは以下の通り。

アクセス制限

ログインユーザーのみページを見ることができるようアクセス制限をかける場合は、PostControllerのコンストラクターでミドルウェアを指定する。

データの操作

 

Laravel – データが属しているユーザーを取り出す

概要

投稿記事を投稿したユーザーや商品カートの保有ユーザーなど、データが所属しているユーザーを取り出す場合。1対多のリレーションの多に属するデータから1のデータを取得する。

データモデルにリレーションを設定することで、そのデータが属するユーザーを属性として取り出すことができる。

手順

データモデルにuser()メソッドを定義し、そのデータがUserモデルのデータに属していることを記述する。メソッド名はUserの単数形となっていることに注意。

これによってデータモデルにuser属性が追加され、それを介してUserの各属性を取得することができる。

モデルデータ->user->name

例えばユーザー認証は設定済みとして、ユーザーの投稿記事モデルPostsを定義済みとする。Postモデルは記事の投稿ユーザーのIDをuser_idとして持ち、外部キー制約を設定している。

まずPostモデルにuser()メソッドを定義し、belongsTo(App\User)を戻り値とする。

  • belongsTo()PostモデルがUserモデルに属することから、Postのインスタンスが属するUserのインスタンスを返す
  • このリレーション設定によって、Postモデルにuserプロパティーが加えられ、Postインスタンスが属するUserのインスタンスを取得できる

これでPostインスタンスのuserプロパティーに、その記事を投稿したユーザーへの参照が格納される。

たとえば投稿記事が$postに格納されているとして、ビュー側でユーザーを取得して表示する場合、以下のようなディレクティブになる。

 

Laravel – ログインユーザーのデータのみ取り出す

概要

ユーザーが投稿した記事や購入予定のカート・商品など、ログインユーザーに関するもののみを利用したい場合。1対多のリレーションの1から多のデータ群を取得する。

以下のいずれかの方法がある。

  • モデルのwhere()->get()で抽出する
  • Userモデルにリレーションを定義し、Userのプロパティーとして取得する

where()メソッドを使う方法

手順

全ての記事を取得するならコントローラーのアクションでPost::all()として取り出すが、この場合はwhere()メソッドで全データのうちuser_idがログインユーザーのidと同じデータだけを抽出する。

SQLではWHERE user_id = ログインユーザーidとなるが、Laravelでは以下のように書く。

モデル::where('モデルのユーザーid', \Auth::user()->id)->get()

例えばユーザー認証は設定済みとして、ユーザーの投稿記事モデルPostsを定義済みとする。Postモデルは記事の投稿ユーザーのIDをuser_idとして持ち、外部キー制約を設定している。

以下はログインユーザーの全記事をPostControllerindexアクションで取得し、indexビューで表示させる例。ビュー側で受け取った$postsにログインユーザーが投稿した記事だけが配列で格納される。

モデルのリレーションを定義する方法

手順

Userモデルにモデル名の複数形(テーブル名に相当)でメソッドを定義し、リレーションを記述する。

これによってUserモデルの属性にメソッド名と同名のプロパティーが追加され、そのユーザーのidを外部キーに持つモデルのデータのみを取り出すことができる。

そしてコントローラーのアクションで、ログインユーザーのプロパティーでログインユーザーのidを持つデータのみを取得できる。

以下は先と同じPostモデルを扱う例。

まずUserモデルにPostの複数形(テーブル名)でposts()メソッドを定義し、hasMany(App\Post)を戻り値とする。

  • hasMany()は元のモデル(User)が複数の引数モデルを持つことから、Userのインスタンスに属する複数のデータの配列を返す
  • このリレーション設定によって、Userモデルにpostsプロパティーが加えられ、Userインスタンスに属するPostの配列を取得できる

これでユーザーのインスタンスのpostsプロパティーに、そのユーザーが投稿した記事だけが格納される。

コントローラーではログインユーザー(\Auth::user())のpostsプロパティーを参照すればよいので、以下のようになる。

 

Laravel – フォーム入力・データ登録

create, store~データの入力と登録

リソースルーティングでは、以下を意図している。

create
登録するデータを作成する。フォーム入力の場合、フォームを持つビューを表示する。
store
データをデータベースに登録する。createがフォーム入力の場合、フォームのaction先をこのルートにする。

前提

モデルデータの操作で準備した枠組みを使う。ユーザー認証機能を持ち、ユーザーごとに入力されたシンプルな投稿記事Postを扱う。

流れ

  1. ドメイン名/posts/createをGETリクエスト
  2. posts.createルートでPostsControllercreateアクションを実行
  3. createアクションで入力フォームを含むビューを表示
  4. 入力フォームのactionからposts.storeへルーティング
  5. posts.storeルートでPostControllerstoreアクションを実行
  6. storeアクションでデータベースにデータを登録

createアクションの呼び出し

リソースルーティングで以下のように設定されていて、ドメイン/posts/createからPostControllercreateアクションにルーティングされる。ルーティング名はposts.create

コントローラー~createアクション

コントローラーでは、ビューの$titleを設定してresources/views/posts/create.blade.phpを表示させるだけ。

入力フォームビュー

bladeテンプレート

投稿を入力するフォームはposts/create.blade.phpに以下の要領で記述。

actionで指定しているroute('posts.store')はデータベースへの登録アクションstoreへのルーティング。

フォームリクエスト~バリデーション

フォーム入力のバリデーションをフォームリクエストに記述する。

まず、以下のコマンドでフォームリクエストを作成。

app/Http/Requestsディレクトリーに作成されたPostRequest.phpを編集し、バリデーションルールを追加。ここでは唯一のフォーム入力commentに対して、入力必須(required)と最大200文字(max:200)を配列で設定している。

コントローラー~storeアクション

use指定

PostControllerPostモデルとPostのフォームリクエストを使うため、以下のようにファイル冒頭でuse指定する(個別の名前空間指定が不要になる)。

storeアクション

入力フォームのaction先で指定されたアクションメソッドで、フォームの入力をデータベースに書き込む。

ここではPostクラスが継承しているModelクラスのcreate()メソッドで、フォームリクエストのパラメーターをセットしてデータを書き込んでいる。

  • 入力バリデーションはマスアサインメント実行時にフォームリクエストで実行され、エラーがあれば$errorsに格納される
  • Post::create()は最終的にEloquent/Buildercreate()が実行されるらしい(参照:モデルのcreate()メソッドはどこに?)

データ書き込み後posts.indexにリダイレクトしているが、これは投稿記事一覧表示へのルーティング。

モデルでの$fillable設定

create()メソッドによる登録はマスアサインメントになるので、モデルに$fillableを定義しておく。

確認

ユーザーログイン状態でドメイン名/posts/createをGETリクエストすると入力フォームのページが表示され、投稿ボタンを押すとデータが登録される。

たとえばuser1とuser2をユーザー登録しておいて・・・

それぞれの記事を投稿した結果は以下の通り。

 

Laravel – モデルのcreate()メソッドはどこに?

Modelにcreate()メソッドがない

LaravelのUserや作成したモデルで呼び出すcreate()メソッドについて調べた過程。

たとえばLaravelでモデルとコントローラーをPostPostControllerのように生成し、store()アクション内でマスアサインメントによってデータを登録する場合に、以下のように書き、モデルクラスのスタティックメソッドcreate()を呼び出している。

ここでPostモデルのクラスを見てみると、EloquentModelクラスを継承している。

ところがIlluminate\Database\Eloquent\Modelクラスなどをたどってみても、create()メソッドが見当たらなかった。

マジックメソッドで実装している

以下のように、Modelクラスではマジックメソッド__call()__callstatic()が定義されている。

まず、Post::create()が実行されると、

  1. Postクラスにはstatic create()がない
  2. 継承元のModelクラスにもstatic create()がない
  3. Model::__callstatic()が呼ばれる
  4. new staticがlate static bindingで実行されてnew Post()と解釈される
  5. Postインスタンスにはcreate()がない
  6. 継承元のModelにもcreate()がない
  7. Model::__call()が呼ばれる

この__call()メソッドの動作はよくわかっていないが、create()の場合は最終的に以下が実行されるようだ。

forwardCallTo()ModeluseしているForwardsCallsトレイトのメソッドを呼んでいるが、要するにnewQuery()の戻り値のオブジェクトで定義されている$method(この場合はcreate())を呼んでいる。

ここからどんどん複雑になっていくので手に負えなくなるが、参考サイトを拝見するとEloquent\Builderというクラスのインスタンスが戻り値になるようで、その中にcreate()が定義されていた。

参考サイト:【Laravel】 第1回 Eloquent ソースコードリーディング – モデルの取得

尻切れトンボになってしまうが、ひとまずここまで。

 

PHP – マジックメソッド – __call()

__call()はマジックメソッドの一つで、実行させようとしたインスタンスのメソッドが存在しない時に呼ばれる。

以下の例では、MyClass__call()メソッドのみが定義されている。__call()の内容は、引数の$method$argsを表示させるようにしている。

このクラスに存在しないインスタンスメソッドを、引数なし、引数1個、2個で実行した場合の実行結果。引数は配列として$argsにセットされ、引数がない場合は空の配列、引数が1個の場合は要素数1(要素番号0)の配列となる。

 

Laravel – マイグレーションコマンド

表示

status

マイグレーションの状態表示

SQLの表示

migrateコマンドのオプションに--pretendを指定すると、マイグレーションにおけるテーブル生成のクエリーを表示する。

migrate:rollbackコマンドでも指定可能だが、rollbackの時と同じメッセージが出力される。他のコマンドでは、--pretendは指定できない。

マイグレーションの実行

マイグレートされていない全マイグレーションファイルのマイグレート

ロールバック

rollback

直前のマイグレーションのロールバック

指定したステップ分のロールバック(--step=1は指定なしと同じ)

マイグレーションの再実行

reset

実行済みの全マイグレーションのロールバック

refresh

実行済みの全マイグレーションファイルをロールバックし、マイグレーションを実行。

マイグレート済みの各マイグレーションファイルのdown()メソッドが実行された後、全マイグレーションファイルのup()メソッドが実行される。

デフォルトのdown()メソッドはテーブルをドロップするのみなので、後述のfreshと同じ効果。

fresh

全テーブルをドロップした後、全マイグレーションファイルをマイグレート。

ロールバックは行われずテーブルが再度新規に構築される。各マイグレーションファイルのdown()メソッドは実行されず、up()メソッドのみが実行される。