Illuminate Database (Laravel Eloquent)

Illuminate DatabaseはLaravelフレームワークの一部として開発された「エレガントで表現力豊かなActive Record実装」として、PHPにおける最も人気の高いORMライブラリです。シンプルなActive Recordパターンを採用し、データベーステーブルとモデルクラスの間に直感的な対応関係を提供します。強力なクエリビルダーと組み合わせることで、SQLクエリを書くことなく複雑なデータベース操作を実現できます。リレーションシップ、マイグレーション、シーディング、ソフトデリートなど現代的なWeb開発に必要な機能を包括的に提供し、開発者の生産性を大幅に向上させています。

ORMPHPLaravelActive Recordクエリビルダーリレーション

GitHub概要

laravel/framework

The Laravel Framework.

スター33,844
ウォッチ958
フォーク11,444
作成日:2013年1月10日
言語:PHP
ライセンス:MIT License

トピックス

frameworklaravelphp

スター履歴

laravel/framework Star History
データ取得日時: 2025/7/19 09:31

ライブラリ

Illuminate Database (Laravel Eloquent)

概要

Illuminate DatabaseはLaravelフレームワークの一部として開発された「エレガントで表現力豊かなActive Record実装」として、PHPにおける最も人気の高いORMライブラリです。シンプルなActive Recordパターンを採用し、データベーステーブルとモデルクラスの間に直感的な対応関係を提供します。強力なクエリビルダーと組み合わせることで、SQLクエリを書くことなく複雑なデータベース操作を実現できます。リレーションシップ、マイグレーション、シーディング、ソフトデリートなど現代的なWeb開発に必要な機能を包括的に提供し、開発者の生産性を大幅に向上させています。

詳細

Illuminate Database 2025年版は、PHP 8.2以降に完全対応し、型ヒント、enum、属性などモダンPHPの機能を最大限活用したORMエクスペリエンスを提供します。Eloquentの美しい構文により、一対一、一対多、多対多などの複雑なリレーションシップを直感的に定義でき、遅延ローディングとEager Loadingの使い分けによってN+1問題を回避できます。クエリビルダーのフルエントインターフェースにより、SQLライクでありながらPHPらしい記述が可能で、データベース抽象化により複数のデータベースエンジン(MySQL、PostgreSQL、SQLite、SQL Server)で同一コードが動作します。

主な特徴

  • Active Recordパターン: モデル中心の直感的なデータ操作
  • リレーションシップ: 一対一、一対多、多対多の自然な表現
  • クエリビルダー: フルエントで表現力豊かなクエリ構築
  • マイグレーション: データベーススキーマのバージョン管理
  • Eager Loading: N+1問題の効率的な解決
  • ソフトデリート: 論理削除によるデータ保護

メリット・デメリット

メリット

  • 直感的で読みやすいコード記述が可能でチーム開発に優秀
  • Laravelエコシステムとの完璧な統合とコンベンション重視
  • 豊富なリレーションシップサポートと自動化された処理
  • 強力なマイグレーション・シーディング機能によるチーム協業
  • ソフトデリート、スコープ、アクセサ等の高度な機能
  • 充実したドキュメントと巨大なコミュニティ

デメリット

  • Laravelフレームワークへの依存でスタンドアロン使用が困難
  • Active Recordパターンの制約で大規模システムでの性能課題
  • 複雑なクエリでは生SQLが必要になる場合がある
  • エラーメッセージが分かりにくく、デバッグが困難な場合がある
  • メモリ使用量が多く、高負荷環境では最適化が必要
  • マジックメソッドによりIDEでの型推論が困難

参考ページ

書き方の例

セットアップ

# Laravelプロジェクトの作成
composer create-project laravel/laravel my-project
cd my-project

# またはスタンドアロンでの使用
composer require illuminate/database illuminate/events
<?php
// config/database.php または直接設定
use Illuminate\Database\Capsule\Manager as Capsule;

$capsule = new Capsule;

