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次元配列として関連する全モデルの属性をカラムとして取り出す必要がありそうだ。

 

Laravel – クエリービルダー

準備

以下の3つのテーブルを使ってクエリービルダーの動作を確認する。

3つのテーブルを結合させるSQLの例。

SQLの実行結果。

基本操作

tinkerで確認する。

ビルダーの書き方

DBファサードのtableメソッド

DBファサードのtable()メソッドを使う場合。

モデルのビルダーメソッド

モデルのビルダーメソッドを直接実行する場合。

ビルダーのインスタンスを保存して使う

DBファサードのtable()メソッドの場合、tinkerだと以下のように詳細な内容が表示される。toSql()メソッドで構築されるSQLが確認できる。

モデルからビルダーメソッドを直接呼び出す場合、tinkerでのインスタンスは表示はシンプルになる。

SQLや実行結果などの取得・確認方法

ビルダーのインスタンス取得

クエリービルダーを実行すると、ビルダーのインスタンスが得られる。

get()~実行結果の取得

クエリービルダーの実行結果はget()メソッドで得る。

toSql()~SQLの確認

toSql()メソッドでビルダーで生成されるSQLを確認できる。

getBindings()~バインドされる値の確認

バインドされる値はgetBindings()メソッドで確認できる。

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

複数のコマンドを含むSQLの場合は、コマンドに対応するクエリービルダーメソッドをチェーンで繋げる。

メソッドチェーンの順番は問わない。以下のようにメソッドの順番を変えても、生成されるSQLは同じ。

コントローラーでの記述

基本の書き方

以下のルーティングで基本の書き方を確認。

コントローラーでの書き方。DBファサードを使う例。

  • customersテーブルのnameカラムの値を取得している
  • 結果は配列customersで各要素は2つの要素idnameを持つ連想配列となる
  • $customersをビューに渡している

ビューでは受け取った$customersの各要素を取り出し、idとnameを表示。

なおdd()で表示させた$customersの内容は以下のとおり。

メソッドチェーン

SQLの行が増えてきた場合、メソッドチェーンでビルダーメソッドを繋げる。

まず以下のようなルーティングを定義。

コントローラーのアクション内でメソッドチェーンを記述。以下の例では、冒頭のLEFT JOINを使ったSQLを意図している。

なお本筋ではないが、select()メソッドの要素内でASによるエイリアスを定義できる。

メソッドチェーンでSQLをビルドした場合、結果は指定したカラムを持つ複数レコードが2次元配列で返される。

以下はビューで受け取った$ordersを表示させる例。foreachで1レコードに相当する連想配列を取り出し、ネストしたforeachで各レコードの属性・内容の対を取り出して表示させている。

ブラウザーには以下の様に表示される。

  1. ordered_at: 2020-06-05 00:00:00
  2. customer_name: customer1
  3. address: customer1@mail.com
  4. item: screw
  1. ordered_at: 2020-06-05 00:00:00
  2. customer_name: customer2
  3. address: customer2@mail.com
  4. item: rubber sheet
  1. ordered_at: 2020-06-06 00:00:00
  2. customer_name: customer2
  3. address: customer2@mail.com
  4. item: plastic plate
  1. ordered_at: 2020-06-07 00:00:00
  2. customer_name: customer1
  3. address: customer1@mail.com
  4. item: rubber sheet
  1. ordered_at: 2020-06-08 00:00:00
  2. customer_name: customer3
  3. address:
  4. item: wire

モデルのリレーション設定に基づく場合

クエリービルダーのメソッドチェーンへの対比として、同じデータをモデル間のリレーション定義に基づいて操作する例を示す。

CustomerEmailOrderの各モデルにリレーションに関する記述を追加する。

コントローラーではOrderモデルの全データを取り込み、ビューに渡す。

ビューでは受け取った$ordersから要素を順に取り出して、関連付けられたデータを表示させる。関連付けられたデータがない場合のエラーを防ぐため、optional()ヘルパーを使っている

