🌟

Laravel Tips集 ~Eloquent & Databaseの便利技大全~

2025/02/14に公開

Laravel & Database チートシート

各セクションでは、Laravel の便利な機能とその使い方を簡潔にまとめています。コード例はそのままご利用いただけます。


1. オリジナル属性の取得

Laravel のアクセサは、取得時に属性を変換できますが、元の値を知りたい場合は getRawOriginal を使います。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;

class User extends Model
{
    protected function username(): Attribute
    {
        return Attribute::make(
            function (string $value, array $attributes) {
                return sprintf('%s#%s', $value, $attributes['app_id']);
            }
        );
    }
}

$user = User::create([
    'username' => 'oussama',
]);

$user->getOriginal('username'); // oussama#1234
$user->getRawOriginal('username'); // oussama

2. カスタムファクトリパスの指定

モデルファクトリの場所をカスタマイズしたい場合、newFactory メソッドを定義して、独自のロジックでファクトリを返すことができます。

<?php

namespace App\Models;

use Database\Factories\UserFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    // ...

    protected static function newFactory()
    {
        // ... custom logic
        return new UserFactory();
    }
}

3. 特定のカラムだけを eager load する

リレーションを eager load するとき、必要なカラムのみ指定することでメモリ使用量を抑えられます。

<?php

$users = Post::with('author:id,name')->get();

4. is() メソッドでモデル同士の同一性を確認

is() ヘルパーを使えば、2つのモデルが同じかどうか簡単に確認できます。

<?php

$user = User::find(1);  
$sameUser = User::find(1);  
$differentUser = User::find(2);

$user->is($sameUser); // true  
$user->is($differentUser); // false

5. 短縮形の whereHas(whereRelation)の利用

従来の whereHas の代わりに、whereRelation を使うと、コードがより簡潔になります。

<?php

// 旧:whereHas を使う場合
User::whereHas('comments', function ($query) {
    $query->where('created_at', '>', now()->subDay());
})->get();

// 新:whereRelation を使う場合
User::whereRelation('comments', 'created_at', '>', now()->subDay())->get();

6. ダイナミックな where 条件

Laravel では、メソッド名に条件を含めることで、動的な where 条件を定義できます。
※IDEの補完のため、PHPDocにメソッド名を追加するとよいでしょう。

<?php

// 例:name と last_name に対して条件を指定する場合
User::whereNameAndLastName('oussama', 'mater')->first();

7. モデルイベントを発火せずに更新・削除する方法

saveQuietly()deleteQuietly() を使えば、モデルのイベントを発火させずに更新・削除できます。

<?php

$user = Auth::user();

$user->name = 'Oussama Mater';

// モデルイベントを発火させずに更新
$user->saveQuietly();
$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();

8. BelongsToMany 関係の関連IDを取得する

allRelatedIds() を使えば、belongsToMany リレーションの全ての関連IDを簡単に取得できます。

<?php

class User extends Model
{
    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }
}

$user = User::find(1);

$roleIds = $user->roles()->pluck('id')->toArray();
$roleIds = $user->roles()->allRelatedIds()->toArray();

9. 実行された全クエリの取得

DB::listen を使うことで、実行された SQL、バインディング、実行時間などを取得し、デバッグや通知に活用できます。

<?php

DB::listen(function (QueryExecuted $query) {
    dump($query->sql);      // 例: select * from `users` where `users`.`id` = ? limit 1
    dump($query->bindings); // バインディング内容
    dump($query->time);     // 実行時間(ミリ秒)
});

10. Prunable トレイトでレコードを削除

Prunable トレイトを使えば、特定条件に合致したレコード(ソフトデリート済みも含む)を自動で削除できます。

<?php

// 削除条件を定義
class User extends Authenticatable
{
    use Prunable;

    public function prunable()
    {
        return static::query()
            ->whereNull('email_verified_at')
            ->where('created_at', '<', now()->subMonths(6));
    }
}

// スケジュールに PruneCommand を追加して定期実行します
$schedule->command(PruneCommand::class)->daily();

11. whereBelongsTo メソッド

whereBelongsTo を使うと、親モデルに基づいたクエリをより読みやすく記述できます。