$capsule->addConnection([
    'driver'    => 'mysql',
    'host'      => 'localhost',
    'database'  => 'my_database',
    'username'  => 'username',
    'password'  => 'password',
    'charset'   => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci',
    'prefix'    => '',
]);

// イベントディスパッチャーを設定(Eloquentに必要)
$capsule->setEventDispatcher(new \Illuminate\Events\Dispatcher);

// Eloquentをグローバルに利用可能にする
$capsule->setAsGlobal();
$capsule->bootEloquent();

基本的な使い方

<?php

// マイグレーション例(database/migrations/create_users_table.php)
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->date('birth_date')->nullable();
            $table->enum('status', ['active', 'inactive', 'suspended'])->default('active');
            $table->json('preferences')->nullable();
            $table->rememberToken();
            $table->timestamps();
            $table->softDeletes();

            $table->index(['status', 'created_at']);
        });
    }

    public function down()
    {
        Schema::dropIfExists('users');
    }
}

// Eloquentモデル(app/Models/User.php)
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class User extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = [
        'name',
        'email',
        'password',
        'birth_date',
        'status',
        'preferences',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
        'birth_date' => 'date',
        'preferences' => 'array',
        'status' => UserStatus::class, // Enumキャスト
    ];

    // アクセサー(属性の取得時の変換)
    public function getNameAttribute($value)
    {
        return ucfirst($value);
    }

    // ミューテータ(属性の設定時の変換)
    public function setPasswordAttribute($value)
    {
        $this->attributes['password'] = bcrypt($value);
    }

    // 仮想属性
    public function getAgeAttribute()
    {
        return $this->birth_date?->diffInYears(now());
    }

    // スコープ(クエリの再利用可能なフィルタ)
    public function scopeActive($query)
    {
        return $query->where('status', UserStatus::Active);
    }

    public function scopeAdults($query)
    {
        return $query->whereRaw('DATEDIFF(CURDATE(), birth_date) >= ?', [365 * 18]);
    }

    public function scopeSearch($query, $term)
    {
        return $query->where('name', 'like', "%{$term}%")
                    ->orWhere('email', 'like', "%{$term}%");
    }
}

// Enumクラス(PHP 8.1以降)
enum UserStatus: string
{
    case Active = 'active';
    case Inactive = 'inactive';
    case Suspended = 'suspended';

    public function label(): string
    {
        return match($this) {
            self::Active => 'アクティブ',
            self::Inactive => '非アクティブ',
            self::Suspended => '停止中',
        };
    }
}

データ操作

<?php

// 基本的なCRUD操作
class UserService
{
    // 作成
    public function createUser(array $userData): User
    {
        return User::create([
            'name' => $userData['name'],
            'email' => $userData['email'],
            'password' => $userData['password'],
            'birth_date' => $userData['birth_date'] ?? null,
            'preferences' => $userData['preferences'] ?? [],
        ]);
    }

    // 読み取り
    public function findUser(int $id): ?User
    {
        return User::find($id);
    }

    public function findUserByEmail(string $email): ?User
    {
        return User::where('email', $email)->first();
    }

    // 更新
    public function updateUser(int $id, array $updates): bool
    {
        $user = User::find($id);
        if (!$user) {
            return false;
        }

        return $user->update($updates);
    }

    // 削除(ソフトデリート)
    public function deleteUser(int $id): bool
    {
        $user = User::find($id);
        if (!$user) {
            return false;
        }

        return $user->delete();
    }

    // 物理削除
    public function forceDeleteUser(int $id): bool
    {
        $user = User::withTrashed()->find($id);
        if (!$user) {
            return false;
        }

        return $user->forceDelete();
    }

    // 復元
    public function restoreUser(int $id): bool
    {
        $user = User::withTrashed()->find($id);
        if (!$user) {
            return false;
        }

        return $user->restore();
    }
}

// クエリビルダーを使った複雑な検索
class UserRepository
{
    public function getActiveUsers(int $limit = 10): Collection
    {
        return User::active()
                   ->orderBy('created_at', 'desc')
                   ->limit($limit)
                   ->get();
    }