ブラウザーには以下の様に出力される。emailが存在しない場合は無表示となっている。

  1. 2020-06-05 00:00:00: customer1
    • e-mail: customer1@mail.com
    • item: screw
  2. 2020-06-05 00:00:00: customer2
    • e-mail: customer2@mail.com
    • item: rubber sheet
  3. 2020-06-06 00:00:00: customer2
    • e-mail: customer2@mail.com
    • item: plastic plate
  4. 2020-06-07 00:00:00: customer1
    • e-mail: customer1@mail.com
    • item: rubber sheet
  5. 2020-06-08 00:00:00: customer3
    • e-mail:
    • item: wire

なおdd()で表示させた$ordersの内容は以下のとおり。1つ目の要素のみ展開している。

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

クエリービルダーでSQLを発行すると、フレームワークに負担をかけずにDBMS側でデータを絞り込めるが、その方法によっては意図した動作にならなかったり、リレーションを使えない場合がある。

結論としてはモデルクラスでクエリービルダーを記述するのがよさそうだが、その詳細についてはこちらにまとめた。

クエリービルダーでのエイリアス

クエリービルダーでSQLと同様にエイリアスを定義できる。クエリービルダーでのエイリアスを参照。

 

Laravel – Trying to get property ‘…’ of non-object

Trying to get property ‘address’ of non-object

Laravelで複数のモデル/テーブルを関連付けて表示させようとしたところ、「オブジェクトでもないものから属性を得ようとした」というエラーが表示された。

エラー発生の流れ

以下のような構造の顧客とメールアドレスのテーブルを(マイグレーションで)準備して、

以下の様にデータを準備して、

モデルでリレーションを定義して、

コントローラーでCustomerの全データをビューに渡して、

ビューで各customersに関連付けられたemailsaddressを読もうとすると、

ブラウザーにエラーが表示された。

原因

以下の様に、customer3に対してはemailsのレコードがなく、戻り値がnullとなるので、そこからaddressを得ることができないため。

対処法

@issetディレクティブ

@issetで参照結果がnullかどうかを判定。

emailsのレコードを持たないcusutomersのレコードは、@issetによってnullが無視されるため表示されないが、emailに関する2行目だけを@issetで囲めばnameだけは表示される。

連想配列の要素として指定

オブジェクトの属性ではなく、連想配列の要素として読み出す。

参照しようとする配列がnullの場合、要素指定して読みだした結果はnullとなる。customersのレコードは全て表示され、emailsのレコードがnullの場合はemailsの情報だけ表示されない。

optional()ヘルパー

optional()の引数の内容がnullの場合、その属性参照はエラーとならずに属性の参照結果がnullとなる。

ブラウザーの表示結果は上と同じ。

なお、optional()は1段先の参照結果までは保証できるが、3段目以降には作用しない。必要ならoptional()を入れ子にしなければならない。

null合体演算子との組み合わせ

PHPのnull合体演算子を使って、結果がnullの場合の内容を定義できる。

以下の様に、ブラウザー上で適切な表現が可能になる。

 

PHP – null合体演算子

null合体演算子(null coalesce operator)は、opr1 ?? opr2の形をとり、以下の様に結果を返す。

  • 左辺opr1の評価結果がnullでない場合は評価結果をそのまま返す
  • 左辺opr1の評価結果がnullの場合は右辺opr2を返す

変数の場合。未定義だとnullで第2オペランドが、定義済みだとその内容が返る。

??単独の演算子ではなく、左辺の評価対象と右辺の戻り値を含めて戻り値を持つ式に相当し、三項演算子と似ている。

 

 

WordPress – QuickLatexのエラー

QuickLatexにエラー発生

2021年11月のある日、WordPressで作業していてQuickLatexでエラーが出てしまった。

certificateに関するエラーらしいがよくわからない。

下記サイトに対処方法が整理されていて助かった。感謝したい。

参考サイト:数樂管理人のブログ~Cannot connect to QuickLaTeX server:…

手順

古い証明書の退避

さくらサーバーにssh接続して、証明書ファイルの確認。

このca-bundle.crtを別の名前にリネーム(.bakを付けるなど)。

新しい証明書の導入

このgithubサイトのWordPressページに証明書の内容が掲載されているので、その内容でca-bundle.crtファイルをローカルに作成し、先のディレクトリーにアップロード。

これで元の様にQuickLatexがコンパイルされるようになった。

 

