Laravel – Collection – コールバックによる操作

概要

Collectionには引数にコールバックをとるメソッドが準備されている。これらはコレクションの各要素にコールバックで定義された処理をし、その結果に応じて要素の絞り込み、各要素への処理の適用、各要素を用いた縮退処理を行う。

いずれも元のコレクションは変更されず、戻り値として新たなコレクションやオブジェクト、変数などが返される。

単純配列型に対するメソッド

filter()~条件に合う要素の絞り込み

filter()は、設定した条件により元のコレクションの要素の一部を取り出す。

  • filter()のコールバックは、各要素に対応する引数を1つ撮る
  • コールバックの戻り値は、受け取った要素に対する条件の評価結果(true/false)
  • filter()の戻り値として、元のコレクションのうち条件に合致する要素だけが残ったコレクションが返される

以下の例は、奇数要素のみを取り出す例。

基数要素だけが残ったコレクションが得られる。

以下は4未満の要素のみを残す例。

4未満の要素のみが残っている。

reject()~条件に合わない要素の絞り込み

reject()filter()の逆で、条件に合う要素を取り除いた残りのコレクションを返す。

以下は奇数要素のみを取り除く例。

奇数要素が除かれ、偶数要素のみが残っている。

map()~各要素への処理のマッピング

map()は設定した処理を元のコレクションの全要素に適用する。

  • map()のコールバックは、各要素に対応する引数を1つ撮る
  • コールバックの戻り値は、処理の結果更新される各要素の値
  • map()の戻り値として、元のコレクションの各要素にコールバックの処理が適用されたコレクションが返される

以下はコレクションの各要素を10倍する例。

元の要素が10倍された結果になっている。

reduce()~縮退処理

ここでいう縮退とは、各要素やその加工結果を1つの結果にまとめ込んでいくものとする。

例として「コレクションの各要素の値の合計を求める」という処理の場合、「合計値」という結果に要素ごとの値を「足し込む」という処理になる。

このためreuce()filter()map()と以下の2点で異なる。

  • コールバックの引数は縮退の途中結果とその時点での要素の2つの引数をとる
  • reduce()自身が上記のコールバックと縮退処理の初期値の2つの引数をとる

コールバックについては以下のとおり。

  • reduce()のコールバックは、各時点までに蓄積された処理結果(たとえば$carry)と、その時点で処理される要素(たとえば$element)の2つの引数をとる
  • コールバックの戻り値は、受け取った$carryに対して$elementに関して処理された新たな$carryの内容
  • 最終的なreduce()の戻り値として、$carryの縮退結果が返される

以下はコレクションの各要素の和を求める例で、縮退の初期値を0としてreduce()の第2引数で与え、第1引数のコールバックでは$carryに各要素の値を足し込んでいる。

結果は1~5までの和になる。

以下は初期値を0→900に変更した例。

初期値0ではなく900に対する足し込みとなっている。

また次の例では、要素の値を文字列として後ろにつなげていく。初期値には空文字列''を指定している。

結果は以下のように、値がつなげられた文字列になる。

連想配列への適用

以下の連想配列を使う。

filter()

連想配列のコレクションにフィルターを適用する場合。

ただしこれはwhere()による操作で代替できる。

map()

連想配列のコレクションにmap()を適用した例。genderキー列の値に応じて内容を書き換えている('male''m''female''f')。

また以下の例では、groupBy()でグルーピングされたコレクションに対してmap()を適用し、グループごとの要素数をカウントしている(グルーピングと集計参照)。

reduce()

以下は連想配列のコレクションに対してreduce()を適用して、要素のheightキー列の値の二乗和を求め、その結果から分散を計算している。

なおコールバックにuseを使い、処理中の要素がコレクションの末尾要素のときに、集計結果を使った計算をさせ、reduce()の結果が直接分散となるようにもできる。

 

Laravel – Collection – 基本操作

概要

コレクションは配列と同じように要素の集合体だが、細やかな操作・集計などのメソッドを備えている。

コレクションの作成~collect()ヘルパー

collect()ヘルパーの引数に配列を入れて、Collectionのインスタンスを得られる。

与えるのは連想配列でもよい。

値の参照

ブラケット([])による参照

以下のような連想配列を準備する。

