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 ソースコードリーディング – モデルの取得

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

 

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

表示

status

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

SQLの表示

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

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

マイグレーションの実行

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

ロールバック

rollback

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

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

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

reset

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

refresh

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

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

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

fresh

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

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

 

Laravel – モデル – 外部キー

外部キーの準備

通常、外部キーは相手方テーブルで自動連番として生成されたキーを対象とすることが多い。

例えば参照先のテーブルのプライマリーキーが'id'であるとして、これがAuto Incrementの場合にはマイグレーションファイルでは以下のように定義される。

$table->bigIncrements('id');

これにより生成されるカラムのデータ型はunsignedBigIntegerとなるので、参照元のテーブルで外部キーを設定するときは以下のように定義する。

$table->unsignedBigInteger('参照先テーブル名の単数形_id');

例えば参照先テーブルをcustomersとすると、マイグレーションファイルでは以下のように書かれる。

ordersテーブルから上記のcustomersテーブルを参照する場合、ordersテーブルの外部キーは以下のように記述する。

外部キーの設定

Laravelのモデルに外部キーを設定する場合、マイグレーションファイルでforeign()メソッドを使う。

具体的には、テーブルを作成するクラスのup()メソッドで呼ばれるSchema::create()メソッド内に以下を記述。

$table->foreign('外部キー')->references('参照キー')->on('参照テーブル');

外部キー制約

マイグレーションファイルで外部キーを設定した場合、デフォルトの外部キー制約RESTRICTになる。この制約をCASCADESET NULLに変更することができる。

削除、更新に対する制約はonDelete()onUpdate()をメソッドチェーンに追加して設定する。

引数は大文字でも小文字でもよく、CASCADESET NULLRESTRICTの何れかを指定する。

コード例

たとえば.投稿記事のPostモデルのテーブルがuser_idカラムを持ち、ここにUsersモデルのidを外部キーとする場合、PostのマイグレーションファイルのCreatePostsTableクラスのup()メソッドに、外部キー設定の行を記述する。

 

Laravel – ログアウト

概要

基本的な流れの一つ。

  1. form要素のアクションでlogoutを指定して、submitボタンを配置
  2. LoginControllerloggedOut()メソッドをオーバーライドしてリダイレクト先を指定

ログアウトボタンの配置

フォームとボタンによる方法

ログアウト機能を実装するビューで以下のようなform要素を記述。

アンカー要素による方法

ログアウト機能を実装するビューで以下のようなアンカー要素を記述。

アンカー要素はGETメソッドを発行するので、以下のルーティングをroutes/web.phpに追加。

その他の方法

このほか、form要素にhiddenタイプのinput要素を配置して、アンカータグでログアウトさせる方法もあるらしい。

ログアウト後のリダイレクト先を変更

LoginControllerで以下の様にloggedOut()メソッドをオーバーライドし、リダイレクト先を指定。

メソッドインジェクションのRequestは、ファイル冒頭でuseするならフルネームスペースでなくてもよい。

 

Laravel – アクセス制限

ミドルウェアの適用

コントローラー単位でアクション→ビューにアクセス制限をかける場合。コントローラーのコンストラクターでアクセス制限のミドルウェアを適用する。

$this->middleware('auth')

特定のアクションのみに適用したり、逆に適用除外にするには、only()/except()を使う。

たとえばマーケットサイトのCartControllerへのルーティングで、ログインユーザーのみ処理させるには以下の様にコントローラーを書く。

こうすると、ログイン状態でアクセスするとindexなどのアクションが実行されてビューに遷移するが、ログイン状態でない場合はログインページに遷移する。

middleware(‘auth’)

コンストラクターの$this->midlleware('auth')は、コントローラーの全アクションに対して名前'auth'のミドルウェアを適用する。

'auth'Kernel.phpでルートミドルウェアAuthenticateに結び付けられている。

Authenticateミドルウェア

Authenticateミドルウェアは、Laravelのプロジェクト作成時にapp/Middlewareディレクトリーに生成されている。

  • このミドルウェアのhandle()メソッドはIlluminate\Auth\Middleware\Authenticateで定義されている
  • $request->expectsJson()でログイン状態にあるかどうかを判定しているようだが、その流れはよくわかっていない

