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