mcpField
L
laravel-models

laravel-models

github.com/leeovery开发4338 次安装31 周前更新
代码仓库

关于 laravel-models

laravel-models 是介绍 Laravel Eloquent 模型约定的开发技能,面向 Laravel 开发者。它涵盖关系、类型转换、自定义查询构造器和生命周期钩子的组织方式,并建议将业务逻辑放在 Actions 中。

主要功能

  1. 01自定义查询构造器规范
  2. 02关系与类型转换模式
  3. 03访问器和修改器示例
  4. 04使用观察者处理生命周期钩子
  5. 05路由绑定与软删除指南

适用场景

  1. 01为新 Eloquent 模型制定规范
  2. 02建模关联记录和类型化属性
  3. 03避免在模型中堆放业务逻辑
  4. 04配置路由绑定和软删除

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/leeovery/claude-laravel --skill laravel-models

安装后在 Claude Code 中输入/laravel-models确认已加载。

附带文件references/HasUuid.phpreferences/query-builders.md

详细介绍

Laravel 模型

模型表示数据库表和领域实体。

相关指南:

  • 查询构造器 - 自定义查询构造器(不使用局部作用域)
  • 操作 - 操作中包含业务逻辑
  • DTO - 将模型 JSON 列转换为 DTO

理念

模型应该:

  • 使用自定义查询构造器(而非局部作用域)——参见查询构造器
  • 定义关系
  • 定义类型转换
  • 包含简单的访问器/修改器
  • 不包含业务逻辑(业务逻辑应放在操作中)

基本模型结构

<?php

declare(strict_types=1);

namespace App\Models;

use App\Builders\OrderBuilder;
use App\Enums\OrderStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Order extends Model
{
    use HasFactory;

    protected function casts(): array
    {
        return [
            'status' => OrderStatus::class,
            'total' => 'integer',
        ];
    }

    // Custom Query Builder
    public function newEloquentBuilder($query): OrderBuilder
    {
        return new OrderBuilder($query);
    }

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

    public function items(): HasMany
    {
        return $this->hasMany(OrderItem::class);
    }
}

类型转换

定义类型转换以确保类型安全:

protected function casts(): array
{
    return [
        'status' => OrderStatus::class,         // Enum
        'total' => 'integer',                   // Integer
        'is_paid' => 'boolean',                 // Boolean
        'metadata' => OrderMetadataData::class, // DTO
        'completed_at' => 'datetime',           // Carbon
        'tags' => 'array',                      // JSON array
    ];
}

可用的类型转换:

  • 'integer'、'real'、'float'、'double'
  • 'string'、'boolean'
  • 'array'、'json'、'object'、'collection'
  • 'date'、'datetime'、'immutable_date'、'immutable_datetime'
  • 'timestamp'
  • 'encrypted'、'encrypted:array'、'encrypted:collection'、'encrypted:json'、'encrypted:object'
  • 自定义类型转换类
  • 枚举类
  • DTO 类

关系

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

public function customer(): BelongsTo
{
    return $this->belongsTo(Customer::class, 'customer_id', 'id');
}
HasMany
public function orders(): HasMany
{
    return $this->hasMany(Order::class);
}

public function items(): HasMany
{
    return $this->hasMany(OrderItem::class);
}
HasOne
public function profile(): HasOne
{
    return $this->hasOne(UserProfile::class);
}
BelongsToMany
public function roles(): BelongsToMany
{
    return $this->belongsToMany(Role::class)
        ->withTimestamps()
        ->withPivot('assigned_at');
}
HasManyThrough
public function deployments(): HasManyThrough
{
    return $this->hasManyThrough(Deployment::class, Environment::class);
}
MorphTo / MorphMany
// MorphTo
public function commentable(): MorphTo
{
    return $this->morphTo();
}

// MorphMany
public function comments(): MorphMany
{
    return $this->morphMany(Comment::class, 'commentable');
}

访问器和修改器

访问器(获取)
use Illuminate\Database\Eloquent\Casts\Attribute;

protected function fullName(): Attribute
{
    return Attribute::make(
        get: fn () => "{$this->first_name} {$this->last_name}",
    );
}

// Usage
$user->full_name; // "John Doe"
修改器(设置)
protected function password(): Attribute
{
    return Attribute::make(
        set: fn (string $value) => bcrypt($value),
    );
}

// Usage
$user->password = 'secret'; // Automatically hashed
同时获取和设置
protected function email(): Attribute
{
    return Attribute::make(
        get: fn (string $value) => strtolower($value),
        set: fn (string $value) => strtolower(trim($value)),
    );
}

模型方法

简单的辅助方法可以接受:

class Order extends Model
{
    public function isPending(): bool
    {
        return $this->status === OrderStatus::Pending;
    }

    public function isCompleted(): bool
    {
        return $this->status === OrderStatus::Completed;
    }

    public function canBeCancelled(): bool
    {
        return $this->isPending() || $this->status === OrderStatus::Processing;
    }
}

但不能包含业务逻辑:

