目录

ORM 使用指南

最后更新: 2026-01-27 11:02:47

ORM 使用指南

Unicode Framework 提供了功能完整的 ActiveRecord 风格 ORM,支持模型定义、查询、关联、事件、作用域等高级特性。本文档是 ORM 的完整使用指南。

快速开始

创建第一个模型

<?php
namespace App\Index\Model;

use Unicode\Framework\Database\Model;

class User extends Model { // 模型会自动映射到 users 表 }

基本使用

// 查找用户
$user = User::find(1);

// 创建用户 $user = User::create([ 'name' => 'John Doe', 'email' => 'john@example.com', ]);

// 更新用户 $user->name = 'Jane Doe'; $user->save();

// 删除用户 $user->delete();

模型定义

基本模型类

<?php
namespace App\Index\Model;

use Unicode\Framework\Database\Model;

class User extends Model { // 所有配置都是可选的,框架会使用合理的默认值 }

表名配置

class User extends Model
{
    // 方式 1:显式指定表名
    protected static ?string $table = 'users';

// 方式 2:不指定,框架自动推断 // 类名 User -> 表名 users // 类名 UserProfile -> 表名 user_profiles }

表名推断规则
  • 将类名从驼峰转换为下划线:UserProfileuser_profile
  • 自动复数化:user_profileuser_profiles
  • 主键配置

    class User extends Model
    {
        // 指定主键字段名(默认为 'id')
        protected static string $primaryKey = 'id';
    

    // 使用其他主键 protected static string $primaryKey = 'user_id'; }

    完整模型示例

    <?php
    namespace App\Index\Model;
    

    use Unicode\Framework\Database\Model; use Unicode\Framework\Database\Relations\HasOne; use Unicode\Framework\Database\Relations\HasMany; use Unicode\Framework\Database\Relations\BelongsTo;

    class User extends Model { // 表名(可选) protected static ?string $table = 'users';

    // 主键(可选,默认为 'id') protected static string $primaryKey = 'id';

    // 时间戳配置 protected static bool $timestamps = true; protected static string $createdAt = 'created_at'; protected static string $updatedAt = 'updated_at';

    // 批量赋值保护 protected static array $fillable = [ 'name', 'email', 'password', ];

    protected static array $guarded = [];

    // 软删除 protected static bool $softDelete = false; protected static string $deleteTime = 'deleted_at';

    // 字段类型转换 protected static array $casts = [ 'is_active' => 'boolean', 'settings' => 'json', 'created_at' => 'datetime', 'score' => 'integer', ];

    // 关联关系 public function profile(): HasOne { return $this->hasOne(Profile::class, 'user_id'); }

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

    查询数据

    查找单条记录

    #### find() - 根据主键查找

    // 根据主键查找
    $user = User::find(1);
    

    // 如果不存在,返回 null if ($user === null) { // 用户不存在 }

    // 查找多个主键 $users = []; foreach ([1, 2, 3] as $id) { $user = User::find($id); if ($user !== null) { $users[] = $user; } }

    #### first() - 查找第一条记录

    // 查找第一条记录
    $user = User::first();
    

    // 条件查找第一条 $user = User::where('email', 'user@example.com')->first();

    // 排序后查找第一条 $user = User::orderBy('created_at', 'desc')->first();

    #### 条件查找

    // 单条件
    $user = User::where('email', 'user@example.com')->first();
    

    // 多条件 $user = User::where('email', 'user@example.com') ->where('status', 'active') ->first();

    // 复杂条件 $user = User::where('age', '>', 18) ->where('status', 'active') ->orWhere('is_vip', true) ->first();

    查找多条记录

    #### all() - 查找所有记录

    // 查找所有记录
    $users = User::all();
    

    // ⚠️ 注意:如果数据量大,建议使用分页 // 推荐:使用查询构建器限制数量 $users = User::query()->limit(100)->get();

    #### get() - 条件查询多条

    // 基本查询
    $users = User::where('status', 'active')->get();
    

    // 复杂查询 $users = User::query() ->where('status', 'active') ->where('age', '>=', 18) ->orderBy('created_at', 'desc') ->limit(100) ->get();

    // 查询指定字段 $users = User::query() ->select(['id', 'name', 'email']) ->get();

    查询构建器方法

    模型可以使用所有查询构建器的方法:

    // WHERE 条件
    $users = User::where('status', 'active')->get();
    $users = User::where('age', '>', 18)->get();
    $users = User::whereIn('id', [1, 2, 3])->get();
    $users = User::whereNull('deleted_at')->get();
    

    // 排序 $users = User::orderBy('created_at', 'desc')->get(); $users = User::orderBy('name', 'asc')->orderBy('id', 'desc')->get();

    // 限制和偏移 $users = User::limit(10)->get(); $users = User::offset(10)->limit(10)->get();

    // 分组 $stats = User::query() ->select('status', db()->raw('COUNT(*) as count')) ->groupBy('status') ->get();

    聚合查询

    // 计数
    $count = User::count();
    $activeCount = User::where('status', 'active')->count();
    

    // 最大值 $maxAge = User::max('age');

    // 最小值 $minAge = User::min('age');

    // 平均值 $avgAge = User::avg('age');

    // 求和 $totalScore = User::sum('score');

    查询单个值

    // 获取单个字段值
    $name = User::where('id', 1)->value('name');
    

    // 获取单个字段值(带默认值) $name = User::where('id', 999)->value('name', 'Unknown');

    判断记录是否存在

