laravel5.2.4あたり CSRFミドルウェアが消えない現象

表題の通りなんですが、CSRFミドルウェア(app\Http\Middleware\VerifyCsrfToken.php)がroutes.php(app\Http\routes.php)で設定しなくても動いている現象に遭遇。

なんでroutes.phpの設定どおりに動かないの?って思ってたらwebミドルウェアグループが2重に設定されている。。

 

ws000852

ws000855

ws000854

 

2重?ってどういうことだ?と調べてみたらいました。

RouteServiceProvider.php(app\Providers\RouteServiceProvider.php)に。。。

 

ws000853

 

デフォルトでwebミドルウェアグループが設定されています。お前か。

多分ミドルウェアグループが設定されてなくてセッション動かないんだけど?みたいな問い合わせが多かったから変えたんだと思いますがわかりづらい。。

 

参考

http://stackoverflow.com/questions/37069203/laravel-5-2-web-middleware-is-applied-twice

https://readouble.com/laravel/5.2/ja/middleware.html

 

ドキュメントにも書いてはあります。。見つからなかった。。

laravel 5.2 Eager Loadingによる集約関数(MAX, MIN, COUNT, SUM…)のやり方

なんだか気温が安定しませんね。

春っぽくなってきました。

 

今回は laravel の eager loading についてです。

eager loading って名前聞いただけだと全然わからないですよね。。laravelってこういうの多い気がします。

 

eager loading とはなんぞや?ということは

http://readouble.com/laravel/5/1/ja/eloquent-relationships.html#eager-loading

を見てもらえば早いのですが、要するに「リレーション先のレコードを IN 句でまとめて取ってきて、一つの結果にまとめますよ」っていう機能です。

伝わりにくいですね。

正直普通の1対1のリレーションなら使う必要はないと思います。

 

単純な実例で示しましょう。

 

今回、こんな二つのテーブルを考えます。test_user テーブルと test_user_item テーブル です。

userテーブルitemテーブル

osanai, shinoda, takenaka の3人がいて、それぞれアイテムを持っています。

 

osanai は 「1億円のお金と2千万円の車」 を持っています。

shinoda は 何も持ってない。

takenaka は「1000円の鉛筆、100円のノート、200円の消しゴム」 を持っています。

格差を感じます。

 

ここで「各ユーザーが持っているアイテムの最高金額が知りたい」という場合です。

SQLで言えば、

SELECT test_user.user_id, test_user.user_name, MAX(test_user_item.price) as max_price

FROM test_user LEFT JOIN test_user_item ON test_user.user_id = test_user_item.user_item

GROUP BY test_user_item.user_id;

 

です。

もちろんこのまま書いてもいいんですが、ページネーションの時とかに少し面倒です。

もともとは GROUP BY 必要ないのに、1:多 の複数レコード対応のために、(test_user_itemテーブルに、1つのuser_idに対応する複数のレコードがあるから)

GROUP BYを追加しなくちゃならないのはなぁ。。。みたいな場合ですね。

 

特にページネーションでは1ページに表示する件数が100件以下の場合が多いので、取得した全レコードに対してGROUP BY をするのは非効率なときもあります。

なので、「今開いているページのuser_idに対応するアイテムのデータだけ取ってきたい」ってやれたらいいんじゃない?を実現したいわけです。

つまり、

 

1つめのクエリ

SELECT test_user.user_id, test_user.user_name FROM test_user LEFT JOIN test_user_item ON test_user.user_id = test_user_item.user_id ;

 

2つめのクエリ

SELECT MAX(test_user_item.price) as max_price FROM test_user_item WHERE test_user_item.user_id IN (…必要なuser_id(1つめのクエリで取得したuser_id))

GROUP BY test_user_item.user_id;

 

みたいにしたい。

こうすると必要なuser_idでだけデータを取得できてスピードも速いのでは?ってことですね。

これを実現するのがeager loadingです。

 

設定の順番は、

 

1. リレーションを設定する。

2. 1のリレーション設定の中で取得時に発行するSQLを書く

3. Model::withメソッドでeager loading設定

です。

 

1. リレーションの設定

まず、test_user テーブルに対応する TestUser クラスと、test_user_item テーブルに対応する TestUserItem クラスを作成します。

WS000252

WS000258

そして、メインとなるテーブル(今回はtest_user テーブル)に対応するTestUserクラスに、JOIN先のテーブル(test_user_itemテーブル)に対応する

クラス名のメソッド(public function TestUserItem)を作成します。

このメソッドの中で、リレーションを書いていきます。

 

2. リレーション時に発行するSQLを書く