配列のインデックスや連想配列のキーを指定するのと同じようにして、ブラケット([])で要素を参照できる。

get()メソッドによる参照

ブラケットの代わりにget()メソッドを用いても要素を参照できる。

random()~ランダムに1つ取り出す

random()メソッドを呼び出すたびに、コレクションの要素がランダムに1つ返される。

first()/last()

first()メソッド/last()メソッドは、コレクションの先頭要素/末尾要素を返す。

値のセット

ブラケット([])によるセット

配列や連想配列と同じように、インデックスやキーを指定して値をセットできる。

put()メソッドによるセット

put(index/key, value)でも指定したインデックス/キーの要素の値をセットできる。

空のコレクションと要素の追加

空のコレクションの生成

collect()メソッドの引数を省略すると、空のCollectionインスタンスが生成される。

ブラケット([])による追加

存在しないインデックスやキーを指定して値をセットすると、新たに要素が追加される。

ブラケットでキーを指定しないと、末尾にデータが追加される。

put()メソッドによる追加

put(index/key, value)でも要素を追加できる。

値の引き抜き

pull()メソッドは、指定したインデックス/キーの要素をコレクションから抜き取る。その要素はコレクションから削除され、戻り値として返される。

以下の例では、pull()メソッドで2つの要素を抜き取っている。

スタック

Collectionにはpush()メソッドとpop()メソッドが準備されていて、コレクションをスタックのように扱える。以下のデータで確認する。

push()メソッド

push()メソッドは、引数で指定した値をコレクションの末尾に追加する。

2つ以上の引数を指定してもエラーにはならないが、1つ目の引数の値のみが使われてプッシュされる。

pop()メソッド

pop()メソッドは、コレクションの末尾から要素を抜き取り、戻り値として返す。

空かどうかの確認

isEmpty()/isNotEmpty()は、コレクションが空か/空でないかを確認して結果をtrue/falseで返す。

nullの場合はCollectionのインスタンスではないので、これらのメソッドの結果はエラーになる。

コレクションから配列への変換

toArray()メソッドは、コレクションの内容を配列として返す。toArray()のほかall()メソッドでも同じ結果が得られる。

 

Laravel – Collection – 集計・加工

集計

コレクションの要素の個数、合計値、平均などを求めるメソッドが準備されている。これらのメソッドは、コレクションの要素に基づいた処理結果を1つの数値として返す。

クエリービルダーのメソッドでも書いたが、データベースに対するクエリーに関するメソッドというよりも、Collectionの要素を集計するメソッドととらえた方がよい。

以下の2つのコレクション$c$pで確認する。

count()~要素数

コレクションの要素の数を返す。引数を指定しても無視される。

max()/min()~最大値・最小値

コレクションの要素の最大値/最小値を返す。

連想配列を要素とする配列の場合は、キーを指定してその値の最小値/最大値を取得できる。値が文字列を含む場合、辞書順での最小値/最大値となる。

連想配列で引数を指定しないと、最初のキー列の値で判定され、その値を持つ要素が連想配列として返される。

sum()/average()/avg()~合計値・平均値

コレクションの要素の合計値/平均値を返す。

連想配列の場合は、集計するキー列を指定する。引数を指定しないとエラー、文字列を値に含むキーを指定すると警告が出て結果は0になる。

並べ替え

コレクションの要素を並べ替え、結果のコレクションを返す。元のコレクションは影響を受けない。

reverse()~逆順に並べる

reverse()メソッドは、元のコレクションの要素を逆順にしたコレクションを返す。引数を指定しても無視される。

shuffle()~シャッフル

shuffle()メソッドは、元のコレクションの要素の値をシャッフルしたコレクションを返す。

sort()~昇順ソート

sort()メソッドは元のコレクションの値の昇順で要素を並べ替える

連想配列型でも使えるが、引数を指定するとエラーになる。引数なしで呼ぶと、1つ目のキー列の内容でソートされる。

sortBy()~引数キーの昇順

sortBy()メソッドは引数にキーを指定して、そのキーの値の昇順で要素を並べ替えたコレクションを返す。

以下の例ではnameを指定して名前の昇順で並べ替えている。文字列は辞書順でソートされる。