    // 检查记录是否存在
    $exists = User::where('email', 'user@example.com')->exists();
    

    // 在条件判断中使用 if (User::where('email', $email)->exists()) { // 邮箱已存在 }

    分页查询

    // 使用 limit 和 offset
    $page = 1;
    $perPage = 20;
    $users = User::query()
        ->offset(($page - 1) * $perPage)
        ->limit($perPage)
        ->get();
    

    // 获取总数 $total = User::count();

    创建数据

    create() - 批量创建

    // 基本创建
    $user = User::create([
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'password' => password_hash('password', PASSWORD_DEFAULT),
    ]);
    

    // 创建后自动获取 ID echo $user->id; // 自动填充主键

    new + save() - 手动创建

    // 方式 1:使用 new 和属性赋值
    $user = new User();
    $user->name = 'John Doe';
    $user->email = 'john@example.com';
    $user->save();
    

    // 方式 2:使用 new 和 fill $user = new User(); $user->fill([ 'name' => 'John Doe', 'email' => 'john@example.com', ]); $user->save();

    fill() - 批量赋值

    $user = new User();
    $user->fill([
        'name' => 'John Doe',
        'email' => 'john@example.com',
        // 只有 fillable 中的字段会被赋值
    ]);
    $user->save();
    

    创建时的注意事项

    // ✅ 推荐:使用 fillable 保护敏感字段
    class User extends Model
    {
        protected static array $fillable = [
            'name',
            'email',
            // 不包含 password、is_admin 等敏感字段
        ];
    }
    

    // 创建时,敏感字段会被忽略 $user = User::create([ 'name' => 'John', 'email' => 'john@example.com', 'is_admin' => true, // 会被忽略(不在 fillable 中) ]);

    更新数据

    save() - 保存模型

    // 查找后更新
    $user = User::find(1);
    $user->name = 'Jane Doe';
    $user->email = 'jane@example.com';
    $user->save();
    

    // 只更新变更的字段 $user = User::find(1); $user->name = 'New Name'; // 只有 name 变更 $user->save(); // 只更新 name 字段

    update() - 批量更新

    // 方式 1:使用 update 方法
    $user = User::find(1);
    $user->update([
        'name' => 'Jane Doe',
        'email' => 'jane@example.com',
    ]);
    

    // 方式 2:使用查询构建器批量更新 User::where('status', 'pending') ->update(['status' => 'active']);

    检查变更

    $user = User::find(1);
    

    // 检查是否有变更 if ($user->isDirty()) { $dirty = $user->getDirty(); // 获取变更的字段 echo "Changed fields: " . implode(', ', array_keys($dirty)); $user->save(); }

    // 检查特定字段是否变更 if ($user->isDirty('name')) { echo "Name has been changed"; }

    // 获取原始值 $originalName = $user->getOriginal('name');

    增量/减量更新

    // 使用查询构建器
    User::where('id', 1)->increment('score', 10);
    User::where('id', 1)->decrement('score', 5);
    

    // 带其他字段更新 User::where('id', 1)->increment('score', 10, [ 'updated_at' => date('Y-m-d H:i:s'), ]);

    删除数据

    delete() - 删除模型

    // 方式 1:删除模型实例
    $user = User::find(1);
    $user->delete();
    

    // 方式 2:批量删除 User::where('status', 'inactive')->delete();

    // 方式 3:根据主键删除 User::destroy(1); User::destroy([1, 2, 3]);

    软删除

    如果启用了软删除,delete() 会标记删除而不是真正删除:

    class User extends Model
    {
        protected static bool $softDelete = true;
        protected static string $deleteTime = 'deleted_at';
    }
    

    // 软删除 $user = User::find(1); $user->delete(); // 设置 deleted_at 字段,不真正删除

    // 强制删除(真正删除) $user->delete(true); // 或 $user->forceDelete();

    恢复软删除

    // 恢复软删除的记录
    $user = User::onlyTrashed()->where('id', 1)->first();
    if ($user !== null) {
        $user->restore(); // 清除 deleted_at
    }
    

    查询软删除的记录

    // 排除软删除(默认)
    $users = User::all(); // 不包含已删除的记录
    

    // 包含软删除 $users = User::withTrashed()->get();

    // 只查询软删除 $users = User::onlyTrashed()->get();

    模型关联

    一对一(HasOne)

    #### 定义关联

    class User extends Model
    {
        public function profile(): HasOne
        {
            return $this->hasOne(Profile::class, 'user_id');
        }
    

    // 指定外键和本地键 public function profile(): HasOne { return $this->hasOne(Profile::class, 'user_id', 'id'); } }

    #### 使用关联

    // 获取关联
    $user = User::find(1);
    $profile = $user->profile; // 自动查询关联的 Profile
    

    // 检查关联是否存在 if ($user->profile !== null) { echo $user->profile->bio; }

    #### 创建关联记录

    $user = User::find(1);
    

    // 方式 1:通过关联创建 $profile = $user->profile()->create([ 'bio' => 'User bio', 'avatar' => 'avatar.jpg', ]);

    // 方式 2:手动创建并关联 $profile = new Profile(['bio' => 'User bio']); $user->profile()->save($profile);

    一对多(HasMany)

    #### 定义关联

    class User extends Model
    {
        public function posts(): HasMany
        {
            return $this->hasMany(Post::class, 'user_id');
        }
    

    // 指定外键和本地键 public function posts(): HasMany { return $this->hasMany(Post::class, 'user_id', 'id'); } }

