ホーム>Laravel>Laravel Eloquentの使い方|検索から更新・削除まで実装して確認
Laravel

Laravel Eloquentの使い方|検索から更新・削除まで実装して確認

いつもご利用ありがとうございます。
この記事には広告が掲載されており、その広告費によって運営しています。

Laravel 13 + SQLite で問い合わせ対応ボードを作り、Eloquent の where・orderBy・get・first・count・exists・with・join・update・ソフトデリートの戻り値と実行SQLを1つずつ実測した記録です。

Docker 上に Laravel 13 + SQLite の環境を構築し、「問い合わせ対応ボード」機能を作りながら、頻繁に使用するメソッドを確認していきます。

各セクションは「作成したい画面」「実装コードと出力結果」「戻り値および発行 SQL」の順で構成しています。

なお、Eloquent の読み方は一般に「エロクアント」と呼ばれます。

検証した環境

すべて以下の環境における実測値です。

項目
Laravel Framework13.31.0
PHP8.3.33 (NTS)
データベースSQLite 3.46.1

SQL は DB::enableQueryLog() を使用します。

テストデータ

tickets テーブルに 6 件

customers テーブルに 3 件

のデータを登録しました。

ticketsテーブルの id = 4 の問い合わせのみ customer_id が NULL(顧客未登録)の状態で用意しました。

Eloquent の設定方法

どのデータベースに、どの設定で接続するかを決められます。

大抵の設定はconfig/database.php で行いますが、.env で上書きして使用します。

今回のデモの .env に書いた DB 関連の行は次の 1 行だけです。

.env

DB_CONNECTION=sqlite

config/database.php にはこんな感じに書かれています。

'default' => env('DB_CONNECTION', 'sqlite'),