この'login'へのルーティングを変更すると、未ログインの場合の遷移先を変更できる。

 

Laravel – ログインユーザーの取得

ログインユーザーとプロパティーの取得

ログイン済みのユーザーは、staticメソッド\Auth::user()で取得できる。

ログインユーザーのプロパティーは、以下のように取得できる。

Authは名前空間のルートにあるので先頭の'\'が必要(\Auth)。

なお、\Auth::user()は未ログイン状態ではエラーとなるので、コントローラーでアクセス制限をかけるか、\Auth::check()@auth....@endauthディレクティブでチェックを入れる。

例えば以下は、ビューでログインユーザーを表示させる例。

ただし未ログイン状態のときはエラーになる。ログイン状態の時だけ実行する書き方として、以下の2通りがある。

または

 

Laravel – LoginController

概要

LoginControllerは、Laravelのプロジェクト開始時にapp/Http/Controllers/Authディレクトリー下に生成される。

ルーティングファイルにAuth:routes()を記述することで、このコントローラーの各アクションは以下のようにURLと結び付けられる。

method URL action name
GET /login showLoginForm login
POST /login login

ソースコード

内容

コントローラーの機能

ソースコード冒頭のコメントの要旨は以下の通り。

  • このコントローラーは、アプリケーションにおけるユーザー認証とホームページへの遷移を行う
  • コントローラーはトレイト(AuthrsenticatesUsers)を用いてこの機能を導入している。

AuthenticatesUsersトレイトの導入

AuthenticatesUsersはLaravelで準備されたトレイトで、showLoginFormアクション、loginアクションを実装している

登録後の遷移先

ログイン後の遷移先を設定している。ログイン後の遷移先を変更するには、ここでリダイレクト先を変更するか、HOME定数を変更する。

RouteServiceProviderはコントローラーの冒頭でuseされている。

RouteServiceProviderを見ると、public定数HOME'/home'に設定されている。

ミドルウェアの適用

コントローラーのコンストラクターでミドルウェアを適用している。

'guest'Kernel.phpでルートミドルウェアの名前として定義されていて、RedirectIfAuthenticatedミドルウェアを指している。

RedirectIfAuthenticatedミドルウェアはコントローラー実行前にAuth::guard()メソッドを呼び出している。

 

Laravel – middleware

概要

ミドルウェアを作成・登録する手順は以下のとおり

  1. artisanでミドルウェアを作成
  2. ミドルウェアの処理内容を記述
  3. ミドルウェアを登録
  4. ルーティングでミドルウェアの組み込みを設定

参考サイト:【Laravel】ルーティングのミドルウェアとは?作成手順と実例

準備

以下のルーティング、コントローラー、ビューを準備する。

ルーティング:

コントローラー:

ビュー:

なおコントローラーとビューで、以下のヘルパーコントローラーのconsole_log()メソッドで表示をさせていて、ミドルウェアでもこのメソッドを使っていく。

ミドルウェアの作成・登録手順

ミドルウェアの作成

以下のコマンドでミドルウェアのファイルを作成。

php artisan make:middleware ミドルウェア名

ファイルはapp/Http/Middlewareディレクトリー下に作成される(middlewareはuncountableだが、状況によってはmiddlewaresという複数形もあり得るらしい)。

作成されたファイルの内容。

ミドルウェアの処理内容の記述

ミドルウェアの処理には、その実行タイミングに応じた書き方がある。

それらについては後述するが、ここでは仮にhandle()メソッドに別途準備したconsole_log()で表示をさせている。

ミドルウェアの登録

登録の種類と手順

作成したミドルウェアはapp/Http/Kernel.phpファイルで登録する。

登録方法により、グローバル登録、ルーティング登録、グループ登録の3種がある。

グローバル登録

Kernelクラスの$middlewareプロパティーに作成したミドルウェアを加えると、すべてのルーティングにミドルウェアが適用される。

以下の例では、デフォルトで生成・登録されるミドルウェアに加えてSampleBeforeMiddlewareを登録している。

ルート登録

Kernelクラスの$routeMiddlewareプロパティーに、名前とともにミドルウェアを登録する。

以下の例では、デフォルトで生成・登録されるミドルウェアに加えて、SampleBeforeMiddleware'routed_middleware'という名前で登録している。