    #### 使用关联

    // 获取关联集合
    $user = User::find(1);
    $posts = $user->posts; // 返回 Post 模型数组
    

    // 遍历关联 foreach ($user->posts as $post) { echo $post->title; }

    // 条件查询关联 $publishedPosts = $user->posts() ->where('status', 'published') ->get();

    #### 创建关联记录

    $user = User::find(1);
    

    // 创建关联记录 $post = $user->posts()->create([ 'title' => 'New Post', 'content' => 'Post content', ]);

    // 批量创建 $user->posts()->createMany([ ['title' => 'Post 1', 'content' => 'Content 1'], ['title' => 'Post 2', 'content' => 'Content 2'], ]);

    多对一(BelongsTo)

    #### 定义关联

    class Post extends Model
    {
        public function user(): BelongsTo
        {
            return $this->belongsTo(User::class, 'user_id');
        }
    

    public function category(): BelongsTo { return $this->belongsTo(Category::class, 'category_id'); }

    // 指定外键和关联键 public function author(): BelongsTo { return $this->belongsTo(User::class, 'author_id', 'id'); } }

    #### 使用关联

    // 获取关联
    $post = Post::find(1);
    $user = $post->user; // 自动查询关联的 User
    $category = $post->category;
    

    // 关联可能为 null if ($post->category !== null) { echo $post->category->name; }

    #### 关联查询

    // 通过关联查询
    $posts = Post::whereHas('user', function($query) {
        $query->where('status', 'active');
    })->get();
    

    // 关联计数 $userCount = Post::withCount('user')->get();

    预加载关联(Eager Loading)

    预加载可以避免 N+1 查询问题:

    // ❌ 错误:N+1 查询
    $users = User::all();
    foreach ($users as $user) {
        foreach ($user->posts as $post) {
            // 每次循环都查询数据库
        }
    }
    

    // ✅ 正确:预加载关联 $users = User::with('posts')->get(); foreach ($users as $user) { foreach ($user->posts as $post) { // 已预加载,不查询数据库 } }

    // 预加载多个关联 $users = User::with(['posts', 'profile'])->get();

    // 条件预加载 $users = User::with(['posts' => function($query) { $query->where('status', 'published'); }])->get();

    // 嵌套预加载 $users = User::with(['posts.category'])->get();

    关联查询方法

    // 检查关联是否存在
    $hasPosts = $user->posts()->exists();
    

    // 关联计数 $postCount = $user->posts()->count();

    // 关联查询 $recentPosts = $user->posts() ->where('created_at', '>', date('Y-m-d', strtotime('-7 days'))) ->orderBy('created_at', 'desc') ->get();

    字段类型转换

    定义类型转换

    class User extends Model
    {
        protected static array $casts = [
            'is_active' => 'boolean',
            'is_vip' => 'boolean',
            'score' => 'integer',
            'price' => 'float',
            'settings' => 'json',
            'metadata' => 'array',
            'created_at' => 'datetime',
            'updated_at' => 'datetime',
        ];
    }
    

    支持的转换类型

    | 类型 | 说明 | 示例 | | ------------------- | -------------- | --------------------------------- | | boolean / bool | 布尔值 | true / false | | integer / int | 整数 | 123 | | float / double | 浮点数 | 123.45 | | string | 字符串 | "text" | | json | JSON 数组/对象 | ["a", "b"] / {"key": "value"} | | array | 数组 | ["a", "b"] | | object | 对象 | {"key": "value"} | | datetime / date | 日期时间 | "2024-01-01 12:00:00" | | timestamp | Unix 时间戳 | 1704067200 |

    使用示例

    class User extends Model
    {
        protected static array $casts = [
            'settings' => 'json',
            'is_vip' => 'boolean',
            'score' => 'integer',
        ];
    }
    

    $user = User::find(1);

    // 自动类型转换 $settings = $user->settings; // 自动转换为数组 $isVip = $user->is_vip; // 自动转换为布尔值 $score = $user->score; // 自动转换为整数

    // 设置时自动转换 $user->settings = ['theme' => 'dark', 'lang' => 'zh']; $user->save(); // 自动转换为 JSON 字符串存储

    // JSON 字段 $user->settings = [ 'theme' => 'dark', 'notifications' => ['email' => true, 'sms' => false], ]; $user->save();

    访问器和修改器

    访问器(Accessors)

    访问器允许您在获取属性时进行转换:

    class User extends Model
    {
        // 访问器命名:get{FieldName}Attribute
        public function getFullNameAttribute(): string
        {
            return $this->first_name . ' ' . $this->last_name;
        }
    

    public function getAvatarUrlAttribute(): string { if ($this->avatar) { return '/storage/avatars/' . $this->avatar; } return '/images/default-avatar.png'; }

    public function getIsAdultAttribute(): bool { return $this->age >= 18; } }

    // 使用访问器 $user = User::find(1); echo $user->full_name; // 自动调用 getFullNameAttribute echo $user->avatar_url; // 自动调用 getAvatarUrlAttribute echo $user->is_adult ? 'Adult' : 'Minor';

    修改器(Mutators)

    修改器允许您在设置属性时进行转换:

    class User extends Model
    {
        // 修改器命名:set{FieldName}Attribute
        public function setPasswordAttribute(string $value): void
        {
            $this->attributes['password'] = password_hash($value, PASSWORD_DEFAULT);
        }
    

    public function setEmailAttribute(string $value): void { $this->attributes['email'] = strtolower(trim($value)); }

