mcpField
L
laravel-models

laravel-models

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

关于 laravel-models

laravel-models 是面向 Laravel 开发者的 Eloquent 模型设计技能,介绍模型职责、类型转换、关系和路由绑定,并强调将业务逻辑放在 Actions 中。

主要功能

  1. 01模型职责与结构
  2. 02自定义查询构建器和关系
  3. 03类型安全的属性转换
  4. 04观察器与可复用 Trait
  5. 05路由模型绑定指南

适用场景

  1. 01设计应用的 Eloquent 模型
  2. 02确定业务逻辑的放置位置
  3. 03配置类型转换和模型生命周期钩子

安装

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

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

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

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

详细介绍

Laravel 模型

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

相关指南:

  • 查询构建器 - 自定义查询构建器(而非作用域)
  • Actions - Actions 包含业务逻辑
  • DTOs - 将模型 JSON 列转换为 DTO

理念

模型应当:

  • 使用自定义查询构建器(而非本地作用域)——参见查询构建器
  • 定义关系
  • 定义类型转换
  • 包含简单的访问器/修改器
  • 不包含业务逻辑(业务逻辑应放在 Actions 中)
  • 在可用时优先使用 PHP 属性,而非属性/方法(Laravel 12+ 支持 #[UseEloquentBuilder],Laravel 13+ 支持 #[Table]、#[ObservedBy]、#[UsePolicy]、#[UseFactory] 等)

基本模型结构

<?php

declare(strict_types=1);

namespace App\Models;

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

#[UseEloquentBuilder(OrderBuilder::class)]
class Order extends Model
{
    use HasFactory;

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

    // 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
    ];
}

模型方法

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

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);
}

模型关注点(Traits)

提取可复用的行为:

查看完整实现 →

在模型中使用:

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 数组
  • 灵活性:所有属性都可以批量赋值
  • 信任:在 Form Requests 和 Actions 中进行适当验证后,批量赋值保护就显得多余
  • 更简洁的模型:减少样板代码

**重要:**将输入传递给 Actions/模型前,务必在 Form Requests 中验证。

模型组织

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);
});

评论

支持 Markdown