以下の例ではheightを指定して並べ替えている。身長の数値が昇順となるように並んでいる。

sortByDesc()~引数キーの降順

sortByDesc()メソッドは引数にキーを指定して、そのキーの値の降順で要素を並べ替えたコレクションを返す。

以下の例では、身長の高い順に並べ替えている。

コレクションの変形・取出し

pluck()~キー列の取出し

pluck()メソッドは引数にキーを指定し、そのキーの値を単純配列のコレクションとして取り出す。pluckは英語で「摘む」という意味。

以下はgenderキーの値を取り出している例。

flatten()~コレクションの展開

flatten()メソッドは、多次元配列型のコレクションの要素を再帰的にたどって1次元の配列型に並べる。

連想配列はキーが無視され、値が使われる。

多次元配列の場合、再帰的に要素をたどって1次元化する。

SQL的処理

クエリービルダーメソッドと同等のwhere()groupBy()がある。ただしこれらのメソッドは、クエリービルダーの様にはクエリーを生成はしない。

where()~条件による抽出

where()メソッドはクエリービルダーメソッドのwhere()と同じ。

groupBy()~キーによるグルーピング

groupBy()メソッドは、指定したキー列の値で要素をグルーピングする。

  • 元の配列の要素として、指定したキー列の値をキーとした配列ができる
  • その配列の要素として、各キーに対応した値を持つ元の要素が格納される

以下の例ではgenderキーによってグルーピングしている。genderキーの値malefemaleをキーとする配列が作られ、キーがmaleの配列にはgendermaleの要素、キーがfemaleの配列にはgenderfemaleの要素が格納されている。

 

Laravel – Collection

クエリービルダーget()メソッドの結果、Eloquent\Collectionのインスタンスが得られるが、これはSupport\Collectionを継承している。

ここではSupport\Collectionの操作方法を整理する。

基本操作

  • コレクションの作成や要素の参照・追加などの基本操作
  • スタックのような操作、空かどうかの判定、配列への変換操作も含む

集計・加工

  • 要素数のカウントや最小値・最大値の取得、合計・平均の計算
  • 各種並べ替え
  • キーの抜き出し、一次元展開
  • where、groupBy
  • グループ化された結果の集計については一工夫必要

コールバックによる操作

  • filterによる要素の条件抽出
  • mapによる各要素に対する処理定義
  • reduceによるコレクション→スカラー値への縮退処理
  • useによる元のコレクションの参照

 

Laravel – ローカルスコープクエリー

概要

ローカルクエリースコープを使うと、特定モデルに必要なクエリービルダーに名前を付けて、1つのメソッドとして呼び出すことができる。

準備

クエリービルダーで使ったOrderCustomerのモデルとテーブルを使う。

それぞれのモデルでリレーションを定義している。

手順

ローカルスコープのクエリービルダーは、次の手順で定義する。

  • 対象のモデルでメソッド定義
    • 'scope'に続いてキャメルケースでメソッド名を定義
    • 第1引数にクエリー($queryなど)
    • ビルダーに引数を渡したいときは第2引数以降に加える
    • メソッド内の戻り値は、第1引数のクエリーに対してビルダーを組んだ結果を渡す
  • 定義したメソッドをビルダーの様に呼び出す
    • 呼び出す際は'scope'を除いたメソッド名で呼び出す

例1:引数なし

たとえばordersの最新3データのみ取り出す定形操作があるとする。このとき、Orderモデルで以下の様にメソッドを定義する。

メソッド名は頭に'scope'を付けてscopeLatest3()として、クエリーを受け取る引数に$queryを設定。

tinkerでこれを利用してみる。呼び出す際は’scope’を付けずにlatest3()とし、$queryに対応する引数は渡す必要はない。

生成されるSQLは以下のとおり。

例2:引数を渡す場合

モデルでのメソッド定義の第2引数以下に、渡したい引数を列挙する。以下の例ではcustomer_idを渡し、これに合致するordersのデータを抽出している。

実行時に渡したい引数は、第2引数custome_idで定義している。

tinkerで引数にcustoer_id=1を渡して実行してみる。

生成されるSQLとバインドされる値は以下のとおり。

例3:子モデルが絡む場合