今回は、最終的に「各user_id に対応するアイテムの最高価格が “1つ” 欲しい」ので、has one (1:1)です。

実は 1:多の関係(has many, belongs to)でも大丈夫ですが、viewで取得するとき少しめんどくさいです。

WS000259

ちなみに、hasOneメソッドで取得できるのは HasOne クラスで、親クラスはRelationクラスです。

Relation クラスでは マジックメソッドで \Illuminate\Database\Eloquent\Builder クラスのメソッドを呼んでるので、続いてクエリビルダーで書けるってわけですね。

WS000245

WS000246  WS000247  WS000248 WS000249

ちょっと寄り道しましたね。

今は 「リレーションを設定する」「リレーション時に発行するSQLを書く」の途中です。

 

hasOneメソッドで (クラス名, 結合先テーブルのカラム(test_user_item.user_id), 結合元テーブルのカラム(test_user.user_id)) を指定して、

「FROM test_user LEFT JOIN test_user_item ON test_user.user_id = test_user_item.user_item」を作ります。

hasOneメソッド中ではテーブル名は不要です。

 

あとは作りたいSQLに対してクエリビルダーを書いて、クエリビルダーをTestUserItemメソッドの中でreturn するだけなんですが、注意点が一つ。

SELECT に指定するフィールドに 「JOIN時に用いるカラム」を設定しなければいけません。

下の赤丸で囲んだ部分ですね。

WS000257

これはeager loading の機能で、取得した結果から user_id を取得して IN句を作ってるからだと思います。具体的な処理は見ていませんが。。。

なお、groupBy と selectRawを書いてる場所を分けてるのは特に意味はないです。全部繋げて書いて大丈夫です。

 

3. Model::withメソッドでeager loading設定

上で設定した 「リレーション先取得SQL」をeager loadingする設定です。

WS000260

with メソッドの引数に対応するリレーションを表すメソッド名(TestUserItem)を書きます。

これでOK。

コントローラとビューも見てみましょう。

WS000250 WS000262 WS000251

WS000261

ちゃんと取得できていますね。SQLも正しく発行されています。

ただ、view 側で気をつけなくてはいけないのは、「該当するリレーション先が存在しない場合があるときは、存在チェックを入れないといけない」ってことです。

@if(isset($each_result->TestUserItem))の部分ですね。

 

今回で言えば、user_id = 2 (shinoda)は、何もアイテムを持っていませんでした。

この場合は$each_result->TestUserItem が未定義になってしまうので、$each_result->TestUserItem->max_resultを取得しようとしたときにエラーになります。

ここだけ注意です。

 

実際 eager loading を使う場面は 「普通にjoinできない または join時の条件が複雑でindexが利かせにくい」って時だと思います。

join先のテーブルが違うDBにあるとか、今回みたいに対応するレコードのうち、

「一番大きい, 一番新しい」だけ欲しいって時ですね。

履歴系テーブルの最新情報だけ欲しいみたいなときにいいかもしれないです。

 

では。

laravel5.2 コントローラでのリクエストオブジェクトの3種類の取得方法

いろんなインスタンス化方法があるよって話です。

画像見れば一発。

 

WS000235

1 メソッドの引数をサービスコンテナで自動的に解決する

2 requestヘルパー関数を使う

3 appヘルパー関数からリクエストインスタンスを指定する

 

どれも内部ではサービスコンテナを使ってるはず。

お好きなものをどうぞ。

laravel5.2 リクエストオブジェクトにコントローラでパラメータを追加する

3月になりました。まだまだ寒いですね。

 

意外とコントローラ→viewの記事が少なかったのでそういう系統を。

題名にある通り、コントローラでパラメータを付けたい!っていう場合です。

 

フォームにテキストボックス一つだけの単純なページで確認。

 

WS000225WS000226 WS000220

 

ちなみに、リクエストオブジェクトがこうやってコントローラーのメソッドで引数として取得できるのはlaravelのサービスコンテナが自動的にインスタンスをとってきてくれるからなんです。

すごいですねー。

こういうhtmlでは、リクエストの結果を表示すると次のようになります。

Requestオブジェクトのallメソッドは、パラメータを全部取得します。

 

 

WS000227 WS000228 WS000229

テキストボックスのインプットだけちゃんと入ってますね。

ここに、ビュー(HTML)上にはないけどデフォルトの値を追加したい!って場合が今回の話。

 

で、先に答えを言うとRequest::merge(array $input)っていうメソッドがあるのでそれを使えば大丈夫です。

「addとかsetとかないですけどー」ってずっと探してました。

 