    public function searchUsers(string $term, array $filters = []): Collection
    {
        $query = User::search($term);

        if (isset($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        if (isset($filters['age_min'])) {
            $query->whereRaw('DATEDIFF(CURDATE(), birth_date) >= ?', [365 * $filters['age_min']]);
        }

        if (isset($filters['age_max'])) {
            $query->whereRaw('DATEDIFF(CURDATE(), birth_date) <= ?', [365 * $filters['age_max']]);
        }

        if (isset($filters['created_after'])) {
            $query->where('created_at', '>=', $filters['created_after']);
        }

        return $query->orderBy('name')
                    ->paginate($filters['per_page'] ?? 15);
    }

    public function getUserStats(): array
    {
        return [
            'total' => User::count(),
            'active' => User::active()->count(),
            'new_this_month' => User::where('created_at', '>=', now()->startOfMonth())->count(),
            'adults' => User::adults()->count(),
            'by_status' => User::selectRaw('status, COUNT(*) as count')
                              ->groupBy('status')
                              ->pluck('count', 'status')
                              ->toArray(),
        ];
    }

    public function getBulkUsers(array $ids): Collection
    {
        return User::whereIn('id', $ids)->get();
    }

    public function updateBulkUsers(array $ids, array $updates): int
    {
        return User::whereIn('id', $ids)->update($updates);
    }
}

リレーションシップ

<?php

// Postモデル
class Post extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = [
        'title',
        'content',
        'status',
        'user_id',
        'category_id',
        'published_at',
    ];

    protected $casts = [
        'published_at' => 'datetime',
        'status' => PostStatus::class,
    ];

    // 多対一:投稿は一人のユーザーに属する
    public function user()
    {
        return $this->belongsTo(User::class);
    }

    // 多対一:投稿は一つのカテゴリに属する
    public function category()
    {
        return $this->belongsTo(Category::class);
    }

    // 一対多:投稿は複数のコメントを持つ
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }

    // 多対多:投稿は複数のタグを持つ
    public function tags()
    {
        return $this->belongsToMany(Tag::class)
                    ->withTimestamps()
                    ->withPivot('order');
    }

    // 一対一:投稿は一つの統計情報を持つ
    public function statistics()
    {
        return $this->hasOne(PostStatistics::class);
    }

    // スコープ
    public function scopePublished($query)
    {
        return $query->where('status', PostStatus::Published)
                    ->where('published_at', '<=', now());
    }

    public function scopeByCategory($query, $categoryId)
    {
        return $query->where('category_id', $categoryId);
    }
}

// Userモデルにリレーションシップを追加
class User extends Model
{
    // 既存のコード...

    // 一対多:ユーザーは複数の投稿を持つ
    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    // 一対多:ユーザーは複数のコメントを持つ
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }

    // 一対一:ユーザーは一つのプロフィールを持つ
    public function profile()
    {
        return $this->hasOne(UserProfile::class);
    }

    // 多対多:ユーザーはフォロー関係を持つ
    public function following()
    {
        return $this->belongsToMany(User::class, 'user_follows', 'follower_id', 'following_id')
                    ->withTimestamps();
    }

    public function followers()
    {
        return $this->belongsToMany(User::class, 'user_follows', 'following_id', 'follower_id')
                    ->withTimestamps();
    }

    // Has Many Through:ユーザーが投稿したコメント(投稿経由)
    public function postComments()
    {
        return $this->hasManyThrough(Comment::class, Post::class);
    }
}

// Category、Comment、Tag、UserProfileモデル
class Category extends Model
{
    protected $fillable = ['name', 'description', 'slug'];

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

class Comment extends Model
{
    use SoftDeletes;

    protected $fillable = ['content', 'user_id', 'post_id', 'parent_id'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function post()
    {
        return $this->belongsTo(Post::class);
    }

    // 自己参照関係:親コメント・子コメント
    public function parent()
    {
        return $this->belongsTo(Comment::class, 'parent_id');
    }

    public function children()
    {
        return $this->hasMany(Comment::class, 'parent_id');
    }
}

class Tag extends Model
{
    protected $fillable = ['name', 'slug'];