ビルダーに引数を渡して、子モデルの条件で抽出したい場合。whereHas()を使うが、引数に渡すクロージャ―の変数はローカルスコープのため、useで引数の変数を受け取って渡す。

tinkerでcustomer_name='customer2'を渡して確認。

生成されるSQLとバインドされる値は以下のとおり。

 

Laravel – クエリービルダーのメソッド

 

準備

クエリービルダーで使った以下の3つのテーブルで各メソッドを確認する。

これをordersに対してcustomersemailsを左結合で関連付けて並べると以下のとおり。

抽出

select

SQLのSELECT句に対応し、データとして取り出す属性(=カラム)を指定。モデルに対応したテーブル名のFROM句も生成される。

モデルクラス::select('属性1', '属性2', ...)

"select `属性1`, `属性2`, ... from `テーブル名`"

以下はOrderモデルのidordered_at属性を取り出す例。

全属性を取り出す場合は'*'を指定する。これはall()と同じ効果。

where

モデルの属性に対して条件を課す。オペレーターは=<などの算術演算子やINBETWEENなど。

モデルクラス::where('属性名', 'オペレーター', 値)

"select * from `テーブル名` where `属性名` オペレーター ?"

  • 生成されたSQLには値は直接埋め込まれず、プレースホルダー?が置かれる
  • 値は別途バインドされる

以下はOrderモデルのデータうち、item属性が'rubber sheet'のものを抽出するためのSQLとバインディングされる値を確認したもの。

WHERE … AND …

複数条件のANDは、それぞれの条件のwhere()メソッドをチェーンにする。

where(条件1)->where(条件2)->where...

where 条件1 and 条件2 and ..."

以下はOrderモデルのデータのうち、item属性が'rubber sheet'で、かつid=2のデータを抽出するためのビルダー、SQLとバインド値。

WHERE … OR …

複数条件のORは、orWhere()メソッドをチェーンにする。一番目はwhere()でもorWhere()でも結果は同じ

where(条件1)->orWhere(条件2)->orWhere...

where 条件1 or 条件2 or ..."

以下はOrderモデルのデータのうち、item'screw''plastic plate'、またはid=3のデータを抽出するためのビルダーとSQL、バインド値。

orWhere()

上記WHERE ... OR ...を参照。

where()とorWhere()の優先順位

3つ以上の条件を組み合わせる場合のANDとORの優先順位についてこちらでまとめている

並べ替え

orderBy()

第1引数で並べ替えキーのカラムを、第2引数で'asc''desc'を指定。

orderBy('カラム名', 'asc'または'desc')

oldest()

引数で指定したカラムの昇順に並べ替える。引数を省略した場合はcreated_atフィールドの昇順、すなわち古い順に並べ替える。

latest()

引数で指定したカラムの降順に並べ替える。引数を省略した場合はcreated_atフィールドの降順、すなわち新しい順に並べ替える。

inRandomOrder()

データの順番をランダムに並べ替える。引数に整数のseedを与えることができる。

集約

groupBy()

クエリービルダーのcount()メソッドは整数値を返すため、Model::count(...)->groupBy(...)のような使い方ができない。

そこで、select()メソッドの引数にDB:raw()メソッドを使ってSQLのCOUNT句を書く。

以下の例では、Orderitemでグループ化し、item名とそのかずをコレクションで取得してビューに渡している。

ビュー側でこれを表示する。

表示結果は以下のとおり。

  • screw: 1
  • rubber sheet: 2
  • plastic plate: 1
  • wire: 1

 

なお、クエリービルダーから生成されるSQLは以下のとおり。

having()

上の結果をhaving()で絞り込む例。使い方はwhere()と同じ。

表示結果は以下のとおりで、カウント数が1の結果のみ抽出されている。

  • screw: 1
  • plastic plate: 1
  • wire: 1

 

数の限定~データの一部

limit()

先頭からの個数を引数に指定してデータを限定。

offset()

limit()と組み合わせて使い、先頭から何個目のデータから取り出すかを指定。offset(3)->limit(2)の場合、3個目と4個目のデータが得られる。

結合

join()

キーを指定して、親モデルに子モデルを結合する。