<?php

// 従来の書き方
$posts = Post::where('user_id', $user->id)->get();
// より読みやすい書き方
$posts = Post::whereBelongsTo($user)->get();

12. whenQueryingForLongerThan メソッドで遅いクエリを検出

指定したミリ秒以上かかるクエリが発生した場合に、特定の処理(例:通知など)を実行できます。

<?php

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
            // クエリが遅い場合の処理(例:通知やログ出力)
        });
    }
}

13. foreignIdFor メソッド

モデル名をスネークケースに変換し、「id」を付加した外部キーを簡単に定義できます。

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->foreignIdFor(User::class); // user_id が自動で作成される
    $table->timestamps();
});

14. whereYear() の注意点と範囲検索の推奨

whereYear() はインデックスが使われない場合があるため、範囲検索を利用する方が高速です。

<?php

// whereYear() を使用した例
DB::table('user_posts')
    ->select('user_id', DB::raw('COUNT(*) as count'))
    ->whereYear('created_at', 2023)
    ->groupBy('user_id')
    ->get();

// 範囲検索を使用した例
DB::table('user_posts')
    ->select('user_id', DB::raw('COUNT(*) as count'))
    ->whereBetween('created_at', ['2023-01-01 00:00:00', '2024-01-01 00:00:00'])
    ->groupBy('user_id')
    ->get();

15. withCount メソッド

リレーションの件数を取得する場合、実際にリレーションをロードせずにカウントのみを取得できます。

<?php

$users = User::withCount(['posts'])->get();

// 結果は $users->posts_count としてアクセス可能

16. toQuery メソッド

取得したコレクションに対して、1回のクエリで一括更新を行う際に便利です。

<?php

$users = User::where('status', 'VIP')->get();

// 各モデルを個別に更新する代わりに…
foreach ($users as $user) {
    $user->update(['status' => 'Administrator']);
}

// これを一括で実行
$users->toQuery()->update(['status' => 'Administrator']);

17. whenTableDoesntHaveColumn / whenTableHasColumn メソッド

スキーマが環境ごとに異なる場合、特定のカラムが存在しない(または存在する)場合にだけ処理を実行できます。

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

// email カラムが存在しない場合のみ追加
Schema::whenTableDoesntHaveColumn('users', 'email', function(Blueprint $table){
    $table->string('email')->unique();
});

// email カラムが存在する場合のみ削除
Schema::whenTableHasColumn('users', 'email', function(Blueprint $table){
    $table->dropColumn('email');
});

18. withoutTimestamps メソッド

更新時に updated_at カラムの更新を抑えたい場合に使用します。

<?php

// updated_at が更新されない
Post::withoutTimestamps(fn () => $post->increment(['reads']));

19. ランダム順での取得(Random Ordering)

inRandomOrder() を使って、結果をランダムに並べ替えられます。

$randomUser = DB::table('users')
    ->inRandomOrder()
    ->first();

20. リレーションのタイムスタンプ更新(Touching Relationships)

setTouchedRelations() を使って、関連リレーションの updated_at をまとめて更新できます。

<?php

$user = User::firstOrFail();
$user->setTouchedRelations(['posts']);

// 関連する posts の updated_at が更新される
$user->save();

21. 関連モデルとそのリレーションを再帰的に保存

push() メソッドを使用すると、関連する全てのモデルを再帰的に保存できます。

<?php

$post = Post::find(1);

$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';

// 従来の個別保存をせず…
$post->push();

22. saveMany メソッド

複数の関連モデルを一括して保存する際に利用します。

<?php

$post = Post::find(1);

$post->comments()->saveMany([
    new Comment(['message' => 'A new comment.']),
    new Comment(['message' => 'Another new comment.']),
]);

23. JSON フィールドのクエリ

JSON 型のカラムに対して、特定の値や配列の条件で検索できます。

<?php

// preferences→dining→meal が 'salad' のユーザーを取得
DB::table('users')
    ->where('preferences→dining→meal', 'salad')
    ->get();

// options→languages 配列に 'en' と 'de' が含まれるユーザーを取得
DB::table('users')
    ->whereJsonContains('options→languages', ['en', 'de'])
    ->get();