    public function setNameAttribute(string $value): void { $this->attributes['name'] = ucwords(strtolower($value)); } }

    // 使用修改器 $user = new User(); $user->password = 'plaintext'; // 自动加密 $user->email = ' JOHN@EXAMPLE.COM '; // 自动转换为 'john@example.com' $user->name = 'john doe'; // 自动转换为 'John Doe' $user->save();

    访问器和类型转换结合

    class User extends Model
    {
        protected static array $casts = [
            'settings' => 'json',
        ];
    

    // 访问器可以进一步处理类型转换后的值 public function getThemeAttribute(): string { $settings = $this->settings ?? []; return $settings['theme'] ?? 'light'; }

    // 修改器可以处理设置前的值 public function setSettingsAttribute(array $value): void { // 在存储前进行验证或处理 $this->attributes['settings'] = json_encode($value, JSON_UNESCAPED_UNICODE); } }

    批量赋值保护

    fillable(白名单)

    使用 fillable 定义允许批量赋值的字段:

    class User extends Model
    {
        protected static array $fillable = [
            'name',
            'email',
            'password',
        ];
    }
    

    // 只有 fillable 中的字段可以被批量赋值 $user = User::create([ 'name' => 'John', 'email' => 'john@example.com', 'is_admin' => true, // 这个字段会被忽略(不在 fillable 中) ]);

    guarded(黑名单)

    使用 guarded 定义禁止批量赋值的字段:

    class User extends Model
    {
        protected static array $fillable = [];
        protected static array $guarded = ['id', 'is_admin', 'created_at'];
    }
    

    // guarded 中的字段不能被批量赋值 $user = User::create([ 'name' => 'John', 'email' => 'john@example.com', 'is_admin' => true, // 这个字段会被忽略(在 guarded 中) ]);

    完全禁止批量赋值

    class User extends Model
    {
        protected static array $fillable = [];
        protected static array $guarded = ['*']; // 禁止所有字段
    }
    

    // 必须手动设置每个字段 $user = new User(); $user->name = 'John'; $user->email = 'john@example.com'; $user->save();

    fillable 和 guarded 的优先级

  • 如果 fillable 不为空,只允许 fillable 中的字段
  • 如果 fillable 为空且 guarded 不为空且不包含 '*',排除 guarded 中的字段
  • 如果 guarded 包含 '*',禁止所有批量赋值
  • 推荐配置

    // ✅ 推荐:使用 fillable(白名单更安全)
    class User extends Model
    {
        protected static array $fillable = [
            'name',
            'email',
            'password',
            // 明确列出允许的字段
        ];
    }
    

    // ❌ 不推荐:使用 guarded(容易遗漏) class User extends Model { protected static array $guarded = ['id', 'is_admin']; // 如果新增敏感字段,可能忘记添加到 guarded }

    软删除

    启用软删除

    class User extends Model
    {
        protected static bool $softDelete = true;
        protected static string $deleteTime = 'deleted_at';
    }
    

    软删除操作

    // 软删除(设置 deleted_at)
    $user = User::find(1);
    $user->delete(); // 设置 deleted_at,不真正删除
    

    // 强制删除(真正删除) $user->delete(true); // 或 $user->forceDelete();

    查询软删除的记录

    // 排除软删除(默认行为)
    $users = User::all(); // 不包含已删除的记录
    $user = User::find(1); // 如果已软删除,返回 null
    

    // 包含软删除 $users = User::withTrashed()->get(); $user = User::withTrashed()->find(1); // 即使已软删除也能找到

    // 只查询软删除 $users = User::onlyTrashed()->get(); $user = User::onlyTrashed()->where('id', 1)->first();

    恢复软删除

    // 恢复单个记录
    $user = User::onlyTrashed()->where('id', 1)->first();
    if ($user !== null) {
        $user->restore(); // 清除 deleted_at
    }
    

    // 批量恢复 User::onlyTrashed() ->where('deleted_at', '>', date('Y-m-d', strtotime('-30 days'))) ->get() ->each(function($user) { $user->restore(); });

    软删除的最佳实践

    // ✅ 推荐:对需要保留历史的数据使用软删除
    class Order extends Model
    {
        protected static bool $softDelete = true;
        protected static string $deleteTime = 'deleted_at';
    }
    

    // ✅ 推荐:对临时数据不使用软删除 class Cache extends Model { protected static bool $softDelete = false; }

    // ✅ 推荐:定期清理过期的软删除记录 class CleanupTask { public function cleanupOldSoftDeletes(): void { $cutoffDate = date('Y-m-d', strtotime('-90 days')); User::onlyTrashed() ->where('deleted_at', '<', $cutoffDate) ->get() ->each(function($user) { $user->forceDelete(); // 真正删除 }); } }

    时间戳

    自动维护时间戳

    class User extends Model
    {
        // 启用时间戳(默认)
        protected static bool $timestamps = true;
        protected static string $createdAt = 'created_at';
        protected static string $updatedAt = 'updated_at';
    }
    

    时间戳行为

    // 创建时自动设置 created_at 和 updated_at
    $user = User::create(['name' => 'John']);
    // created_at 和 updated_at 自动设置为当前时间
    

    // 更新时自动更新 updated_at $user->name = 'Jane'; $user->save(); // updated_at 自动更新为当前时间

    // 手动设置时间戳 $user = new User(); $user->name = 'John'; $user->created_at = '2024-01-01 00:00:00'; $user->updated_at = '2024-01-01 00:00:00'; $user->save();