Laravel – マイグレーション – カラムの追加

概要

マイグレーションによって生成されたテーブルに、新たにカラムを追加する。

テーブルを生成するマイグレーションファイルの前までロールバックしてファイル修正、再マイグレートという方法もあるが、ここでは新たにマイグレーションファイルを作成して追加カラムのみ処理する方法を整理する。

追加前

以下のマイグレーションファイルを準備する。

マイグレーション実行後に生成されたテーブルの構造は以下のとおり。

追加マイグレーションファイル

以下のコマンドでカラムを追加するためのマイグレーションファイルを新たに作成。

以下の様にファイルを編集。

  • up()メソッドで追加するカラムを定義
  • down()メソッドで追加したカラムを削除する処理を記述

追加マイグレーションの確認

新たに作成したマイグレーションファイルに基づいてマイグレーションを実行。

以下の様に、2つのカラムが追加されている。

ロールバックの確認

カラムを追加したマイグレーションの1ステップのみロールバック。

down()メソッドにより、追加されたカラムが削除されている。

 

Laravel – バリデーション – uniqueの除外

概要

たとえば登録済みユーザーの情報(ユーザー名、メールアドレスなど)を編集・更新することを考える。

メールアドレスをログインIDとしている場合、登録時にはアドレスにuniqueのバリデーションルールを適用している

ユーザー情報を編集する場合もメールアドレスには同様の制約をかけるが、アドレスを変更せずに他の項目を変更しようとしたとき、「既にデータベース上にアドレスが存在している」のでuniqueに対するバリデーションエラーとなる。

ここでは、その回避方法を整理する。

フォームリクエストの準備

作成

編集用のビューは別に作成済みで、ルーティングも設定されているとして、以下の様にフォームリクエストを作成したとする。

編集

フォームリクエストの実装の際、単にunique制約とする場合は以下の様になる。

バリデーションの適用

アクションの引数において、メソッドインジェクションでフォームリクエストを指定。

ここでユーザー名を空白、メールアドレスは登録済みの元の値でバリデーションが行われると、エラーは以下の様になる。

uniqueルールの除外

uniqueのルールを課しながら、特定のデータについてこれを除外するために、以下の構文が使える。

Rule::unique('テーブル名')->ignore($this->モデル->id)

以下、バリエーション。

  • Request::unique('users')だけだと、'unique:users'と同じ
  • これにメソッドチェーンでignore($this->user->id)を付けると、ルートパラメーターの値をとってくる。
  • ignore($this->user)と直接オブジェクトで指定しても結果は同じ。

このようにすることで、自身のメールアドレスに関しては同じ内容が許容される。

 

Laravel – ルートパラメーター~インスタンスを渡す

概要

ルートパラメーターは、idなどの値を指定するほか、モデルのインスタンスを直接渡してコントローラーで受け取ることができる。

値を渡して値を受け取る

ルーティング

以下の様にルーティングを設定したとする。

ルーティングは以下のようになる。

ビューでの渡し方

ビューからのルーティングでidなどの値を渡すなら、以下のようにURLで展開させるかroute()ヘルパーの第2引数で指定する。

いずれの場合も、以下のようなURLでGETリクエストされる。

ログイン済みユーザーのidを渡す場合、以下のように書ける。

コントローラーでの受け取り方

コントローラー側でパラメーターを受け取る場合、アクションメソッドの引数にパラメーターが渡されるので、それをそのまま利用すればよい。

モデルのインスタンスのまま渡して受け取る

ルーティング

以下の様にルーティングを設定する。

リソースルーティングで指定するなら以下のとおり。

何れの場合もルーティングは以下のようになる。

ビューでの渡し方

ビューからからのルーティングでモデルのインスタンスを渡す場合も、URLで展開させるかroute()ヘルパーの第2引数で指定する。ここでは認証済みのユーザーインスタンスを渡している。

インスタンスを渡しても、ルートパラメーターはidの値になる。

コントローラーでの受け取り方

コントローラーのアクションでは、引数にモデルクラスのメソッドインジェクションを適用して、idに対応したインスタンスを受け取る。

コントローラーの冒頭でモデルをuse指定してもよい。