// options→languages 配列の要素数が1より多いユーザーを取得
DB::table('users')
    ->whereJsonLength('options→languages', '>', 1)
    ->get();

24. toBase() メソッド

膨大なデータをモデル化せず、シンプルなオブジェクトのコレクションとして取得することでメモリ使用量を節約できます。

<?php

// PHP オブジェクトのコレクションとして取得(モデルはハイドレートされない)
$users = User::toBase()->get();

25. value() メソッド

特定のカラムの単一の値だけを直接取得する方法です。

<?php

$email = DB::table('users')->where('name', 'John')->value('email');

dd($email); // john@example.com

26. whereAll と whereAny メソッド

複数カラムに同じ条件を適用でき、全て(whereAll)またはいずれか(whereAny)の条件にマッチするかを確認できます。

<?php

$search = 'ous%';

// whereAll:全てのカラムが条件に一致する場合
User::query()
    ->whereAll(['first_name', 'last_name'], 'LIKE', $search)
    ->get();

// whereAny:いずれかのカラムが条件に一致する場合
User::query()
    ->whereAny(['first_name', 'last_name'], 'LIKE', $search)
    ->get();

27. eager load 時のリレーション件数の制限

Laravel 11 から、ネストしたリレーションにも件数制限が可能になりました。

<?php

User::with([
    'posts' => fn ($query) => $query->limit(5)
])->paginate();

28. キャスト定義をメソッドで記述

Laravel 11 以降、キャストの定義をプロパティではなくメソッドで記述でき、引数の指定がより簡潔になります。

<?php

namespace App\Models;

use App\Casts\Json;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
   // Laravel ≤ 10 の場合
   protected $casts = [
       'statuses' => AsEnumCollection::class.':'.ServerStatus::class,
   ];

   // Laravel 11 の場合
   protected function casts(): array
   {
       return [
           'statuses' => AsEnumCollection::of(ServerStatus::class),
       ];
   }
}

29. withExists メソッド

リレーションの存在有無を確認し、結果を属性として取得できます。

<?php

$user = User::query()
    ->withExists('posts as is_author')
    ->get();

// $user->is_author は true か false

30. whereKey メソッド

主キーに対して、WHERE IN の条件をシンプルに記述できます。

<?php

// 従来の書き方
Post::whereIn('id', [1,2,3])->get();

// 新しい書き方
Post::whereKey([1,2,3])->get();
Post::whereKeyNot([1,2,3])->get();

31. カラム名の曖昧さを防ぐ

qualifyColumn を使って、カラム名が他のテーブルと衝突しないようにテーブル名をプレフィックスできます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Team extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
    ];

    public function servers(): HasMany
    {
        return $this->hasMany(Server::class)->orderBy(
            (new Server)->qualifyColumn('name')
        );
    }
}

32. doesntHave メソッド

リレーションを持たないレコードを取得する際に使用します。

<?php

use App\Models\Post;

$posts = Post::doesntHave('comments')->get();

33. モデルイベントを全てミュートする

withoutEvents を使うことで、ブロック内の処理中に発火するモデルイベントを抑制できます。

<?php

use App\Models\User;

$user = User::withoutEvents(function () {
    User::findOrFail(1)->delete();
    
    return User::find(2);
});

34. unguard を利用する

Model::unguard() を使うと、一時的に全モデルで guard(保護)が解除され、fillable に含まれていない属性も一括で設定できます。

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    protected $fillable = ['name'];
}

Model::unguard();

Flight::create([
    'name' => 'flight',
    'not_in_fillable' => true,
]);

Model::reguard();

35. ピボット属性名のカスタマイズ

多対多リレーションで、中間テーブルの属性名(pivot)を変更できます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class User extends Model
{
    public function podcasts(): BelongsToMany
    {
        return $this->belongsToMany(Podcast::class)
            ->as('subscription')
            ->withTimestamps();
    }
}

$users = User::with('podcasts')->get();

foreach ($users->flatMap->podcasts as $podcast) {
    echo $podcast->subscription->created_at;
}

36. インクリメント・デクリメントメソッド

数値カラムの値を増減させるためのメソッドが用意されています。

