ORM 基础
ORM(Object-Relational Mapping,对象关系映射)是一种将数据库表映射为 PHP 对象的技术。ORM 消除了手动编写 SQL 语句的需要,通过操作对象来间接操作数据库。现代 PHP 框架的 ORM(如 Laravel Eloquent、Doctrine、ThinkPHP ORM)都遵循 ActiveRecord 或 Data Mapper 模式。本节将系统介绍 ORM 的核心概念、设计模式和各种操作方式。
基础概念
什么是 ORM
ORM 的核心思想是:将数据库表映射为类,将行映射为对象实例,将列映射为对象属性。
数据库表 users → PHP 类 User
┌────┬──────────┬─────────┐
│ id │ name │ email │
├────┼──────────┼─────────┤
│ 1 │ Alice │ a@b.com │
│ 2 │ Bob │ b@c.com │
└────┴──────────┴─────────┘
↓ ORM 映射 ↓
class User {
public int $id;
public string $name;
public string $email;
}
$user = User::find(1); // SELECT * FROM users WHERE id = 1
$user->name; // AliceORM 模式对比
| 模式 | 代表 | 特点 |
|---|---|---|
| ActiveRecord | Laravel Eloquent、Yii AR | 模型即表,模型承担 CRUD |
| Data Mapper | Doctrine ORM | 模型是纯数据对象,由 Repository 操作 |
| Row Data Gateway | Zend_Db | 表网关模式 |
详细说明
1. 模型定义
php
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class User extends Model
{
// 关联的表名(默认为类名的蛇形复数)
protected $table = 'users';
// 可批量赋值的字段
protected $fillable = [
'name',
'email',
'password',
'is_active',
];
// 隐藏字段(序列化时不输出)
protected $hidden = [
'password',
'remember_token',
];
// 类型转换
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'is_active' => 'boolean',
'settings' => 'array',
'created_at' => 'datetime',
'updated_at' => 'datetime',
];
// 默认属性值
protected $attributes = [
'is_active' => true,
];
// 日期字段
protected $dates = ['created_at', 'updated_at', 'deleted_at'];
// 一对多关系
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
// 属于关系
public function department(): BelongsTo
{
return $this->belongsTo(Department::class);
}
// 多对多关系
public function roles()
{
return $this->belongsToMany(Role::class)->withTimestamps();
}
// 作用域
public function scopeActive($query)
{
return $query->where('is_active', true);
}
public function scopeRecent($query, int $days = 7)
{
return $query->where('created_at', '>=', now()->subDays($days));
}
}2. 基本 CRUD 操作
php
<?php
declare(strict_types=1);
use App\Models\User;
// ====== Create(创建)======
// 方式一:使用 create
$user = User::create([
'name' => 'Alice',
'email' => 'alice@example.com',
'password' => 'secret',
]);
// 方式二:实例化后保存
$user = new User();
$user->name = 'Bob';
$user->email = 'bob@example.com';
$user->password = 'secret';
$user->save();
// 方式三:firstOrCreate
$user = User::firstOrCreate(
['email' => 'alice@example.com'], // 查找条件
['name' => 'Alice', 'password' => 'secret'] // 不存在时创建的属性
);
// 方式四:firstOrNew
$user = User::firstOrNew(
['email' => 'alice@example.com'],
['name' => 'Alice']
);
$user->save();
// ====== Read(查询)======
// 通过主键查找
$user = User::find(1);
// 查找所有
$users = User::all();
// 条件查询
$activeUsers = User::where('is_active', true)->get();
// 链式查询
$users = User::where('is_active', true)
->where('created_at', '>=', '2024-01-01')
->orderBy('name', 'asc')
->limit(20)
->get();
// 使用作用域
$recentActiveUsers = User::active()->recent(30)->get();
// 分页
$users = User::active()->paginate(20);
// 获取单个结果
$user = User::where('email', 'alice@example.com')->first();
$user = User::where('email', 'alice@example.com')->firstOrFail();
// pluck(获取单列)
$names = User::pluck('name', 'id');
// 查找或失败
$user = User::findOrFail(42);
// 聚合函数
$count = User::where('is_active', true)->count();
$max = User::max('id');
$avg = User::avg('score');
$sum = User::sum('balance');
$exists = User::where('email', 'alice@example.com')->exists();
// ====== Update(更新)======
// 通过属性修改
$user = User::find(1);
$user->name = 'Updated Name';
$user->save();
// 批量更新
User::where('is_active', false)->update(['is_active' => true]);
User::where('department_id', 5)->increment('login_count');
User::where('id', 1)->decrement('balance', 100);
// ====== Delete(删除)======
// 通过实例删除
$user->delete();
// 通过主键删除
User::destroy(1);
User::destroy([1, 2, 3]);
// 条件删除
User::where('is_active', false)->delete();
// 软删除
// 需要在模型中使用 SoftDeletes trait
User::where('id', 1)->delete(); // deleted_at 字段被设置
User::onlyTrashed()->get(); // 仅查询已软删除的记录
User::withTrashed()->get(); // 包含已软删除的记录
User::where('id', 1)->restore(); // 恢复软删除的记录
User::where('id', 1)->forceDelete(); // 永久删除3. 关联关系
php
<?php
declare(strict_types=1);
use App\Models\User;
use App\Models\Post;
use App\Models\Comment;
// 一对一
$user = User::find(1);
$profile = $user->profile; // 返回 Profile 模型或 null
// 一对多
$user = User::find(1);
$posts = $user->posts; // 返回 Post Collection
// 创建关联
$user->posts()->create([
'title' => 'First Post',
'content' => 'Hello World',
]);
// 属于
$post = Post::find(1);
$user = $post->user; // 返回 User 模型
// 多对多
$user = User::find(1);
$roles = $user->roles; // 返回 Role Collection
$user->roles()->attach($roleId); // 添加关联
$user->roles()->detach($roleId); // 移除关联
$user->roles()->sync([$role1Id, $role2Id]); // 同步关联
// 预加载(解决 N+1 查询问题)
$users = User::with('posts', 'roles')->get();
foreach ($users as $user) {
$user->posts; // 不会产生额外查询
}
// 延迟预加载
$users = User::all();
$users->load('posts', 'roles');
// 嵌套预加载
$users = User::with('posts.comments')->get();
// 预加载计数
$users = User::withCount('posts')->get();4. 查询构建器
php
<?php
declare(strict_types=1);
use App\Models\User;
// where 子句
User::where('age', '>=', 18)->get();
User::where('name', 'like', '%Alice%')->get();
User::where('status', ['active', 'pending'])->get();
User::whereBetween('age', [18, 30])->get();
User::whereNotIn('role', ['admin', 'moderator'])->get();
User::whereNull('deleted_at')->get();
User::whereNotNull('email_verified_at')->get();
User::whereDate('created_at', '2024-01-01')->get();
// 排序
User::orderBy('name', 'asc')->get();
User::latest()->get(); // 按创建时间降序
User::inRandomOrder()->first(); // 随机
// 分组与聚合
User::selectRaw('department_id, COUNT(*) as count')
->groupBy('department_id')
->having('count', '>', 10)
->get();
// 子查询
User::whereIn('id', fn($q) => $q->select('user_id')
->from('posts')
->groupBy('user_id')
)->get();
// Join
User::join('departments', 'users.department_id', '=', 'departments.id')
->select('users.*', 'departments.name as dept_name')
->get();
// chunk 分块处理(大量数据处理)
User::chunk(200, function ($users) {
foreach ($users as $user) {
// 处理 200 条记录
}
});实战示例
场景一:复杂查询封装
php
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
public function scopePending($query): Builder
{
return $query->where('status', 'pending');
}
public function scopeCompleted($query): Builder
{
return $query->where('status', 'completed');
}
public function scopeByDateRange($query, string $from, string $to): Builder
{
return $query->whereBetween('created_at', [$from, $to]);
}
public function scopeWithTotal($query): Builder
{
return $query->withSum('items as total_amount', 'price * quantity');
}
// 组合作用域
public static function findPendingInLastWeek(): Builder
{
return static::pending()->byDateRange(
now()->subWeek()->toDateString(),
now()->toDateString()
)->withTotal();
}
}场景二:模型事件
php
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// 模型创建后
protected static function booted(): void
{
static::created(function (User $user) {
// 发送欢迎邮件
\App\Jobs\SendWelcomeEmail::dispatch($user->id);
});
static::updated(function (User $user) {
// 记录变更日志
});
}
// 单个模型事件
protected static function boot(): void
{
parent::boot();
static::creating(function (User $user) {
$user->api_token = bin2hex(random_bytes(32));
});
static::deleting(function (User $user) {
// 删除前检查
if ($user->is_admin) {
throw new \RuntimeException('Cannot delete admin user');
}
});
}
}注意事项
1. N+1 查询问题
php
<?php
// ❌ N+1 查询问题
$users = User::all(); // 1 次查询
foreach ($users as $user) {
echo $user->posts->count(); // N 次查询(每个用户 1 次)
}
// 总共 1 + N 次查询
// ✅ 使用预加载
$users = User::with('posts')->get(); // 1 次查询(JOIN 或额外查询)
foreach ($users as $user) {
echo $user->posts->count(); // 0 次额外查询
}
// 总共 2 次查询2. 大量数据的内存管理
php
<?php
// ❌ 内存溢出风险
$users = User::all(); // 加载所有记录到内存
// ✅ 使用 cursor 或 chunk
foreach (User::cursor() as $user) {
// 逐行读取,内存占用低
}
// ✅ 使用 chunk
User::chunk(100, function ($users) {
foreach ($users as $user) {
// 处理 100 条
}
});最佳实践
1. 使用模型工厂和填充
php
<?php
// database/factories/UserFactory.php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserFactory extends Factory
{
protected $model = User::class;
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'password' => 'password',
'is_active' => true,
];
}
}
// 使用
User::factory()->count(100)->create();2. 使用查询范围封装条件
php
<?php
// ✅ 推荐:使用作用域
class User extends Model
{
public function scopeActive($query) { return $query->where('is_active', true); }
}
$activeUsers = User::active()->get();
// ❌ 不推荐:到处重复条件
$users = User::where('is_active', true)->get();下一节
继续学习:Laravel 框架 — 了解如何在实际框架中使用 ORM。