グループ登録

Kernelクラスの$middlewareGroupsプロパティーに、グループ名をキーとし、ミドルウェアの配列を値として登録する。

以下の例では、デフォルトで登録される2つのグループ'web''api'に加えて’test'というグループを追加し、そのグループにSampleBeforeMiddlewareを登録している。

ミドルウェアの適用

グローバル登録の場合は全適用

グローバル登録されたミドルウェアは、ルーティング設定がなくても全てのコントローラーのアクションに適用される。

この結果、アプリケーションの任意のURLにアクセスすると、ブラウザーのコンソールに以下の様に表示される。

ルーティングファイルでの適用

ルート登録・グループ登録のいずれの場合も、ルーティングで特定のルートにミドルウェアを適用できる。

ルートミドルウェアの場合は登録した名前で適用。

グループミドルウェアの場合は登録したグループ名で適用。

結果はいずれも同じで、以下のようにブラウザーのコンソールに表示される。

コントローラーのコンストラクターでの適用

コントローラーのコンストラクターの中で、middleware()メソッドの引数にミドルウェアの登録名やグループ名を指定して適用できる。

この場合、ルーティングファイルで設定しなくても、当該コントローラーのアクションに対してミドルウェアが適用される。

以下はルートミドルウェア名で登録する場合。

以下はグループ名で適用する場合。

結果はいずれも同じで、以下のようにブラウザーのコンソールに表示される。

ミドルウェアの条件

before middleware

コントローラーが呼ばれる前に実行すべき文を、$next($request)の実行より前に書く。

このとき、$requestからパラメーターを取り出し、その条件に応じた処理をさせることができる。

after middleware

handle()メソッド内で$next($request)を実行した後に、その結果に応じた処理を記述すると、コントローラーの実行後・ビュー表示前にその内容が処理される。

terminate()メソッド内に処理を記述すると、それらはビュー表示後に実行される。

ここで、この2つのミドルウェアをKernelクラスでグループ登録する。

ルーティングへの適用はグループ名で。

実行すると、ブラウザーのコンソールに以下の様に表示される。

なお、上記ではルーティングファイルでミドルウェアを適用したが、コントローラーのコンストラクターで適用しても結果は同じ。

only/except~部分指定と除外

準備

コントローラーのコンストラクターでミドルウェアを適用する場合、コントローラーの特定のアクションのみに適用したり、適用除外にしたりすることができる。

まず、以下のようなルーティング定義、コントローラーを準備する。ミドルウェアとビューはこれまで使ってきたものと同じ。

ルーティング:

コントローラー:

ここで2つのURLをブラウザーで指定すると、それぞれ以下の様に表示される。

middleware/select

middleware/ignore

only()~部分指定

コンストラクター内のmiddleware()に対してonly()メソッドを実行すると、引数の配列で与えたアクション群に対してだけミドルウェアが適用される。

以下の例ではselect()メソッドのみ適用対象としていて、ignore()メソッドにはミドルウェアは適用されない。

この結果、middleware/selectにアクセスした結果は先の結果と変わらないが、middleware/ignoreにアクセスした場合はミドルウェアが実行されず、以下のようなコンソール表示になる。

except()~除外指定

コンストラクター内のmiddleware()に対してexcept()メソッドを実行すると、引数の配列で与えたアクション群に対してだけミドルウェアが適用されなくなる。

以下の例ではignore()メソッドのみ適用除外となり、ミドルウェアが適用されない。

結果はonly()の場合と同じになる。

 

PHP/Laravel – console.log

ちょっと小回りが利かない

webアプリケーション開発の過程で変数の内容をちょっと確認したいとき、Laravelならdump()dd()が使えるが、エラーの場合は内容が確認できなかったり、確認の都度実行を停止しなければならなかったりする。

実行させながら変数の内容を表示させるには、var_dump()などの関数でブラウザーに内容を表示させるのが一般的だが、表示が崩れ、ページが遷移すると確認できない。

JSを使った簡易な対応

Railsのようにサーバーコンソールへの出力やJavascriptのconsole.logのように、ページとは別のところで変数の内容を確認したいとき、標準出力に振り向けるような設定もあるようだが、以下のコードでとりあえずブラウザーのコンソールへの表示ができる。