<?php

// 1ずつ増加
DB::table('users')->increment('votes');

// 5ずつ増加
DB::table('users')->increment('votes', 5);

// 1ずつ減少
DB::table('users')->decrement('votes');

// 5ずつ減少
DB::table('users')->decrement('votes', 5);

// 増加と同時に他のカラムを更新
DB::table('users')->increment('votes', 1, ['name' => 'John']);

// 複数カラムを同時に増加
DB::table('users')->incrementEach([
    'votes' => 5,
    'balance' => 100,
]);

// Eloquent での利用例
User::query()->incrementEach([
    'votes' => 5,
    'balance' => 100
]);

37. モデルの属性が変更されたか確認する

originalIsEquivalent() を使って、特定の属性が変更されたかどうかを判断できます。

<?php

$user = User::firstOrFail(); // ['name' => 'old']

$user->name = 'old'; // 同じ値を再設定

$user->originalIsEquivalent('name'); // true

$user->name = 'new'; // 値を変更

$user->originalIsEquivalent('name'); // false

38. getOrPut メソッド(コレクション操作)

コレクション内にキーが存在しない場合、指定した値を設定して取得できます。

$collection = collect(['price' => 100]);

$value = $collection->getOrPut('name', 'Desk');

39. Higher Order "orWhere" メソッド

コレクションで使う高階メソッドと同様に、クエリでも高階な記法が使えます。

<?php

User::popular()->orWhere->active()->get();

40. whereIntegerInRaw で高速クエリ

数値の whereIn を高速化するために、whereIntegerInRaw を利用できます。

<?php

// 従来の書き方
Product::whereIn('id', range(1, 10000))->get();

// 新しい書き方
Product::whereIntegerInRaw('id', range(1, 10000))->get();

41. upsert メソッド

存在すれば更新、存在しなければ挿入する一括操作を行えます。

<?php

Flight::upsert([
    ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
    ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], uniqueBy: ['departure', 'destination'], update: ['price']);

42. タイムスタンプがないテーブルの指定

テーブルに created_atupdated_at が存在しない場合、モデルで $timestamps を false に設定します。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Flight extends Model
{
    public $timestamps = false;
}

43. simplePaginate メソッド

次へ・前へボタンのみのシンプルなページネーションを利用する方法です。

<?php

$users = User::simplePaginate(15);

44. doesntExist メソッド

レコードが存在しないかどうかを簡潔にチェックできます。

if (User::doesntExist()) {
    // レコードが存在しない場合の処理
}

45. クエリのクローン作成

同じ基本条件を使ったクエリを複数作成する場合に、clone() を使えます。

<?php

$query = User::query()->where('created_at', '<', now()->subMonths(3));

$verified_users = $query->clone()->whereNotNull('email_verified_at')->get();
$unverified_users = $query->clone()->whereNull('email_verified_at')->get();

46. 指定属性の一時非表示

makeHidden() を使うと、指定した属性を一時的に隠すことができます。

<?php

$users = User::all()->makeHidden(['address', 'phone_number']);

47. グローバルスコープの無効化

特定または全てのグローバルスコープを除外してクエリを実行できます。

<?php

// 全てのグローバルスコープを無効化
User::withoutGlobalScopes()->get();

// 特定のスコープのみ無効化
User::withoutGlobalScope(FirstScope::class)->get();

48. パスワードの自動ハッシュ化

hashed キャストを使うことで、パスワードが自動的にハッシュ化されます。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected function casts(): array
    {
        return [
            'password' => 'hashed',
        ];
    }
}

$user = User::create([
    'password' => 'password', // 自動的にハッシュ化される
]);

49. firstOr メソッド

レコードが存在しない場合に、追加のロジックを実行して新しいレコードを作成できます。

<?php

$user = User::where('email', $request->input('email'))->firstOr(function () {
    return User::create([
        // 新規作成時のデータ
    ]);
});

50. latest と oldest メソッド

最新または最古のレコードを簡単に取得できます。カラム指定も可能です。

<?php

User::latest()->get();
User::oldest()->get();

User::latest('id')->get();
User::oldest('id')->get();