'connections' => [
    'sqlite' => [
        'driver' => 'sqlite',
        'url' => env('DB_URL'),
        'database' => env('DB_DATABASE', database_path('database.sqlite')),
        'prefix' => '',
        'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
        'busy_timeout' => null,
        'journal_mode' => null,
        'synchronous' => null,
    ],

ちなみに、反映されたかどうかは php artisan config:show で確認できます。

うまく変更が反映されない場合は、ほぼキャッシュが原因なので、コマンドを打って解決します。

php artisan config:clear

接続できたかどうかは、 php artisan db:show で確認できます。

php artisan db:show

  SQLite .............................................................. 3.46.1
  Connection .......................................................... sqlite
  Database .......................................... database/database.sqlite
  Tables .................................................................. 11

get() の基本的な書き方

get()は、データを複数件取得したいときに使います。

一覧画面に未対応の問い合わせを並べて表示します。

laravel eloquent 01 board

この画面のテーブル表示部分を構成するのが get() です。

条件を指定して get() を呼び出しました。

$tickets = Ticket::get();

get() の戻り値は配列ではなく Collection という連想配列みたいな形式で返されます。

first() の基本的な書き方

first()は、データを1件取得したいときに使います。

ダッシュボードの「次に対応する 1 件」カードに、最も優先度が高い未対応案件を 1 件だけ表示したい場合を想定します。

ソート条件を指定して first() を呼び出しました。

$t = Ticket::query()
    ->where('status', 'open') //status カラムが open のもの
    ->orderByDesc('priority') //priority が高い順
    ->orderBy('id') //id が高い順
    ->first(); //その中から1件

get() では空の Collection が返りましたが、first() では null が返ります。

この点が get() との最大の相違点であり、$t->subject のようにプロパティへアクセスする前に null チェックが必要になります!

findOrFail()

findOrFail()は、データを1件取得したいが、nullの時に HTTP 404 レスポンスを返す関数です。

存在しない ID が直接指定された場合に手動で abort(404) を書く手間が省けるため、

詳細画面などで重宝します

$t = Ticket::findOrFail($ticketId);

where() の基本的な書き方

where()は、データを絞り込むときに使用します。

検索フォームでステータスと件名を指定し、一覧を絞り込みたい場合を考えます。

laravel eloquent 04 open priority

ステータスと件名の両方を指定して呼び出しました。

$r = Ticket::query()
    ->where('status', 'open')
    ->where('subject', 'like', '%請求書%')
    ->get();

echo $r->count(); // 2
echo $r->pluck('id')->implode(', '); // 2, 5
SQL[1] select * from "tickets" where "status" = ? and "subject" like ? and "tickets"."deleted_at" is null
       -- bindings: ["open","%請求書%"]

where() を連続して記述すると、各条件は and で結合されます。

引数が 2 つの場合は = 比較になります。

Ticket::query()->where('priority', 2)->get();

引数が 3 つの場合は第 2 引数が比較演算子として機能します。

Ticket::query()->where('priority', '>=', 2)->get();

orderBy() の基本的な書き方

orderBy()は、並び順を変更することができます。

緊急度の高い問い合わせを一覧の最上位に表示するため、優先度の降順で並べ替えを行いました。

$r = Ticket::query()->orderByDesc('priority')->get();

laravel eloquent 06 customer order

count() の基本的な書き方

count()は、レコードの数を「数字だけ出力する」関数です。

画面上部に「未対応 4 件」と件数を表示するため、抽出条件を指定して count() を呼び出しました。

$c = Ticket::query()->where('status', 'open')->count();

echo get_debug_type($c); // int
echo $c;                 // 4

withCount()の基本的な書き方

withCount()は、リレーション先のレコードの数を出力する関数です。

今回は、「顧客ごとの問い合わせ件数」の集計に使いました。

$customers = Customer::query()->withCount('tickets')->get();
// A商事 tickets_count=int 2
// B工業 tickets_count=int 2
// C制作 tickets_count=int 1

customer レコードに、tickets_countというカラムが追加され、件数が入っています。

exists() の基本的な書き方

exists()は、該当するレコードが1件でも存在するのかどうか、True or False で判定してくれる関数です。

今回は、「未対応案件が 1 件でも存在するかどうか」チェックするために使います。

$e = Ticket::query()->where('status', 'open')->where('priority', 3)->exists();

echo get_debug_type($e);  // bool
var_export($e);           // true

count()first()でも同じようなことは出来ますが、

count() は対象をすべてカウントし、first() は 1 行分のモデルオブジェクトをインスタンス化するため、

exists() は、実データをメモリに読み出すことなく真偽値のみを取得することから、軽量な選択肢となります。

doesntExist() メソッドは、exists() と逆で、「存在しなければ」 True になります。

has() の基本的な書き方

has()は、リレーション先のデータを持つレコードだけに絞り込む関数です。

「問い合わせが 1 件でもある顧客」を取得したい場合に使います。

Customer::query()->has('tickets')->pluck('name');
// ["A商事","B工業","C制作"]

条件を付けて絞り込むときは whereHas() を使います。

Customer::query()
    ->whereHas('tickets', fn ($q) => $q->where('priority', 3)) //緊急の問い合わせを持つ顧客
    ->pluck('name');
// ["A商事"]

発行される SQL は exists のサブクエリでした。

select "name" from "customers" where exists (select * from "tickets" where "customers"."id" = "tickets"."customer_id" and "priority" = ? and "tickets"."deleted_at" is null)

顧客を全件取得してから PHP 側でループする必要がありません。

with() の基本的な書き方

with()は、リレーション先のデータをまとめて取得する関数です。

一覧の各行に顧客名も表示したい場合に使います。

$tickets = Ticket::query()
    ->with('customer') //Ticketモデルに書いたリレーションを取得
    ->get();

必要な列だけに絞る場合は、リレーション名の後ろにカラムを指定します。

Ticket::query()->with(['customer:id,name'])->get();

Eagar Load と言って、最初に一気に取得するので N+1 問題を回避することができます。

load()の基本的な書き方

with()と似ていますが、取得済みの Collection に、後から足すときは load() を使います。

$tickets = Ticket::query()->orderBy('id')->get();
$tickets->load('customer');

これらはサーバーの処理速度を保つために必要な関数です。

Laravel の表示速度をあげたいとき、実装コストに対して効果が高いものはコレ!

join() の基本的な書き方

join()は、別テーブルの列を 1 本の SQL でまとめて取得する関数です。

SQL のjoinみたいな感じです。

顧客名で絞り込んだり、顧客名で並べ替えたりする場合に使います。

$r = Ticket::query()
    ->leftJoin('customers', 'customers.id', '=', 'tickets.customer_id')
    ->select('tickets.*', 'customers.name as customer_name')
    ->orderBy('tickets.id')
    ->get();

withとの違いは、カラムがTicketレコードに追加されます。

リレーションではなく、カラムが横並びになります。

特段理由がない限り、JOIN ではなく、with を使ってリレーションを使うのを僕はおすすめします。

select() を書かないと id が顧客側の ID に上書きされる

カラムが重複すると、上書きされてしまうため、select()を使って回避できます。

->select('tickets.id', 'customers.id as customer_id')

カラムが重複した場合は上記のように、asと命名を変更すると両方使えます。

join() では顧客未登録のレコードが落ちる

customer_id が NULL の ID=4 は、join()(INNER JOIN)だと結合先がないため結果から消えます。

全件を出したいときは leftJoin() を使います。

update() の基本的な書き方

update()は、レコードを更新する関数です。

一覧の「対応済みにする」操作で、ステータスを更新します。

$ticket = Ticket::findOrFail(1);
$result = $ticket->update(['status' => 'done']);

上記の書き方は、Ticket モデルに一括代入を設定しているときに可能です。

#[Fillable(['customer_id', 'subject', 'body', 'priority','status'])] //statusを追加しないとUPDATEは使えない

または、preventSilentlyDiscardingAttributes() を有効にすると、例外に変わります。

Model::preventSilentlyDiscardingAttributes();

Ticket::findOrFail(1)->update(['status' => 'done']);

save()でも出来る

外部から変更させたくない列は、プロパティに代入して save() します。

$ticket = Ticket::findOrFail(1);
$ticket->status = 'done';
$ticket->save();

delete() の基本的な書き方

delete()は、レコードを削除する関数です。

ソフトデリート機能を使うと、論理削除といって、DB にデータは残しつつ、サービス上ではデータを表示しない状態にできます。

Laravel では、Ticket モデルに use SoftDeletes; を書くことで簡単に導入できます。

deleted_at へ日時が入ることでソフトデリートとなります。

$ticket = Ticket::findOrFail(2);
$deleted = $ticket->delete();

laravel eloquent 07 after delete

削除後に取得し直すと、ID=2 は一覧から消えています。

削除済みも含めて表示したい場合は withTrashed()、削除済みだけを数えたい場合は onlyTrashed() を使います。

laravel eloquent 08 with trashed

戻すときは restore() で、deleted_atnull が再代入されます。

Ticket::withTrashed()->findOrFail(2)->restore(); // true

本当に消す時は forceDelete() を使います。

Ticket::findOrFail(6)->forceDelete(); // true

以上、よく使う Eloquent の関数について、実装しながらまとめてみました。

フィードバックのお願い
この記事のフィードバックがありましたらYoutubeの適当な動画にコメントしていただいたり、お問い合わせからご連絡ください。