文字列や配列の表現中にシングルクォートやダブルクォートが含まれるとエラーが出る場合もあるので、変数を結合するときにjson_encode()を使っている。

実行例

以下はPHPでの実行例で、上のコードを関数化している。Laravelならヘルパー、staticクラス、staticメソッドのトレイトなどで準備することが考えられる。

以下は実行の結果生成されたHMTL例で、HTMLドキュメント出力の前段だとhead要素内、出力後だとbody要素内にscript要素が配置される。

以下はコンソールへの出力部分。これで実行を止めずに変数の確認ができる。

Laravelの場合

コントローラーのメソッドに定義

コントローラーでメソッドを定義する方法で可能。

  • コントローラーのstaticメソッドでconsole_log()を定義
  • コントローラー内は$this->console_log()で呼び出し
  • ビューでは{{ namespace\controller::console_log() }}で呼び出し

コントローラーの例

ビューの例

ルーティングからConsoleLogController@show()が実行されると、ブラウザーにビューが表示され、F12キーで以下の表示が確認できる。

ヘルパーコントローラーを作成する方法

以下のようにヘルパーコントローラーをapp/Http/Controllers下に作成し、staticメソッドとしてconsole_log()を定義。

php artisan make:controller HelperController

コントローラーのアクションでは以下の様に呼び出す。

ビューでは以下の様に呼び出す。

 

Laravel – ログイン処理の流れ

ログインフォーム表示

ルーティング定義

ルーティングファイルにAuth::routes();を記述することでユーザー認証関係のルーティングが自動設定される。その中には、以下のログインフォームへのルーティングが含まれる。

  • メソッド:GET
  • URL:login
  • アクション:Auth\LoginController@showLoginForm
  • ルート名:login

showLoginFormアクション

LoginControllerコントローラーはAuthenticatesUsersトレイトを利用していて、その中でshowLoginFormアクションが定義されている。

AuthenticatesUsersトレイトのshowLoginForm()アクションメソッドは以下の通りで、auth.registerビューをレンダリングしている。

auth.registerビューは初期状態では存在しないのでコマンドで生成するかカスタムで作成する必要がある

フォームからのPOSTとログイン実行

ルーティング定義

ルーティングファイルにAuth::routes();を記述することで、以下のルーティングが定義される。

  • メソッド:POST
  • URI:login
  • アクション:Auth\LoginController@login

loginアクション

showLoginFormアクションと同じく、loginアクションもAuthenticatesUsersトレイトで定義されている。

このアクションの動作は以下の通り。

バリデーション

最初はログインフォームのバリデーション。

  • validateLogin()メソッドは同じAuthenticatesUsersトレイトで定義されている

連続エラー時の抑止処理

次はログインエラーが連続したときのログイン抑止処理。ここで捕捉されなければ後段のログイン認証に移る。

  • ここで呼ばれているhasTooManyLoginAttempts()fireLockoutEvent()sendLockoutResponse()の各メソッドは全てThrottlesLoginsトレイトで定義されている
    • hasTooManyLoginAttempts()はログイントライ回数のがmaxAttempts()(デフォルトでは5回)を越えているかどうかをチェック
    • fireLockoutEvent()Lockoutイベントを発火させる
    • sendLockoutResponse()は一定時間ログインを抑止する

      表示例。秒数は30秒程度で試行ごとに変化した。

      Too many login attempts. Please try again in 37 seconds.

認証チェック

上記で、バリデーションと連続トライ回数以内の確認が通ったなら、attemptLogin()で認証チェックを行う。

  • attemptLogin()は同じAuthenticatesUsersトレイトで定義されている。

    • $this->guard()ファサード関係の複雑な処理を経て、\Illuminate\Auth\SessionGuardクラスのインスタンスとなる
    • $this->credentials()は以下のように定義されていて、ユーザー名とパスワードが認証に使われる
    • SessionGuard::attempt()は以下のように認証・ログインを行う
    • SessionGuard::login()メソッドでは、セッション更新、Rememberトークン処理、ユーザーのセットなどが行われる

ログイン失敗時のカウント

ログイン失敗時には失敗回数をカウントアップする。この結果は連続エラーのチェックで参照される。