join('子モデル', ’キー1', 'オペレーター', 'キー2')

以下はOrderCustomerEmailを関連付ける例。

この結果、子モデルのCustomerEmailの属性も直接Orderからアクセスできるようになる。

joinINNER JOINのため、emailが設定されていない(nullの)customer3は含まれない。

  • 1: 2020-06-05 00:00:00
    • customer name: customer1
    • email address: customer1@mail.com
    • ordered item : screw
  • 2: 2020-06-05 00:00:00
    • customer name: customer2
    • email address: customer2@mail.com
    • ordered item : rubber sheet
  • 2: 2020-06-06 00:00:00
    • customer name: customer2
    • email address: customer2@mail.com
    • ordered item : plastic plate
  • 1: 2020-06-07 00:00:00
    • customer name: customer1
    • email address: customer1@mail.com
    • ordered item : rubber sheet

 

leftJoin()

使い方はjoin()と同じで、LEFT JOIN句を生成する。上記のクエリービルダーでjoin()leftJoin()に置き換えると、emailが登録されていないcustomer3も含まれる。

値の取出し

get()

クエリービルダーの実行結果を返す。戻り値はモデルインスタンスのコレクション。

find()

find(n)
主キーの値を指定してデータを取り出す。戻り値はモデルインスタンス。
find([m, n, …])
複数の主キーの値を指定してデータを取り出す。戻り値はモデルインスタンスのコレクション。

first()

クエリービルダーの実行結果の先頭データを返す。戻り値はモデルインスタンス。

pluck()

クエリービルダーの実行結果から、引数で指定したカラムのみ抜き出して返す。戻り値はカラムの内容の配列。

集計関数

全般論

集計関数の結果は数値

クエリービルダーにはSQLと同様の集計関数が準備されている。

ただしこれらは、ビルダーの結果に対して属性(カラム)を指定して集計した結果を数値で返すのみで、そのままselect()の引数として与えることはできない(SQLのようにselect()中で集計関数を使うときは、DB::raw()メソッドで生のSQLを書く必要がある)。

たとえば以下の例では全データの個数が整数で得られる。

where()で絞り込まれた結果のデータ数も得られる。以下はOrderのデータのうちcustomer_id=2のデータの個数を得る。

groupBy()による集約結果の集計はひと手間必要

groupBy()で絞り込んだ結果については、直接集計関数に渡しても適切な結果とならない(たとえばcount()で集計すると常に結果が1になる)。ビルダーの内容を見ると、where()の場合とgroupBy()の場合で構成が異なっている。

where()の結果のビルダーオブジェクト

groupBy()の結果のビルダーオブジェクト

グループ化した結果をカウントしたい場合は、where()の引数にクロージャ―を入れて、その中にgropupBy()を含むクエリービルダーを書くとよい。

DB::rawの方が明快

JOINで結合したテーブルの集計などが必要な場合、select()メソッド中にDB::ra()でSQLのcount()などを書く方法もある。

この場合はストレートにwhere()join()groupBy()などを連ねていけばいいので明快。たとえば以下のように書ける。

なおコントローラー内でDB::raw()を使う場合は、冒頭で以下のuse宣言が必要。

count()

引数で指定した属性でデータ数をカウントして返す。

sum()

引数で指定した属性の値の合計値を返す。

avg()

引数で指定した属性の値の合計を個数で割った平均値を返す。

min()

引数で指定した属性の値の最小値を返す。

max()

引数で指定した値の最大値を返す。

 

Laravel – ANDとORの優先順位

概要

where()orWhere()組み合わせることで、WHERE句のANDORを表現できる。ここではこれらのメソッドの書き方と生成されるSQL、条件の組み合わせの優先順位を整理する。

以下のようなメソッドの書き方が可能になる。

  • 条件1 OR 条件2 AND 条件3ANDが優先される書き方
  • (条件1 OR 条件2) AND 条件3で括弧内のORが優先される書き方

where()orWhere()については、クエリービルダーのメソッドを参照。

準備

クエリービルダーで使った以下の3つのテーブルで各メソッドを確認する。

これをordersに対してcustomersemailsを左結合で関連付けて並べると以下のとおり。

チェーンの展開とANDの優先

where()orWhere()をチェーンで連ねた場合、それぞれの条件が単純にANDあるいはORで連ねられる。その結果ANDが処理された後ORが処理される。

  • ...->where(条件)'... AND 条件'
  • ...->orWhere(条件)'... OR 条件'

以下の例では、where()->orWhere()->where()とメソッドチェーンを組んでいる。

上記のクエリービルダーで生成されるSQLは以下のとおり。

この結果AND演算が優先され、「item='wire'または、item='rubber sheet'かつid=1」という条件で2つのデータが抽出される。

  • 4: 2020-06-07 00:00:00
    • customer name: customer1
    • email address: customer1@mail.com
    • ordered item : rubber sheet
  • 5: 2020-06-08 00:00:00
    • customer name: customer3
    • email address:
    • ordered item : wire

 

クロージャによる()の表現

ANDに対してORの条件を優先させたい場合、SQLでは括弧で括る。クエリービルダーでは、括弧で括りたい処理をクロージャ―でまとめる。

where(function($query) { 括弧でまとめたい処理群; })

以下の例では、上の例と順番は同じだが、最初のwhere()orWhere()をクロージャ―でまとめている。

この結果、まず最初の2項のOR演算が処理され、その結果と残りの条件のAND演算で抽出処理される。生成されるSQLは以下のとおりでOR演算が括弧で括られている。

実行結果は以下のとおりで、「item='wire'またはitem='rubber sheet'」で3つのデータが抽出され、その結果とid=1ANDで結果が一つに絞り込まれる。

  • 4: 2020-06-07 00:00:00
    • customer name: customer1
    • email address: customer1@mail.com
    • ordered item : rubber sheet

 

 

Laravel – リレーション/クエリー

 

リレーション

  • 複数テーブルで構成されるデータを、モデルベースで扱う方法
  • モデルでhasOne()hasMany()belongsTo()メソッドによってリレーションを定義
  • 親モデルに属する子モデルを、親モデルの属性のように扱えるようになる

クエリービルダー
クエリービルダーのメソッド

  • SQLによりフレームワークではなくRDBMS側でテーブルの連結や絞り込みなどの操作を行う
  • クエリービルダーメソッドを組み合わせてSQLを生成する

ANDとORの優先順位

  • WHERE条件でANDORを組み合わせる場合の書き方
  • where()orWhere()の優先順位とクロージャ―による括弧の実装

クエリービルダーとリレーション

  • クエリービルダーの効率性と、リレーションによる直感的な操作を組み合わせる手順

子モデルの属性による絞り込み

  • 子モデルの属性の条件による絞り込みを、クエリービルダーで行う方法
  • whereHas()メソッドを使う

ローカルスコープクエリー

  • 特定モデルで利用するクエリービルダーを、モデルのメソッドとして定義する

 

Laravel – 子テーブルの条件で絞り込み

概要

複数テーブルが関連付けられているデータを子テーブルの条件で絞り込みたい場合。クエリービルダーJOIN句によってテーブルを結合すると結果は配列となるが、配列ではなくオブジェクトのままリレーションで操作したいとき。

whereHas()メソッドを使うと、親のモデルでhasMany()で定義された子モデルの操作が可能になる。

whereHas()の使い方は以下のとおり。

  • 第1引数で子モデル名を指定
  • 第2引数にクエリーを引数としたコールバックを定義
    • コールバックで引数のクエリーに対してクエリービルダーメソッドを適用
  • 最後のget()を忘れがちなので注意

準備

クエリービルダーの確認で使ったテーブルで確認する。

1つ先のリレーション

親が直接持つ子モデルの場合、以下の様に記述する。ここでは子モデルのcustomerの名前が'customer2'のデータを抽出している。

抽出結果は親モデルのコレクションとしてビューに渡している。

モデルのコレクションを受け取ったビューでは、親モデルとしてこれらを扱い、必要に応じてリレーションによって子モデルの属性を取り出す。

emailを持たないcustomerに対応するため、optional()メソッドを使っている

結果は以下のとおりで、customer.id=2に相当するcustomer2のデータが抽出されている。

  1. 2020-06-05 00:00:00: customer2
    • e-mail: customer2@mail.com
    • item: rubber sheet
  2. 2020-06-06 00:00:00: customer2
    • e-mail: customer2@mail.com
    • item: plastic plate

 

whereHas()で生成されるSQLを確認しておく。where句の対象としてサブクエリーが構成されている(見やすいように結果を改行している)。

バインドされる値も確認。

2つ先のリレーション

子モデルの子モデルの属性で絞り込む場合は、whereHas()のコールバック内でwhereHas()を入れ子で呼び出す。呼び出し部分だけを取り出すと以下のとおり。

コールバックの引数は、コールバック内ローカルスコープなので同じ名前でも構わない。

結果は以下のとおりで、メールアドレスが'customer2'で始まるcustomer2のみ取り出されている。

  1. 2020-06-05 00:00:00: customer2
    • e-mail: customer2@mail.com
    • item: rubber sheet
  2. 2020-06-06 00:00:00: customer2
    • e-mail: customer2@mail.com
    • item: plastic plate

 

生成されるSQLを確認。サブクエリーが入れ子で構成されている。

バインドされる値も確認。

 

Laravel – クエリービルダーとリレーション

概要

Laravelでデータベースの大量のデータを扱う場合、クエリービルダーでSQLを発行してDBMS側でデータを絞り込んだ方がフレームワークの負担が少なくなる。

一方でリレーションに基づいた記述は、可読性やメンテナンスの面で有利な面を持つ。

そこで、クエリービルダーとリレーションの組み合わせについて試してみた。

  • モデルクラスのall()メソッドとwhere()のチェーンによる絞り込みは、DBMSの機能を使っておらず非効率ではないか
  • モデルクラスで記述するクエリービルダーは、DBMS側でデータを絞り込んだ結果をフレームワークで扱うので効率的なようだ
  • DBファサードで記述するクエリービルダーは、得られるデータがモデルのインスタンスではなく連想配列になるため、モデルで定義したリレーションが使えない

準備

クエリービルダーで使ったものと同じデータを準備する。

all()とwhere()の組み合わせ

以下の様なコントローラーのアクションでデータを絞り込んでビューに渡す。

ビュー側では$ordersを受け取って、リレーションに基づいて表示する。

ブラウザーでの表示は以下のとおり。

  1. 2020-06-05 00:00:00: customer2
    • e-mail: customer2@mail.com
    • item: rubber sheet
  2. 2020-06-06 00:00:00: customer2
    • e-mail: customer2@mail.com
    • item: plastic plate

 

ただしこの場合の動作は、DBMSから全データをall()で取得した後にcustomer_idで絞り込んでいる。以下の様にtinkerで確認してみると、where句を含むSQLは発行されていないようだ。

実際、get()なしで直接モデルのインスタンスが得られている。tinkerで確認してみると、$ordersOrderクラスのインスタンスを要素とするコレクションとなっている。

モデルのメソッドによるクエリービルダー

コントローラーのアクションで、以下の様にOrderモデルからクエリービルダーを構成する。

ビューは上と同じ内容で、結果も同じように表示される。

tinkerで確認するとSQLが構成されて値もバインドされているので、DBMS側でデータが絞り込まれているようだ。

get()の結果得られるデータは上記のall()where()と同じで、Orderクラスのインスタンスを要素とするコレクション。

DBファサードによるクエリービルダー

クエリビルダ―をモデルクラスからではなくDBファサードから記述した場合。

同じビューを使った場合、以下のようなエラーがブラウザーに表示される。

tinkerで確認すると、SQLは同じように発行されバインディングも同じ。

ただし、get()で得られるデータが上と異なる。全体は配列だが、要素がOrderクラスのインスタンスではなく、Orderの属性をキーとする連想配列となっている。

このため、$ordersの要素からリレーションを定義したはずのcustomerを呼び出そうとしても、「定義されていない属性」としてエラーが発生する。

そこでビュー側でリレーションを用いず各要素を連想配列として取り出してみる。

こうすると以下の様に$ordersの抽出結果が表示される。

  1. id: 2
  2. customer_id: 2
  3. ordered_at: 2020-06-05 00:00:00
  4. item: rubber sheet
  1. id: 3
  2. customer_id: 2
  3. ordered_at: 2020-06-06 00:00:00
  4. item: plastic plate

 

ただし各要素のorderに関係づけられたcustomerはこのままでは得られないため、クエリーレベルで2次元配列として関連する全モデルの属性をカラムとして取り出す必要がありそうだ。