こんな感じです。

WS000232 WS000234

 

「getとpostどっちに追加されるのよー」って思ったかもしれません。

その時のリクエストに応じて自動的にやってくれます。

 

laravel/framework/src/Illuminate/Http/Request.php

WS000230 WS000231

 

素晴らしいですね。

ただ、merge という言葉の通り「上書き」されるので、そうされたくない時は存在チェックしてからmerge です。

 

WS000233

これでok。

laravel5.2 自作Facadeを作る

laravelを使っていれば自然と使うことになるFacade.

FacadeとはもともとデザインパターンのFacadeパターンが有名ですけど、「シンプルな使い方を提供する」って感じの意味で名付けてるんでしょうね。

APIみたいなイメージでしょうか。

 

そんなFacadeですがもちろん作れます。作ってみましょう。

テストなので文字列を出力する単純な機能だけです。

TestEchoHelper クラスという名前にします。Heplerっていう名前は特別必要ないです。TestEchoDesuyo クラスとかでも大丈夫です。

 

手順が少し複雑なので、先に説明しましょう。

  1. 実装したい機能を持つクラスを作る(TestEchoHelper クラス)
  2. TestEchoHelperクラスのインスタンス化方法を決める
  3. サービスプロバイダの機能を使って、手順2で指定した「インスタンス化の方法」をサービスコンテナの機能に登録する
  4. 3で指定したサービスプロバイダを実際に起動するときに実行するように登録する。起動時とはlaravelの起動時(リクエストがあった時など)である (config/app.php)
  5. 実際に使う窓口となるTestEchoFacadeクラスを作る
  6. TestEchoFacadeクラスを呼び出すときの別名を決める (config/app.php)
  7. 実際に呼び出して使う

長いですね。。。一つ一つやっていきましょう。

 

1  クラスの作成

クラスを作ります。場所はどこでもいいです。

どこでもいいって言われると逆に困るかもしれませんが、どこでも大丈夫です。好きな場所にしましょう。

今回はapp/Helpersっていうディレクトリを作って、そこに作ります。

WS000197

 

これで完了!

 

2 インスタンス化方法を決める

今回は普通にnew しましょう。つまり、new TestEchoHelper() です。まずは単純に。

 

3 インスタンス化方法を登録

少し複雑になってきます。Illuminate\Support\ServiceProviderクラスを継承したクラスを準備します。

今回用意するAppServiceProviderクラスはデフォルトで用意されてるサービスプロバイダーです。App\Providers\AppServiceProvider にありますね。

デフォルトでregisterメソッドとbootメソッド、2つのメソッドが用意されています。

registerメソッド→bootメソッドの順で自動で動きます。すごいですねー。

 

その前に、そもそも「サービスコンテナとは何か」?というと、

「クラスのインスタンス化方法を定めるもの」

と思って大丈夫です。laravelではバインドする、って言います。

今回の例で言えば、「TestEchoHepler クラスは new されてインスタンス化されますよー」って知らせてるわけですね。

 

で、サービスプロバイダっていうのはそのサービスコンテナに対してインスタンス化方法を登録する場所、みたいなイメージです。

ちなみにサービスコンテナの実体は \Illuminate\Contracts\Foundation\Application クラスのことです。

ServiceProviderクラスを継承すると、サービスプロバイダの中でサービスコンテナ(Applicationクラスのインスタンス)をプロパティ名 app として使えます。

下の画像でいうと$this->app ですね。

WS000198

 

このサービスコンテナのbindメソッドで、TestEchoHelperメソッドのインスタンス化方法を登録します。

1つ前の手順で決めたように、そのまま new  TestEchoHelper() としています。

 

第1引数のTestEchoってなんだ?と思ったかもしれません。

これはサービスコンテナから「俺はTestEchoを使う!」って呼び出したときに、TestEchoHelperクラスのインスタンス化方法が実行されるようにしています。

つまり、インスタンス化方法の名前を決めてるわけですね。

今回は「’TestEcho’っていうインスタンス化方法で、TestEchoHelperクラスはインスタンス化される」という登録をしました。

「そんな長く文字書くのイヤだ」って方は$this->app->bind(‘Te’, function()…)って書いても大丈夫です。

 

これで「TestEchoHeplerクラスを呼び出す方法をサービスコンテナに登録する」準備が整いました。

まだ準備なんですね。。。

 

4 「インスタンス化方法をサービスコンテナに登録するサービスプロバイダ」をlaravel起動時に呼び出すようにする。

呼び出そうとしているのはサービスプロバイダです。「サービスコンテナにインスタンス化方法を登録する処理を書くクラス」です。

