关于 laravel-models
laravel-models 是介绍 Laravel Eloquent 模型约定的开发技能,面向 Laravel 开发者。它涵盖关系、类型转换、自定义查询构造器和生命周期钩子的组织方式,并建议将业务逻辑放在 Actions 中。
主要功能
- 01自定义查询构造器规范
- 02关系与类型转换模式
- 03访问器和修改器示例
- 04使用观察者处理生命周期钩子
- 05路由绑定与软删除指南
适用场景
- 01为新 Eloquent 模型制定规范
- 02建模关联记录和类型化属性
- 03避免在模型中堆放业务逻辑
- 04配置路由绑定和软删除
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ leeovery/ claude-laravel --skill laravel-models安装后在 Claude Code 中输入/laravel-models确认已加载。
附带文件references/HasUuid.phpreferences/query-builders.md
详细介绍
Laravel 模型
模型表示数据库表和领域实体。
相关指南:
理念
模型应该:
- 使用自定义查询构造器(而非局部作用域)——参见查询构造器
- 定义关系
- 定义类型转换
- 包含简单的访问器/修改器
- 不包含业务逻辑(业务逻辑应放在操作中)
基本模型结构
<?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