51. insertOrIgnore メソッド

挿入時に重複エラーなどを無視して処理を実行できます。

<?php

DB::table('users')->insertOrIgnore([
    ['id' => 1, 'email' => 'sisko@example.com'],
    ['id' => 2, 'email' => 'archer@example.com'],
]);

52. findMany メソッド

複数のIDに一致するレコードを一度に取得できます。

<?php

$users = User::findMany([1, 2, 3]);

53. モデルが前回取得時から変更されたか確認する

isDirty() を使って、特定の属性が変更されたかどうか確認できます。

<?php

$user = User::create([
    'first_name' => 'John',
    'last_name' => 'Doe',
    'age' => 20,
]);

$user->age = 21;

$user->isDirty(); // true
$user->isDirty('age'); // true
$user->isDirty('first_name'); // false

54. タイムスタンプカラムのカスタマイズ

既存テーブルに合わせて、デフォルトのタイムスタンプカラム名を変更できます。

<?php

class Flight extends Model
{
    const CREATED_AT = 'creation_date';
    const UPDATED_AT = 'updated_date';
}

55. 塗りつぶし不可属性への値割り当ての防止

preventSilentlyDiscardingAttributes() を利用して、fillable に含まれない属性の割り当て時に例外を発生させられます。

<?php

use Illuminate\Database\Eloquent\Model;

Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());

56. updateOrCreate メソッド

指定条件に一致するレコードがあれば更新し、なければ新規作成できます。

<?php

$flight = Flight::updateOrCreate(
    ['departure' => 'Oakland', 'destination' => 'San Diego'],
    ['price' => 99, 'discounted' => 1]
);

57. destroy メソッドでレコード削除

主キーを指定して、簡単にレコードを削除できます。複数のIDにも対応。

<?php

Flight::destroy(1);
Flight::destroy(1, 2, 3);
Flight::destroy([1, 2, 3]);

58. withCasts メソッドで実行時にキャスト

クエリ実行時にキャストを動的に適用できます。

<?php

$users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
        ->whereColumn('user_id', 'users.id')
])->withCasts([
    'last_posted_at' => 'datetime'
])->get();

59. valueOrFail メソッド

レコードが存在しない場合に例外を投げながら、単一のカラム値を取得できます。

<?php

$flightName = Flight::where('legs', '>', 3)->valueOrFail('name');

60. firstWhere メソッド

指定した条件に一致する最初のレコードを取得する簡潔な方法です。

<?php

$user = User::query()->firstWhere('name', 'john');

61. N+1 問題の防止

eager loading を徹底させるため、開発時に lazy loading を禁止する設定が可能です。

<?php

use Illuminate\Database\Eloquent\Model;

Model::preventLazyLoading(! $this->app->isProduction());

Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
    info("Attempted to lazy load [{$relation}] on model [".get_class($model)."].");
});

62. JSON の妥当性チェック

Str::isJson() を使って、文字列が有効な JSON かどうか確認できます。

<?php

use Illuminate\Support\Str;

Str::isJson('[1,2,3]'); // true
Str::isJson('{"first": "John", "last": "Doe"}'); // true
Str::isJson('{first: "John", last: "Doe"}'); // false

63. 単語出現回数のカウント

substrCount() を利用して、文中に特定の単語が何回出現するか数えられます。

<?php

use Illuminate\Support\Str;

Str::substrCount('My name is Bond, James Bond', 'Bond'); // 2

64. カラム削除のショートカット

Laravel は特定のカラム削除のためのショートカットメソッドを提供しています。

<?php

Schema::table('users', function (Blueprint $table) {
    $table->dropTimestamps();
    $table->dropSoftDeletes();
    $table->dropRememberToken();
    $table->dropMorphs('morphable');
});

65. 非表示カラム(Invisible Columns)

MySQL/MariaDB で、SELECT * で自動的に取得されないカラムを作成できます。

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('users', function (Blueprint $table) {
    $table->string('secret')->nullable()->invisible();
    $table->string('posts_count')->default(0)->invisible();
});

$user = User::first();
$user->secret; // null

// 明示的に選択する必要がある
$user = User::select('secret')->first();

66. 生成カラム(Generated Columns)

