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指定してもよい。

 

Laravel – アップロード画像の変更・削除

概要

公開領域のディスクに保存された画像ファイルを変更・削除する手順を整理する。

  1. アップロード画像処理~準備
  2. 画像ファイルの入力
  3. アップロード画像ファイルの保存・登録
  4. アップロード画像の表示
  5. アップロード画像の変更・削除

変更は現在のファイルを削除して新しいファイルを保存するので、画像ファイルの削除手順がキーになる。

一般的なアップロードファイルの削除はこちらを参照。

ファイルの削除手順

storageディレクトリーのディスクに保存された画像ファイルの削除は、以下で行う。

ここで'ディスク''ファイルパス'は、保存・登録store()メソッドの引数に指定したものと同じ。

データの変更

例題のアプリケーションを、画像ファイルのほか、商品名や価格も併せて変更できるように修正する。

ルーティング

  • 画像データの編集は、GETメソッドでeditアクションにルーティングされる。
  • 編集されたデータの更新登録は、PATCHメソッドでupdateアクションにルーティングされる。

ビューからのリンク~index.blade.php

indexページで一覧された各商品に、編集のリンクを配置する。要点としては、

  • a要素のhrefの遷移先でroute('items.edit', $item)をしている
  • 第1引数はeditのルート名、第2引数は編集対象のデータインスタンス
  • GETのURLは以下の様に展開される
    • アプリケーションルート/{$id}/edit

コントローラー~edit

GETメソッドでルーティングされたeditアクションでは、URLに含まれる$idに対応するインスタンス$itemがデータベースから引数として渡され、このインスタンスをそのままeditビューに渡している。

ビュー~edit.blade.php

editビューでは渡された$itemインスタンスの内容を各input要素に表示し、画像ファイルのパスから画像を表示している。

  • 画像を扱うため、formenctypeオプションを指定している
  • form要素で指定できるメソッドはGETPOSTだけだが、updateルートはPATCHメソッドを期待しているので、@methodディレクティブで'patch'を指定している
  • 新規入力時と同じく、フォームリクエストで編集時の入力バリデーションを行っている

コントローラー~update

PATCHメソッドでルーティングされたupdateアクションでは、編集入力内容によって、商品データの内容と画像ファイルを更新している。

  • 編集時に画像が選択されていれば、現在のファイルを削除して、選択されたファイルを保存
    • このときにファイルのパスも取得
  • 商品内容、画像ファイルへのパスでデータの内容を更新

データの削除

ルーティング

indexページで一覧表示された商品の削除ボタンを押すと、DELETEメソッドでdestroyにルーティングされる。

ビューへの削除ボタン配置~index.blade.php

indexページの各商品表示にフォームと削除ボタンを加える。

  • form要素ではmethod="post"を指定し、@methodディレクティブで'delete'メソッドを指定している
  • 削除する商品を指定してdestroyにルーティングするだけなので、フォームにはsubmitボタンだけが配置されている

コントローラー~destroy

destroyアクションでは、ファイルが登録されていればpublicディスクから削除し、商品データをデータベースから削除している。

 

Laravel – アップロード画像の読み込み・表示

概要

データベースに登録されたパスから画像ファイルを取得し、ビューで表示する手順を整理する。ここでは、indexページで画像のほかに商品名や商品価格も表示させる。

  1. アップロード画像処理~準備
  2. 画像ファイルの入力
  3. アップロード画像ファイルの保存・登録
  4. アップロード画像の表示
  5. アップロード画像の変更・削除

表示された画像ファイルの変更・削除についてはこちらを参照。

publicディスクへのリンク作成

アップロード画像を保存したpublicディスクはstrage/app/publicに割り当てられているが、このディレクトリーはWebサーバー上は公開されていない。

そこで、publicディスクをアプリケーション下のpublicディレクトリーで公開するため、以下のコマンドを実行しておく。

これにより、publicディレクトリーにシンボリックリンクstorageが作成されて、publicディスクに割り当てられたディレクトリーに公開領域からアクセスが可能になる。

コントローラー~index

indexアクションでitemsテーブルの全データを取得し、これをビューに渡して表示させる。

ビュー~index.blade.php

コントローラーから$itemsを受け取り、@foreachディレクティブによって全データの内容と画像データを表示する。

ここでは\Storageファサードのurl()メソッドを使って画像ファイルのパスを得ていて、これによってpublicディレクトリー下のstorage/app/publicディレクトリー下にある画像ファイルにアクセスしている。

公開領域のファイルのURLについてはこちらを参照。

なお、商品画像が登録されていない場合は、プレースホルダー画像を表示させている。

Laravel – アップロード画像の保存・登録

概要

フォームで選択した画像ファイルをサーバーに保存し、画像ファイルへのパスを含むデータをデータベースに登録する手順を整理する。

  1. アップロード画像処理~準備
  2. 画像ファイルの入力
  3. アップロード画像ファイルの保存・登録
  4. アップロード画像の表示
  5. アップロード画像の変更・削除

要点は以下のとおり。

  1. フォームリクエストからアップロード画像を取得
  2. 画像を公開エリアに保存し、パスを取得
  3. 画像へのパスをデータベースに登録

一般的なアップロードファイルの保存方法についてはこちらを参照。

画像ファイル保存・登録後の画像の表示についてはこちらを参照。また、保存済みの画像ファイルの変更・削除についてはこちらを参照。

コントローラー~store

フォームで画像選択後にsubmitボタンを押すと、POSTメソッドでコントローラーのstoreアクションへルーティングされる。storeアクションでの処理は以下のとおり。

  • フォームリクエストから画像ファイルのインスタンスを取得
  • 一時保存領域に保存されたファイル本体を、store()メソッドで公開領域に保存
  • store()メソッドの戻り値として返される、画像ファイルへのパスを取得
  • Itemsデータの他の項目とともに、ファイルへのパスをデータベースへ登録

ここでは、store()の第1引数と第2引数を指定し、publicディスク内のitemsディレクトリー下にファイルを保存している。

データ登録確認

indexページで新規データを登録した結果を、MySQLのコンソールで確認した例。

画像ファイルはファイル名にランダム文字列が与えられ、publicディスク下のitemsディレクトリー内に保存される。imageカラムにitemsディレクトリー以下のパスが登録されている。

publicディスクが充てられているstorage/app/publicディレクトリーを確認すると、itemsディレクトリー内に上記のファイルが保存されているのが確認できる。

no_image.pngは、画像が登録されていない場合に表示するプレースホルダー画像。