    禁用时间戳

    // 全局禁用
    class User extends Model
    {
        protected static bool $timestamps = false;
    }
    

    // 单次操作禁用 $user = User::find(1); $user->timestamps = false; // 注意:这不是标准方法,需要检查实现 $user->save();

    自定义时间戳字段名

    class User extends Model
    {
        protected static string $createdAt = 'create_time';
        protected static string $updatedAt = 'update_time';
    }
    

    模型事件

    模型提供了生命周期事件钩子:

    beforeSave() - 保存前

    class User extends Model
    {
        protected function beforeSave(): bool
        {
            // 保存前的逻辑
            if (empty($this->name)) {
                return false; // 返回 false 取消保存
            }
    

    // 自动设置某些字段 if (!$this->exists) { $this->activation_token = bin2hex(random_bytes(32)); }

    return true; // 返回 true 继续保存 } }

    afterSave() - 保存后

    class User extends Model
    {
        protected function afterSave(): void
        {
            // 保存后的逻辑
            if (!$this->exists) {
                // 新创建的用户,发送欢迎邮件
                $this->sendWelcomeEmail();
            } else {
                // 更新的用户,记录变更日志
                $this->logChanges();
            }
        }
    

    private function sendWelcomeEmail(): void { // 发送邮件逻辑 }

    private function logChanges(): void { // 记录变更日志 } }

    beforeDelete() - 删除前

    class User extends Model
    {
        protected function beforeDelete(): bool
        {
            // 删除前的检查
            if ($this->hasActiveOrders()) {
                return false; // 有活跃订单,不允许删除
            }
    

    // 清理关联数据 $this->posts()->delete(); $this->profile()->delete();

    return true; }

    private function hasActiveOrders(): bool { return db('orders') ->where('user_id', $this->id) ->where('status', 'active') ->exists(); } }

    afterDelete() - 删除后

    class User extends Model
    {
        protected function afterDelete(): void
        {
            // 删除后的清理工作
            // 删除用户上传的文件
            $this->deleteUserFiles();
    

    // 记录删除日志 Log::info('User deleted', ['user_id' => $this->id]); }

    private function deleteUserFiles(): void { // 删除文件逻辑 } }

    事件使用示例

    class Order extends Model
    {
        protected function beforeSave(): bool
        {
            // 自动计算总价
            if ($this->isDirty('items')) {
                $this->total = $this->calculateTotal();
            }
    

    // 验证库存 if (!$this->validateStock()) { return false; }

    return true; }

    protected function afterSave(): void { // 更新库存 if ($this->wasRecentlyCreated) { $this->updateStock(); }

    // 发送通知 $this->sendNotification(); }

    protected function beforeDelete(): bool { // 检查是否可以删除 if ($this->status === 'shipped') { return false; // 已发货的订单不能删除 }

    return true; }

    protected function afterDelete(): void { // 恢复库存 $this->restoreStock();

    // 记录删除日志 Log::info('Order deleted', ['order_id' => $this->id]); } }

    查询作用域

    查询作用域允许您封装常用的查询逻辑:

    定义作用域

    class User extends Model
    {
        // 作用域方法命名:scope{ScopeName}
        public static function scopeActive($query)
        {
            return $query->where('status', 'active');
        }
    

    public static function scopeOlderThan($query, int $age) { return $query->where('age', '>', $age); }

    public static function scopeVerified($query) { return $query->whereNotNull('email_verified_at'); }

    public static function scopeInRole($query, string $role) { return $query->where('role', $role); } }

    使用作用域

    // 单个作用域
    $activeUsers = User::active()->get();
    

    // 多个作用域链式调用 $adults = User::active()->olderThan(18)->get();

    // 带参数的作用域 $seniors = User::olderThan(65)->get(); $admins = User::inRole('admin')->get();

    // 组合使用 $verifiedAdults = User::verified()->olderThan(18)->get();

    作用域最佳实践

    class User extends Model
    {
        // ✅ 推荐:按业务逻辑组织作用域
        public static function scopeActive($query)
        {
            return $query->where('status', 'active');
        }
    

    public static function scopeInactive($query) { return $query->where('status', 'inactive'); }

    public static function scopeVerified($query) { return $query->whereNotNull('email_verified_at'); }

    public static function scopeUnverified($query) { return $query->whereNull('email_verified_at'); }

    // 复杂作用域 public static function scopeEligibleForPromotion($query) { return $query->where('status', 'active') ->where('age', '>=', 18) ->whereNotNull('email_verified_at') ->where('score', '>', 100); } }

    // 使用 $eligibleUsers = User::eligibleForPromotion()->get();

    属性操作

    获取属性

    $user = User::find(1);
    

    // 方式 1:使用属性访问(推荐) $name = $user->name;

    // 方式 2:使用 getAttribute 方法 $name = $user->getAttribute('name'); $name = $user->getAttribute('name', 'Default Name'); // 带默认值

    // 获取所有属性 $attributes = $user->getAttributes();

    设置属性

    $user = User::find(1);
    

    // 方式 1:使用属性赋值(推荐) $user->name = 'New Name';

    // 方式 2:使用 setAttribute 方法 $user->setAttribute('name', 'New Name');

    检查属性

    $user = User::find(1);
    

    // 检查属性是否存在 if (isset($user->name)) { echo $user->name; }

    // 检查属性是否为空 if (empty($user->avatar)) { $user->avatar = 'default.jpg'; }

    获取原始值

    $user = User::find(1);
    $originalName = $user->name; // 'John'
    

    $user->name = 'Jane'; $originalName = $user->getOriginal('name'); // 'John'(原始值)

    // 获取所有原始值 $original = $user->getOriginal();

    检查变更

    $user = User::find(1);
    

    // 检查是否有变更 if ($user->isDirty()) { $dirty = $user->getDirty(); // 获取变更的字段 // ['name' => 'New Name'] }

    // 检查特定字段是否变更 if ($user->isDirty('name')) { echo "Name has been changed"; }

    // 检查字段是否未变更 if (!$user->isDirty('email')) { echo "Email has not been changed"; }

    注意isDirty() 方法在当前的 Model 实现中通过 getDirty() 方法实现,可以通过检查 getDirty() 是否为空来判断:
    // 检查是否有变更
    $hasChanges = !empty($user->getDirty());
    

    // 检查特定字段是否变更 $nameChanged = isset($user->getDirty()['name']);

    获取原始值

    $user = User::find(1);
    $originalName = $user->name; // 'John'
    

    $user->name = 'Jane'; $originalName = $user->getOriginal('name'); // 'John'(原始值)

    // 获取所有原始值 $original = $user->getOriginal();

    注意:当前 Model 实现中,getOriginal() 方法需要直接访问 $original 属性或通过 getAttributes() 对比实现。建议在模型中添加辅助方法:
    // 在模型中使用
    $user = User::find(1);
    $original = $user->original; // 直接访问 protected 属性(不推荐)
    

    // 推荐:在模型中添加方法 public function getOriginal(?string $key = null): mixed { if ($key === null) { return $this->original; } return $this->original[$key] ?? null; }

    同步原始值

    $user = User::find(1);
    $user->name = 'New Name';
    

    // 同步原始值(取消变更)- 保存后自动同步 $user->save(); // 保存后 original 会自动更新为当前 attributes

    // 手动同步(如果需要) // 注意:当前实现中,保存后会自动同步,无需手动调用

    序列化

    toArray() - 转换为数组

    $user = User::find(1);
    

    // 转换为数组 $array = $user->toArray(); // [ // 'id' => 1, // 'name' => 'John Doe', // 'email' => 'john@example.com', // ... // ]

    // 在响应中使用 return Response::json($user->toArray());

    toJson() - 转换为 JSON

    $user = User::find(1);
    

    // 转换为 JSON 字符串 $json = $user->toJson(); // '{"id":1,"name":"John Doe","email":"john@example.com",...}'

    // 在响应中使用 return Response::json($user->toArray()); // 或直接返回模型(框架会自动转换) return Response::json($user);

    序列化关联

    $user = User::with('posts')->find(1);
    

    // 包含关联的数组 $array = $user->toArray(); // [ // 'id' => 1, // 'name' => 'John Doe', // 'posts' => [ // ['id' => 1, 'title' => 'Post 1'], // ['id' => 2, 'title' => 'Post 2'], // ], // ]

    数据库连接

    使用默认连接

    class User extends Model
    {
        // 使用默认数据库连接
    }
    

    指定连接

    class User extends Model
    {
        protected static function getConnection(): ConnectionInterface
        {
            return DatabaseManager::getInstance()->connection('mysql2');
        }
    }
    

    动态设置连接

    // 为模型设置连接
    User::setConnection(DatabaseManager::getInstance()->connection('mysql2'));
    

    // 使用指定连接查询 $user = User::on('mysql2')->find(1);

    最佳实践

    1. 模型组织

    // ✅ 推荐:按功能模块组织模型
    app/Index/Model/
    ├── User.php
    ├── Profile.php
    ├── Post.php
    ├── Category.php
    └── Comment.php
    

    2. 命名规范

    // ✅ 推荐:模型类名使用单数,表名使用复数
    class User extends Model { }        // 表名:users
    class Post extends Model { }        // 表名:posts
    class UserProfile extends Model { } // 表名:user_profiles
    

    3. 批量赋值保护

    // ✅ 推荐:使用 fillable 保护敏感字段
    class User extends Model
    {
        protected static array $fillable = [
            'name',
            'email',
            // 不包含 password、is_admin、token 等敏感字段
        ];
    }
    

    4. 类型转换

    // ✅ 推荐:为所有需要转换的字段定义类型
    class User extends Model
    {
        protected static array $casts = [
            'is_active' => 'boolean',
            'settings' => 'json',
            'score' => 'integer',
            'created_at' => 'datetime',
        ];
    }
    

    5. 避免 N+1 查询

    // ❌ 错误:N+1 查询
    $users = User::all();
    foreach ($users as $user) {
        $posts = $user->posts; // 每次循环都查询数据库
    }
    

    // ✅ 正确:预加载关联 $users = User::with('posts')->get(); foreach ($users as $user) { $posts = $user->posts; // 已预加载,不查询数据库 }

    6. 使用查询作用域

    // ✅ 推荐:封装常用查询逻辑
    class User extends Model
    {
        public static function scopeActive($query)
        {
            return $query->where('status', 'active');
        }
    }
    

    // 使用 $activeUsers = User::active()->get();

    7. 合理使用软删除

    // ✅ 推荐:对需要保留历史的数据使用软删除
    class Order extends Model
    {
        protected static bool $softDelete = true;
    }
    

    // ✅ 推荐:对临时数据不使用软删除 class Session extends Model { protected static bool $softDelete = false; }

    8. 模型事件

    // ✅ 推荐:在模型事件中处理业务逻辑
    class Order extends Model
    {
        protected function beforeSave(): bool
        {
            // 自动计算、验证等
            return true;
        }
    

    protected function afterSave(): void { // 发送通知、更新缓存等 } }

    9. 访问器和修改器

    // ✅ 推荐:使用访问器处理计算属性
    class User extends Model
    {
        public function getFullNameAttribute(): string
        {
            return $this->first_name . ' ' . $this->last_name;
        }
    }
    

    // ✅ 推荐:使用修改器处理输入 class User extends Model { public function setPasswordAttribute(string $value): void { $this->attributes['password'] = password_hash($value, PASSWORD_DEFAULT); } }

    10. 查询优化

    // ✅ 推荐:只查询需要的字段
    $users = User::query()
        ->select(['id', 'name', 'email'])
        ->get();
    

    // ✅ 推荐:使用索引字段查询 $user = User::where('email', 'user@example.com')->first();

    // ✅ 推荐:使用分页 $users = User::query() ->offset(0) ->limit(20) ->get();

    API 参考

    静态方法

    #### find(int|string $id): ?static

    根据主键查找模型。

    $user = User::find(1);
    

    #### all(): array

    查找所有记录。

    $users = User::all();
    

    #### create(array $attributes): static

    创建新记录。

    $user = User::create(['name' => 'John']);
    

    #### query(): QueryBuilder

    获取查询构建器。

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

    #### withTrashed(): QueryBuilder

    包含软删除记录的查询。

    $users = User::withTrashed()->get();
    

    #### onlyTrashed(): QueryBuilder

    只查询软删除的记录。

    $users = User::onlyTrashed()->get();
    

    #### getTableName(): string

    获取表名。

    $tableName = User::getTableName(); // 'users'
    

    #### getPrimaryKey(): string

    获取主键名。

    $primaryKey = User::getPrimaryKey(); // 'id'
    

    实例方法

    #### save(): bool

    保存模型。

    $user->save();
    

    #### update(array $attributes): bool

    更新模型属性。

    $user->update(['name' => 'Jane']);
    

    #### delete(bool $force = false): bool

    删除模型。

    $user->delete();
    $user->delete(true); // 强制删除
    

    #### forceDelete(): bool

    强制删除(即使启用软删除也真正删除)。

    $user->forceDelete();
    

    #### restore(): bool

    恢复软删除的记录。

    $user->restore();
    

    #### fill(array $attributes): self

    批量赋值。

    $user->fill(['name' => 'John', 'email' => 'john@example.com']);
    

    #### getAttribute(string $key, mixed $default = null): mixed

    获取属性值。

    $name = $user->getAttribute('name');
    $name = $user->getAttribute('name', 'Default');
    

    #### setAttribute(string $key, mixed $value): void

    设置属性值。

    $user->setAttribute('name', 'John');
    

    #### getAttributes(): array

    获取所有属性。

    $attributes = $user->getAttributes();
    

    #### getDirty(): array

    获取变更的字段。

    $dirty = $user->getDirty();
    

    #### getOriginal(?string $key = null): mixed

    获取原始值。

    $original = $user->getOriginal('name');
    $allOriginal = $user->getOriginal();
    

    #### isDirty(?string $key = null): bool

    检查是否有变更。

    $isDirty = $user->isDirty();
    $nameDirty = $user->isDirty('name');
    

    #### toArray(): array

    转换为数组。

    $array = $user->toArray();
    

    #### toJson(): string

    转换为 JSON。

    $json = $user->toJson();
    

    关联方法

    #### hasOne(string $related, ?string $foreignKey = null, string $localKey = 'id'): HasOne

    定义一对一关联。

    public function profile(): HasOne
    {
        return $this->hasOne(Profile::class, 'user_id');
    }
    

    #### hasMany(string $related, ?string $foreignKey = null, string $localKey = 'id'): HasMany

    定义一对多关联。

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

    #### belongsTo(string $related, ?string $foreignKey = null, string $ownerKey = 'id'): BelongsTo

    定义多对一关联。

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

    常见问题

    Q: 如何获取原始属性值?

    A: 使用 getOriginal() 方法:

    $user = User::find(1);
    $originalName = $user->getOriginal('name');
    

    Q: 如何检查模型是否已保存?

    A: 使用 exists 属性:

    $user = new User();
    $user->exists; // false
    

    $user->save(); $user->exists; // true

    Q: 如何获取变更的字段?

    A: 使用 getDirty() 方法:

    $user = User::find(1);
    $user->name = 'New Name';
    $dirty = $user->getDirty(); // ['name' => 'New Name']
    

    Q: 模型可以跨数据库连接吗?

    A: 可以,在模型中重写 getConnection() 方法或使用 setConnection() 方法。

    Q: 如何禁用时间戳?

    A: 设置 $timestamps = false

    class User extends Model
    {
        protected static bool $timestamps = false;
    }
    

    Q: 访问器和修改器的命名规则是什么?

    A:

  • 访问器:get{FieldName}Attribute
  • 修改器:set{FieldName}Attribute
  • 字段名使用驼峰命名,例如 full_name 对应 getFullNameAttribute

    Q: 如何预加载多个关联?

    A: 使用 with() 方法:

    $users = User::with(['posts', 'profile', 'comments'])->get();
    

    Q: 关联查询如何添加条件?

    A: 使用闭包:

    $users = User::with(['posts' => function($query) {
        $query->where('status', 'published');
    }])->get();
    

    高级功能

    模型刷新

    从数据库重新加载模型数据:

    $user = User::find(1);
    $user->name = 'New Name';
    

    // 从数据库重新加载(丢弃本地修改) $user = User::find(1); // 重新查询

    // 或者使用查询构建器重新查询 $freshUser = User::query() ->where('id', $user->id) ->first();

    模型复制

    创建模型的副本(用于创建相似记录):

    $user = User::find(1);
    

    // 复制模型(排除主键和时间戳) $newUser = new User(); $newUser->fill($user->getAttributes()); unset($newUser->id); // 移除主键 unset($newUser->created_at); unset($newUser->updated_at); $newUser->save();

    批量更新

    使用查询构建器批量更新:

    // 批量更新
    User::where('status', 'pending')
        ->update(['status' => 'active']);
    

    // 条件批量更新 User::where('age', '<', 18) ->update(['is_minor' => true]);

    批量删除

    // 批量删除
    User::where('status', 'inactive')->delete();
    

    // 根据主键批量删除 // 注意:当前实现中没有 destroy() 方法,可以使用查询构建器 User::whereIn('id', [1, 2, 3])->delete();

    模型比较

    比较两个模型实例:

    $user1 = User::find(1);
    $user2 = User::find(2);
    

    // 比较主键 if ($user1->id === $user2->id) { echo "Same user"; }

    // 比较属性 if ($user1->email === $user2->email) { echo "Same email"; }

    模型集合操作

    // 获取所有模型
    $users = User::all();
    

    // 遍历集合 foreach ($users as $user) { echo $user->name; }

    // 过滤集合 $activeUsers = array_filter($users, function($user) { return $user->status === 'active'; });

    // 映射集合 $names = array_map(function($user) { return $user->name; }, $users);

    常见问题

    Q: 如何获取原始属性值?

    A: 当前 Model 实现中,原始值存储在 $original 属性中。可以在模型中添加辅助方法:

    class User extends Model
    {
        public function getOriginal(?string $key = null): mixed
        {
            if ($key === null) {
                return $this->original;
            }
            return $this->original[$key] ?? null;
        }
    }
    

    Q: 如何检查模型是否已保存?

    A: 使用 exists 属性:

    $user = new User();
    $user->exists; // false
    

    $user->save(); $user->exists; // true

    Q: 如何获取变更的字段?

    A: 使用 getDirty() 方法:

    $user = User::find(1);
    $user->name = 'New Name';
    $dirty = $user->getDirty(); // ['name' => 'New Name']
    

    // 检查是否有变更 $hasChanges = !empty($user->getDirty());

    // 检查特定字段是否变更 $nameChanged = isset($user->getDirty()['name']);

    Q: 模型可以跨数据库连接吗?

    A: 可以,在模型中重写 getConnection() 方法或使用 setConnection() 方法:

    class User extends Model
    {
        protected static function getConnection(): ConnectionInterface
        {
            return DatabaseManager::getInstance()->connection('mysql2');
        }
    }
    

    // 或者动态设置 User::setConnection(DatabaseManager::getInstance()->connection('mysql2'));

    Q: 如何禁用时间戳?

    A: 设置 $timestamps = false

    class User extends Model
    {
        protected static bool $timestamps = false;
    }
    

    Q: 访问器和修改器的命名规则是什么?

    A:

  • 访问器:get{FieldName}Attribute
  • 修改器:set{FieldName}Attribute
  • 字段名使用驼峰命名,例如 full_name 对应 getFullNameAttribute

    Q: 如何预加载多个关联?

    A: 当前实现中,关联是懒加载的。可以通过查询构建器预加载:

    // 方式 1:使用 JOIN
    $users = User::query()
        ->leftJoin('profiles', 'users.id', '=', 'profiles.user_id')
        ->select('users.', 'profiles.')
        ->get();
    

    // 方式 2:手动预加载(在循环外查询) $userIds = array_column($users, 'id'); $profiles = Profile::whereIn('user_id', $userIds)->get(); $profilesByUserId = []; foreach ($profiles as $profile) { $profilesByUserId[$profile->user_id] = $profile; }

    Q: 关联查询如何添加条件?

    A: 在关联方法中返回查询构建器,然后添加条件:

    class User extends Model
    {
        public function activePosts(): HasMany
        {
            return $this->hasMany(Post::class, 'user_id')
                ->where('status', 'published');
        }
    }
    

    // 使用 $user = User::find(1); $activePosts = $user->activePosts; // 只返回已发布的文章

    Q: 如何实现模型作用域?

    A: 定义静态方法,方法名以 scope 开头:

    class User extends Model
    {
        public static function scopeActive($query)
        {
            return $query->where('status', 'active');
        }
    }
    

    // 使用 $activeUsers = User::active()->get();

    Q: 模型事件如何工作?

    A: 重写模型事件方法:

    class User extends Model
    {
        protected function beforeSave(): bool
        {
            // 保存前的逻辑
            return true; // 返回 false 取消保存
        }
    

    protected function afterSave(): void { // 保存后的逻辑 }

    protected function beforeDelete(): bool { // 删除前的逻辑 return true; // 返回 false 取消删除 }

    protected function afterDelete(): void { // 删除后的逻辑 } }

    相关文档

  • 数据库系统 - 数据库系统总览
  • 查询构建器 - 数据库查询详细说明
  • 数据库迁移 - 数据库结构管理
  • 模型关联示例 - 关联关系更多示例