マイグレーションで、計算結果を自動生成するカラムを定義できます。

<?php

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->decimal('unit_price');
            $table->integer('quantity');
            $table->decimal('full_price')->storedAs('unit_price * quantity');
        });
    }
};

67. シーディング時のモデルイベント無効化

シーディング処理中はモデルイベントを発火させず、処理速度を向上させることができます。

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;

class DatabaseSeeder extends Seeder
{
    use WithoutModelEvents;

    public function run(): void
    {
        $this->call([
            UserSeeder::class,
            PostSeeder::class,
            CommentSeeder::class,
        ]);
    }
}

68. デフォルトモデルの利用

リレーションが null の場合でも、withDefault() を使えばデフォルトのモデルを返せます。

<?php

public function user(): BelongsTo
{
    return $this->belongsTo(User::class)->withDefault([
        'name' => 'Guest Author',
    ]);
}

// 以降は $post->user->name が常に値を返す

69. ソフトデリート済みモデルの完全削除

forceDelete() または Laravel 11.21 以降は forceDestroy() で、ソフトデリート済みのレコードを完全に削除できます。

<?php

// Laravel < v11.20
$flight = Flight::find(1);
$flight->forceDelete();

// Laravel v11.21 以降
Flight::forceDestroy(1);

70. ゴミ箱のレコードのみ取得(onlyTrashed)

ソフトデリートされたレコードだけを取得できます。

<?php

$trashedUsers = User::query()->onlyTrashed()->get();

71. 複数カラムをまとめて追加(Add Multiple Columns After Another)

既存カラムの後ろに複数カラムを追加する際に、after() を使えます。

<?php

Schema::table('users', function (Blueprint $table) {
    $table->after('password', function (Blueprint $table) {
        $table->string('address_line1');
        $table->string('address_line2');
        $table->string('city');
    });
});

72. アクセサ結果のキャッシュ

計算コストの高いアクセサの場合、shouldCache() を使って結果をキャッシュできます。

<?php

protected function hash(): Attribute
{
    return Attribute::make(
        get: fn (string $value) => bcrypt(gzuncompress($value)),
    )->shouldCache();
}

73. whereLike メソッド

大文字小文字の区別を調整可能な LIKE クエリを簡潔に記述できます。

<?php

// 例:通常の LIKE
User::query()->whereLike('name', 'Jo%')->get();

// 大文字小文字を区別する場合
User::query()->whereLike('name', 'Jo%', caseSensitive: true)->get();

74. 複数IDと特定カラムでの find の使い方

find() に配列でIDを渡し、特定のカラムだけ取得できます。

<?php
User::find(id: [1, 2], columns: ['email']);

75. リレーションの件数を動的にロード(loadCount)

loadCount() を使えば、取得後にリレーションの件数を追加でロードできます。

<?php
$user = User::first();
$user->loadCount('posts');

76. カラムを先頭に移動する

first() を使うと、後から追加したカラムをテーブルの先頭に移動できます。
※大量データの場合、テーブルの再構築に時間がかかる可能性があります。

<?php

Schema::table('posts', function (Blueprint $table) {
    $table->string('uuid')->first();
});

77. 最新のレコードを取得(latestOfMany)

一対多リレーションで、最新の関連レコードを簡単に取得できます。

<?php

class User extends Model
{
    public function latestOrder()
    {
        return $this->hasOne(Order::class)->latestOfMany('created_at');
    }
}

$latestOrder = $user->latestOrder;

78. クリーンなネストされた eager loading の記法

ドット記法の代わりに、配列を使ってネストしたリレーションを指定できます。

<?php

$books = Book::with([
    'author' => [
        'contacts',
        'publisher'
    ]
])->get();

79. insertGetId メソッド

レコード挿入後に、新規挿入したレコードのIDを取得できます。

<?php

$id = DB::table('users')->insertGetId(
    ['email' => 'john@example.com', 'votes' => 0]
);

dd($id);

80. ddRawSql メソッド

SQL文とバインディング済みの状態をダンプし、デバッグを容易にします。

<?php

DB::table('users')->where('votes', '>', 100)->ddRawSql();

81. 重複クエリの回避