落ち着いて順番に追っていきましょう。

 

登録する場所は config/app.php ファイルです。laravelを入れたなら一回は見たことある場所じゃないでしょうか?

ここのproviders配列内にクラス名を登録します。今回のAppServiceProviderはもう登録されていますね。

WS000199

 

ここに登録することで、laravel起動時にAppServiceProviderクラスのregisterメソッドとbootメソッド(あれば)が実行されます。

laravel起動時ってリクエスト時だけじゃないの?って思うかもしれませんが、シェルなどの実行時も動きます。

 

5 ファサードクラスを作る

やっとFacadeが出てきました。このFacadeクラスもどこにおいても大丈夫です。今回はapp/Facadesディレクトリを作ってそこに置きます。

このFacadeクラスでは何を書くかといえば、getFacadeAccesorメソッドで「サービスコンテナに登録されたインスタンス化方法の名前を返す」

ことだけです。もっと書いてもいいんでしょうけど。

 

今回は手順3(覚えてますか?)で、サービスコンテナによるTestEchoHelperクラスのインスタンス化方法を「TestEcho」っていう名前にしたので、

TestEchoを返します。これだけ!

WS000201

 

6 別名を決める

やっとここまで来ました。これもconfig/app.phpです。

見覚えがあるんじゃないでしょうか?

 

実際に呼び出すことになるのはTestEchoFacadeクラスです。なんといっても窓口ですから。

でもTestEchoFacade::って少し長いですね?なのでTestEcho::で呼び出せるようにします。

WS000200

 

手順3で「インスタンス化の方法」にもTestEchoと名付けてここでも同じ名前ですが、同じ名前にする必要性はありません。

ただ、同じにした方がわかりやすい気もします。

 

7 実際に使う

やっと使えます!

まずはコントローラーから。コントローラは名前空間App\Http\Controllers 配下にあるので、絶対パスで指定しましょう。

WS000202

WS000203

 

やりました。↑は画像ですが白文字背景+黒文字だけなのでわかりにくくてすみません。

viewでも出してみましょう。

WS000204WS000207

WS000206

 

そうですね。

 

こんな感じでカスタムヘルパーみたいなのを作れます。

参考になれば。

laravel 5.1~ DB::rawでもDB::connectionが必要

みなさんlaravel使ってますか?

query builderがかなり高機能で面白いですね。

 

ただかなり単純なことで時間を食ったので同じことでハマった人がいた時のために。

結論はタイトル通りですが。

 

問題はSELECTで関数を使った時です。こんな感じ。

WS000195

 

これで実行しようとすると「Database mysql1 is not configured.」みたいなエラーが。。。

何を言ってるんだね?と思ったわけです。

仕方ないのでconfig/database.phpを確認。

WS000194

 

そもそもなんで使ってるDB設定が「mysql1」なの?$connectionに書いてるよね?

って思ってたら、DBファサードはデフォルトのDB接続設定使うんだと思い出したわけです。

そしたら、このdatabase.phpのdefaultに「mysql1」ってありますね。

 

「rawメソッドでもconnection設定しなきゃダメなんだ」と気づきました。

WS000196

 

これでOKです。

rawだと文字列操作関数みたいなイメージだから失念してました。

laravel 5.1~ phpstormでクエリービルダーのauto completeを効かせる

phpstorm使ってますか?

phpをメインに開発してるなら今すぐ導入しましょう。

有料で年10000円くらいですが、使いやすさを考えると安すぎるくらいです。

 

そんなphpstormでlaravelのプロジェクトを作るときにお手軽にコード補完をする方法。

お手軽なのでモデル(Eloquent)周りの、さらにquery builder周りだけです。

「composerとかide-helperとかこわい」って人向けです。

 

まずはTestという名前のEloquentを作ります。使用テーブルはtest_table.

そしてTestControllerから「SELECT column1 FROM test_table;」を実行するというコードを書く。

 

WS000174

 

 

 

 

WS000173

 

「そんなメソッドないよ」って言われてます。マジックメソッドですからね。

__callメソッドで指定しているものまでは補完してくれません。でも補完してほしいですね。

 

そんなときはModelクラスに直接PHPDocを追加です。

WS000177

WS000176

 

ちゃんと補完されるようになりました。

「Modelクラスには、\Illuminate\Database\Query\Builderクラスを戻り値として返すselectっていうstaticメソッドありますよ」

って教えてあげたからですね。

参考までにこのクラスを作り出してるのは下のModelクラス内の__callマジックメソッドです。