    public function posts()
    {
        return $this->belongsToMany(Post::class)
                    ->withTimestamps()
                    ->withPivot('order');
    }
}

class UserProfile extends Model
{
    protected $fillable = [
        'bio',
        'website',
        'location',
        'birth_date',
        'avatar_url',
    ];

    protected $casts = [
        'birth_date' => 'date',
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

// リレーションシップの使用例
class BlogService
{
    public function getUserWithPosts(int $userId): ?User
    {
        return User::with(['posts' => function ($query) {
            $query->published()->orderBy('published_at', 'desc');
        }])->find($userId);
    }

    public function getPostWithRelations(int $postId): ?Post
    {
        return Post::with([
            'user:id,name,email',
            'category:id,name',
            'tags:id,name',
            'comments.user:id,name',
            'statistics'
        ])->find($postId);
    }

    public function getPopularPosts(int $limit = 10): Collection
    {
        return Post::with(['user:id,name', 'category:id,name'])
                   ->published()
                   ->has('comments', '>=', 5)
                   ->orderByDesc('created_at')
                   ->limit($limit)
                   ->get();
    }

    public function createPostWithTags(array $postData, array $tagIds): Post
    {
        $post = Post::create($postData);
        $post->tags()->attach($tagIds);
        return $post->load('tags');
    }
}

トランザクションと高度な操作

<?php

use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Collection;

class AdvancedUserService
{
    // トランザクション処理
    public function transferUserPosts(int $fromUserId, int $toUserId): bool
    {
        return DB::transaction(function () use ($fromUserId, $toUserId) {
            $fromUser = User::findOrFail($fromUserId);
            $toUser = User::findOrFail($toUserId);

            // 投稿の移行
            Post::where('user_id', $fromUserId)
                ->update(['user_id' => $toUserId]);

            // コメントの移行
            Comment::where('user_id', $fromUserId)
                ->update(['user_id' => $toUserId]);

            // 統計の更新
            $fromUser->update(['posts_count' => 0]);
            $toUser->increment('posts_count', $fromUser->posts()->count());

            return true;
        });
    }

    // バッチ処理
    public function processInactiveUsers(): array
    {
        $result = [
            'processed' => 0,
            'deleted' => 0,
            'notified' => 0,
        ];

        // チャンクを使った大量データの効率的処理
        User::where('last_login_at', '<', now()->subMonths(6))
            ->where('status', UserStatus::Active)
            ->chunk(100, function (Collection $users) use (&$result) {
                foreach ($users as $user) {
                    $result['processed']++;

                    if ($user->last_login_at < now()->subYear()) {
                        // 1年以上ログインなしは削除
                        $user->delete();
                        $result['deleted']++;
                    } else {
                        // 6ヶ月以上ログインなしは通知
                        $this->sendInactiveNotification($user);
                        $result['notified']++;
                    }
                }
            });

        return $result;
    }

    // カスタムクエリ
    public function getUserEngagementStats(): Collection
    {
        return User::selectRaw('
                users.id,
                users.name,
                COUNT(DISTINCT posts.id) as posts_count,
                COUNT(DISTINCT comments.id) as comments_count,
                MAX(posts.created_at) as last_post_at,
                MAX(comments.created_at) as last_comment_at,
                AVG(post_statistics.views) as avg_post_views
            ')
            ->leftJoin('posts', 'users.id', '=', 'posts.user_id')
            ->leftJoin('comments', 'users.id', '=', 'comments.user_id')
            ->leftJoin('post_statistics', 'posts.id', '=', 'post_statistics.post_id')
            ->groupBy('users.id', 'users.name')
            ->having('posts_count', '>', 0)
            ->orderByDesc('posts_count')
            ->get();
    }

    // モデルイベントの活用
    public function setupUserEvents(): void
    {
        User::creating(function (User $user) {
            // ユーザー作成時に自動でプロフィールを作成
            if (!$user->profile) {
                $user->profile()->create([]);
            }
        });

        User::updated(function (User $user) {
            // ユーザー情報更新時にキャッシュをクリア
            Cache::forget("user.{$user->id}");
        });

        User::deleting(function (User $user) {
            // ユーザー削除時に関連データをクリーンアップ
            $user->posts()->delete();
            $user->comments()->delete();
            $user->profile()->delete();
        });
    }

    // 複雑な検索とフィルタリング
    public function advancedUserSearch(array $criteria): Collection
    {
        $query = User::query();

        // 動的フィルタの適用
        if (!empty($criteria['name'])) {
            $query->where('name', 'like', "%{$criteria['name']}%");
        }

        if (!empty($criteria['email'])) {
            $query->where('email', 'like', "%{$criteria['email']}%");
        }

        if (!empty($criteria['status'])) {
            $query->whereIn('status', (array) $criteria['status']);
        }

        if (!empty($criteria['age_range'])) {
            [$min, $max] = $criteria['age_range'];
            $query->whereRaw('DATEDIFF(CURDATE(), birth_date) BETWEEN ? AND ?', 
                            [365 * $min, 365 * $max]);
        }

        if (!empty($criteria['posts_count_min'])) {
            $query->has('posts', '>=', $criteria['posts_count_min']);
        }

        if (!empty($criteria['joined_after'])) {
            $query->where('created_at', '>=', $criteria['joined_after']);
        }

        if (!empty($criteria['has_profile'])) {
            if ($criteria['has_profile']) {
                $query->has('profile');
            } else {
                $query->doesntHave('profile');
            }
        }

        // ソート
        $sortBy = $criteria['sort_by'] ?? 'created_at';
        $sortOrder = $criteria['sort_order'] ?? 'desc';
        $query->orderBy($sortBy, $sortOrder);

        // リレーションの事前読み込み
        $with = $criteria['with'] ?? [];
        if (!empty($with)) {
            $query->with($with);
        }

        return $query->get();
    }

    // ファクトリとシーディングの活用
    public function createTestData(): array
    {
        return DB::transaction(function () {
            // ユーザーを10人作成
            $users = User::factory(10)->create();

            // 各ユーザーに投稿を3-7個ずつ作成
            $users->each(function (User $user) {
                Post::factory(rand(3, 7))->create(['user_id' => $user->id]);
            });

            // カテゴリとタグを作成
            $categories = Category::factory(5)->create();
            $tags = Tag::factory(20)->create();

            // 投稿にランダムにタグを割り当て
            Post::all()->each(function (Post $post) use ($tags) {
                $post->tags()->attach(
                    $tags->random(rand(1, 4))->pluck('id')
                );
            });

            return [
                'users' => $users->count(),
                'posts' => Post::count(),
                'categories' => $categories->count(),
                'tags' => $tags->count(),
            ];
        });
    }

    private function sendInactiveNotification(User $user): void
    {
        // 通知ロジック(実装は省略)
        \Log::info("Sent inactive notification to user {$user->id}");
    }
}

エラーハンドリング

<?php

use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\QueryException;
use Illuminate\Validation\ValidationException;

class RobustUserService
{
    public function safeCreateUser(array $userData): array
    {
        try {
            DB::beginTransaction();

            // バリデーション
            $this->validateUserData($userData);

            // ユーザー作成
            $user = User::create($userData);

            // プロフィール作成
            $user->profile()->create([
                'bio' => $userData['bio'] ?? '',
                'location' => $userData['location'] ?? '',
            ]);

            // 初期設定
            $this->setupInitialUserSettings($user);

            DB::commit();

            return [
                'success' => true,
                'user' => $user->load('profile'),
                'message' => 'User created successfully'
            ];

        } catch (ValidationException $e) {
            DB::rollBack();
            return [
                'success' => false,
                'errors' => $e->errors(),
                'message' => 'Validation failed'
            ];

        } catch (QueryException $e) {
            DB::rollBack();
            
            if ($e->getCode() === '23000') { // 重複エラー
                return [
                    'success' => false,
                    'errors' => ['email' => ['Email already exists']],
                    'message' => 'Email already exists'
                ];
            }

            \Log::error('Database error in user creation', [
                'exception' => $e,
                'userData' => $userData
            ]);

            return [
                'success' => false,
                'errors' => ['database' => ['Database error occurred']],
                'message' => 'Database error'
            ];

        } catch (\Exception $e) {
            DB::rollBack();
            
            \Log::error('Unexpected error in user creation', [
                'exception' => $e,
                'userData' => $userData
            ]);

            return [
                'success' => false,
                'errors' => ['system' => ['System error occurred']],
                'message' => 'System error'
            ];
        }
    }

    public function safeUpdateUser(int $userId, array $updates): array
    {
        try {
            $user = User::findOrFail($userId);

            // 変更権限チェック
            if (!$this->canUpdateUser($user)) {
                return [
                    'success' => false,
                    'errors' => ['permission' => ['Permission denied']],
                    'message' => 'Permission denied'
                ];
            }

            // バリデーション
            $this->validateUserUpdateData($updates, $user);

            DB::beginTransaction();

            $user->update($updates);

            // 関連データの更新
            if (isset($updates['profile'])) {
                $user->profile()->updateOrCreate([], $updates['profile']);
            }

            DB::commit();

            return [
                'success' => true,
                'user' => $user->fresh('profile'),
                'message' => 'User updated successfully'
            ];

        } catch (ModelNotFoundException $e) {
            return [
                'success' => false,
                'errors' => ['user' => ['User not found']],
                'message' => 'User not found'
            ];

        } catch (ValidationException $e) {
            DB::rollBack();
            return [
                'success' => false,
                'errors' => $e->errors(),
                'message' => 'Validation failed'
            ];

        } catch (\Exception $e) {
            DB::rollBack();
            
            \Log::error('Error updating user', [
                'userId' => $userId,
                'updates' => $updates,
                'exception' => $e
            ]);

            return [
                'success' => false,
                'errors' => ['system' => ['Update failed']],
                'message' => 'Update failed'
            ];
        }
    }

    public function safeBulkOperation(array $userIds, callable $operation): array
    {
        $results = [
            'success' => [],
            'failed' => [],
            'total' => count($userIds)
        ];

        foreach ($userIds as $userId) {
            try {
                $result = $operation($userId);
                $results['success'][] = [
                    'id' => $userId,
                    'result' => $result
                ];
            } catch (\Exception $e) {
                $results['failed'][] = [
                    'id' => $userId,
                    'error' => $e->getMessage()
                ];
                
                \Log::error('Bulk operation failed for user', [
                    'userId' => $userId,
                    'exception' => $e
                ]);
            }
        }

        return $results;
    }

    private function validateUserData(array $data): void
    {
        $validator = validator($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|email|unique:users,email',
            'password' => 'required|string|min:8',
            'birth_date' => 'nullable|date|before:today',
        ]);

        if ($validator->fails()) {
            throw new ValidationException($validator);
        }
    }

    private function validateUserUpdateData(array $data, User $user): void
    {
        $validator = validator($data, [
            'name' => 'sometimes|string|max:255',
            'email' => 'sometimes|email|unique:users,email,' . $user->id,
            'birth_date' => 'sometimes|nullable|date|before:today',
        ]);

        if ($validator->fails()) {
            throw new ValidationException($validator);
        }
    }

    private function canUpdateUser(User $user): bool
    {
        // 権限チェックロジック(例)
        return auth()->user()->can('update', $user);
    }

    private function setupInitialUserSettings(User $user): void
    {
        // 初期設定ロジック
        $user->update([
            'preferences' => [
                'notifications' => true,
                'newsletter' => false,
                'theme' => 'light'
            ]
        ]);
    }
}