loadMissing() を使えば、既にロード済みのリレーションを再取得しないようにできます。

<?php

$users->loadMissing(['comments', 'posts']);

82. 新しい CollectedBy 属性

Laravel 11.28 以降、newCollection の代わりに CollectedBy 属性を使用してカスタムコレクションを指定できます。

<?php

use Illuminate\Database\Eloquent\Attributes\CollectedBy;

#[CollectedBy(PostCollection::class)]
class Post
{
    public function newCollection(array $models = [])
    {
        return new PostCollection($models);
    }
}

83. 集約関数

withSumwithMaxwithAvgwithMin を使って、リレーションの集計結果を取得できます。

<?php

Post::withSum('comments', 'votes')->first();
Post::withMax('comments', 'votes')->first();
Post::withAvg('comments', 'votes')->first();
Post::withMin('comments', 'votes')->first();

84. toggle メソッド

多対多リレーションで、指定したIDの関連をオンオフ切り替えできます。

<?php

// 例:like 機能の切り替え
$user->likes()->toggle($tweet->id);
$user->products()->toggle([1, 2, 3]);

85. 完全なクエリログの取得

enableQueryLog()getRawQueryLog() を使って、実行された全クエリとその実行時間を確認できます。

<?php

use Illuminate\Support\Facades\DB;

DB::enableQueryLog();

User::whereNotNull('email_verified_at')->get();

DB::getRawQueryLog();

86. 新しい rawColumn メソッド

生の SQL 文を直接書く代わりに、rawColumn() を使ってカラム定義ができます。

<?php

new class extends Migration {
    public function up()
    {
        Schema::create('table', function (Blueprint $table) {
            $table->id();
            $table->rawColumn('legacy_boolean', 'int(1)')->default(0);
            $table->index(['id', 'legacy_boolean']);
        });
    }
};

87. Eloquent クエリの EXPLAIN 実行

explain() をチェーンして、クエリの実行計画を確認できます。

<?php

User::query()
    ->where('email', 'john@example.com')
    ->explain()
    ->dd();

88. firstOrNew メソッド

条件に一致するレコードがなければ、インスタンスだけを生成(保存はしない)できます。

<?php

use App\Models\User;

User::firstOrCreate(['email' => 'name@example.com']);
User::firstOrNew(['email' => 'name@example.com']);

89. withWhereHas メソッド

リレーションの存在条件を絞り込んだ上で、同時に eager load できます。

<?php

User::query()
    ->withWhereHas('posts', fn (Builder $query) => $query->where('featured', true))
    ->get();

90. ピボットテーブルのカラム更新

updateExistingPivot() を使えば、既に存在するピボットレコードのカラムを更新できます。

<?php

$user = User::first();
$roleId = Role::value('id');

$user->roles()->updateExistingPivot($roleId, [
    'active' => false,
]);

91. 存在しない属性へのアクセス防止

preventAccessingMissingAttributes() を利用して、存在しない属性へアクセスした際に例外を発生させることができます。

<?php

use Illuminate\Database\Eloquent\Model;

Model::preventAccessingMissingAttributes(! app()->isProduction());

$user = User::select('name')->first();
$user->email; // 存在しないため例外が発生

92. incrementOrCreate メソッド

レコードが存在すれば指定カラムの値をインクリメントし、存在しなければ新規作成できます。

<?php

PromoCodeUsage::incrementOrCreate(
    attributes: ['code' => 'oussama-25-off'],
    column: 'usage_count',
    default: 1,
    step: 1,
    extra: ['last_used_at' => now()]
);

93. オープンな DB 接続の監視

db:monitor コマンドを使い、オープンなデータベース接続数を監視し、閾値を超えた場合にイベントを発火できます。

<?php

use Illuminate\Database\Events\DatabaseBusy;
use Illuminate\Support\Facades\Event;

public function boot(): void
{
    Event::listen(function (DatabaseBusy $event) {
        // 通知などの処理
    });
}

// cron で php artisan db:monitor --databases=mysql --max=100 を実行

元記事

https://github.com/OussamaMater/Laravel-Tips/blob/main/tips/eloquent-and-database.md

Discussion