(正確には、今回はTestクラスのstaticメソッド呼び出し(Test::select)してるので

  1. __callStaticが呼ばれて、自分自身のインスタンスのselectメソッドを呼ぶ
  2. __callが呼ばれて、newQueryメソッドで\Illuminate\Database\Query\Builderインスタンスが生成され、\Illuminate\Database\Query\Builderクラスのselectメソッドが呼ばれる

)

WS000178

 

これで怖いものなしですね。

もしかしてjoinの中でも補完したいとお思いかもしれません。

WS000179

 

そんな時も大丈夫。この$joinは何かといえば、\Illuminate\Database\Query\JoinClause クラスです。

WS000181

 

なので、関数内に…

WS000182

 

やりました。

なんだか長いんだよなーって気になる方にはこちら。

use Illuminate\Database\Query\JoinClause;をコントローラに追加して…

WS000183

スッキリしました。

これまた参考ですが、\Illuminate\Database\Query\JoinClosure クラスを返しているのはこちら。

 

WS000180

 

\Illuminate\Database\Query\Builderクラスのjoinメソッド内で、最初のif文の中でnewされてますね。

これがcall_user_func($one, $join);の2つめの引数として渡され、function($join)の引数になるわけです。

 

これでお手軽にコード補完ができました。

悪いプログラムを書かないようにするには?

「良いプログラムとは?」と聞かれると結構千差万別ですが、悪いプログラムはえてして共通してます。

で、その悪いプログラムを書かないようにするには、「適切な名前を付ける」ことが簡単でおススメです。

深く考えていくと難しいのですが、コード上ですぐできるという意味では非常にお手軽で効果も高い。

お買い得なのです。

 

悪い例

WS000166

checkとはいったいなんなのか?

checkとは値を変えることも含む言葉なのか?

そもそも$dataとは何を投げればいいのか?

困りものです。

 

「この関数・クラスはそもそも何をやっているのか?」を明示しましょう。

 

改善1

WS000169

and多すぎ。

名前を明示すると「ちょっとこの関数でやりすぎじゃないか?」と感じますね。

現状を隠さずはっきりと見つめることで問題が浮かび上がってきます。掃除やダイエットと同じです。

 

さらに改善

WS000170

 

何が必要なのかわかってきましたね。実はclient_id以外はいらなかったのです。

さらに名前もコードも呼び出すときに必要あれば追加すれば良かったのです。チェックする人が勝手に直したら隠蔽です。

 

めでたしめでたし。

これで後からプロジェクトに参加する人も楽しく働けましたとさ。

Cakephp 2.x でページネートの結果をキャッシュ

Webアプリケーションのパフォーマンス改善の筆頭といえばキャッシュです。

データベースへなどへのアクセスが必要なデータをメモリやファイルに持っておけば負荷も減りますし、頼もしい限り。

 

Cakephp (2.x)では、基本的に

キャッシュにデータ書き込み
$result = array(‘result’ => 100);
Cache::write(‘result’, $result, ‘paginate_cache’);

 

キャッシュデータ呼び出し
$result = Cache::read(‘result’, ‘paginate_cache’);

//result = array(‘result’ => 100)

 

で大丈夫なんですが、ページネータの場合はさらに2つの操作が必要になります。

リクエストオブジェクトのparamsパラメータの’paging’キーのキャッシュと、コントローラ内でのPaginatorHelperの使用宣言です。

 

pagingキーに入っている値っていうのはカウント数とか、ページ数を持っている配列ですね。

↓こういう値が入ってます。

WS000076

んで、キャッシュするときにこの値もちゃんとCache::writeでとっておかないと、「結果0件なのに表示されてる。。。けど1ページ目しかない」みたいな表示にバグります。

なので、

$paging = $this->request->params[‘paging’];

Cache::write(‘paging’, $paging, ‘paginate_cache’);

 

$paging = Cache::read(‘paging’, ‘paginate_cache’);

$this->request->params[‘paging’] = $paging;

 

のように、キャッシュを呼び出す時にもう一回設定しなおさないとダメなんですね。

2つめの「PaginatorHelperの使用宣言」ってのはこういうのです。

WS000074

これを設定しておかないと、クエリパラメータとかちゃんととってくれません。 Cakephpのコードを見てみると、確かにPaginatorComponentのpaginateメソッドの最後で設定してます。

WS000077

キャッシュを使うときはこのpaginateメソッドを通らないので、自分で設定しておく必要があるってことですね。 これで Order by とかが重くなりがちなpaginateも余裕のクリアです。

でも売り切れ状態とかリアルタイムでほしいステータスもキャッシュしてしまうので気をつけてください。