// ❌ Bad - business logic in model
class Order extends Model
{
    public function cancel(): void
    {
        DB::transaction(function () {
            $this->update(['status' => OrderStatus::Cancelled]);
            $this->refundPayment();
            $this->notifyCustomer();
        });
    }
}

// ✅ Good - business logic in action
class CancelOrderAction
{
    public function __invoke(Order $order): Order
    {
        return DB::transaction(function () use ($order) {
            $order->update(['status' => OrderStatus::Cancelled]);
            resolve(RefundPaymentAction::class)($order);
            resolve(NotifyCustomerAction::class)($order);
            return $order;
        });
    }
}

模型观察器

用于模型生命周期钩子:

<?php

declare(strict_types=1);

namespace App\Observers;

use App\Models\Order;
use Illuminate\Support\Str;

class OrderObserver
{
    public function creating(Order $order): void
    {
        if (! $order->uuid) {
            $order->uuid = Str::uuid();
        }
    }

    public function created(Order $order): void
    {
        // Dispatch event, queue job, etc.
    }

    public function updating(Order $order): void
    {
        // Before update
    }

    public function updated(Order $order): void
    {
        // After update
    }

    public function deleted(Order $order): void
    {
        // After delete
    }
}

在 AppServiceProvider 中注册:

use App\Models\Order;
use App\Observers\OrderObserver;

public function boot(): void
{
    Order::observe(OrderObserver::class);
}

模型关注点(Trait)

提取可复用行为:

查看完整实现 →

在模型中使用:

class Order extends Model
{
    use HasUuid;
}

路由模型绑定

隐式绑定
// Route
Route::get('/orders/{order}', [OrderController::class, 'show']);

// Controller - automatically receives Order model
public function show(Order $order) { }
自定义键
Route::get('/orders/{order:uuid}', [OrderController::class, 'show']);
自定义解析
public function resolveRouteBinding($value, $field = null)
{
    return $this->where($field ?? 'id', $value)
        ->where('is_active', true)
        ->firstOrFail();
}

批量赋值保护

默认情况下,所有模型都应取消保护。

AppServiceProvider 配置

在 AppServiceProvider::boot() 方法中调用 Model::unguard():

<?php

declare(strict_types=1);

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::unguard();
    }
}
模型配置

不要在模型中使用 $fillable 或 $guarded 属性:

// ✅ Good - no fillable/guarded
class Order extends Model
{
    protected function casts(): array
    {
        return [
            'status' => OrderStatus::class,
        ];
    }
}

// ❌ Bad - don't use fillable
class Order extends Model
{
    protected $fillable = ['name', 'email'];
}

// ❌ Bad - don't use guarded
class Order extends Model
{
    protected $guarded = [];
}
为什么取消保护?
  • 简单:无需维护 fillable/guarded 数组
  • 灵活:所有属性都可以批量赋值
  • 信任:在表单请求和操作中进行适当验证后,批量赋值保护便是多余的
  • 模型更简洁:减少样板代码

**重要:**在将输入传递给操作/模型之前,务必在表单请求中验证输入。

时间戳

// Disable timestamps
public $timestamps = false;

// Custom timestamp columns
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'updated_date';

软删除

use Illuminate\Database\Eloquent\SoftDeletes;

class Order extends Model
{
    use SoftDeletes;
}

用法:

$order->delete();      // Soft delete
$order->forceDelete(); // Permanent delete
$order->restore();     // Restore

Order::withTrashed()->find($id);
Order::onlyTrashed()->get();

集合

查询结果会返回集合:

$orders = Order::all(); // Illuminate\Database\Eloquent\Collection

$orders->filter(fn($order) => $order->isPending());
$orders->map(fn($order) => $order->total);
$orders->sum('total');

模型组织

app/Models/
├── Order.php
├── User.php
├── Concerns/
│   ├── HasUuid.php
│   ├── BelongsToTenant.php
│   └── Searchable.php
└── Contracts/
    └── Searchable.php

测试模型

it('can mass assign attributes', function () {
    $order = Order::create([
        'user_id' => 1,
        'status' => 'pending',
        'total' => 1000,
        'notes' => 'Test order',
    ]);

    expect($order->user_id)->toBe(1)
        ->and($order->total)->toBe(1000);
});

it('casts status to enum', function () {
    $order = Order::factory()->create(['status' => 'pending']);

    expect($order->status)->toBeInstanceOf(OrderStatus::class);
});

it('has user relationship', function () {
    $order = Order::factory()->create();

    expect($order->user)->toBeInstanceOf(User::class);
});

总结

模型应该:

  • 通过 AppServiceProvider 中的 Model::unguard() 在全局范围内取消保护
  • 定义结构(类型转换、关系)
  • 使用自定义查询构造器(而非作用域)
  • 包含简单的辅助方法
  • 使用观察器处理生命周期钩子

模型不应该:

  • 使用 $fillable 或 $guarded 属性
  • 包含业务逻辑(使用操作)
  • 包含复杂方法(使用操作)
  • 使用局部作用域(使用自定义构造器)

评论

支持 Markdown