laravel-models

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Laravel Models

Laravel 模型

Models represent database tables and domain entities.
Related guides:
  • Query Builders - Custom query builders (not scopes)
  • Actions - Actions contain business logic
  • DTOs - Casting model JSON columns to DTOs
模型代表数据库表和领域实体。
相关指南:
  • 查询构建器 - 自定义查询构建器(非作用域)
  • 动作 - 动作包含业务逻辑
  • DTO - 将模型JSON列转换为DTO

Philosophy

设计理念

Models should:
  • Use custom query builders (not local scopes) - see Query Builders
  • Define relationships
  • Define casts
  • Contain simple accessors/mutators
  • NOT contain business logic (that belongs in Actions)
模型应:
  • 使用自定义查询构建器(而非本地作用域)- 参见查询构建器
  • 定义关联关系
  • 定义类型转换
  • 包含简单的访问器/修改器
  • 不包含业务逻辑(业务逻辑应放在动作中)

Basic Model Structure

基础模型结构

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

Casts

类型转换

Define casts for type safety:
php
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
    ];
}
Available casts:
  • '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'
  • Custom cast classes
  • Enum classes
  • DTO classes
为类型安全定义类型转换:
php
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类

Relationships

关联关系

BelongsTo

BelongsTo(属于)

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

public function customer(): BelongsTo
{
    return $this->belongsTo(Customer::class, 'customer_id', 'id');
}
php
public function user(): BelongsTo
{
    return $this->belongsTo(User::class);
}

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

HasMany

HasMany(有多个)

php
public function orders(): HasMany
{
    return $this->hasMany(Order::class);
}

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

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

HasOne

HasOne(有一个)

php
public function profile(): HasOne
{
    return $this->hasOne(UserProfile::class);
}
php
public function profile(): HasOne
{
    return $this->hasOne(UserProfile::class);
}

BelongsToMany

BelongsToMany(属于多个)

php
public function roles(): BelongsToMany
{
    return $this->belongsToMany(Role::class)
        ->withTimestamps()
        ->withPivot('assigned_at');
}
php
public function roles(): BelongsToMany
{
    return $this->belongsToMany(Role::class)
        ->withTimestamps()
        ->withPivot('assigned_at');
}

HasManyThrough

HasManyThrough(通过...有多个)

php
public function deployments(): HasManyThrough
{
    return $this->hasManyThrough(Deployment::class, Environment::class);
}
php
public function deployments(): HasManyThrough
{
    return $this->hasManyThrough(Deployment::class, Environment::class);
}

MorphTo / MorphMany

MorphTo / MorphMany(多态关联)

php
// MorphTo
public function commentable(): MorphTo
{
    return $this->morphTo();
}

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

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

Accessors & Mutators

访问器与修改器

Accessors (Get)

访问器(获取)

php
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"
php
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"

Mutators (Set)

修改器(设置)

php
protected function password(): Attribute
{
    return Attribute::make(
        set: fn (string $value) => bcrypt($value),
    );
}

// Usage
$user->password = 'secret'; // Automatically hashed
php
protected function password(): Attribute
{
    return Attribute::make(
        set: fn (string $value) => bcrypt($value),
    );
}

// Usage
$user->password = 'secret'; // Automatically hashed

Both Get and Set

同时支持获取与设置

php
protected function email(): Attribute
{
    return Attribute::make(
        get: fn (string $value) => strtolower($value),
        set: fn (string $value) => strtolower(trim($value)),
    );
}
php
protected function email(): Attribute
{
    return Attribute::make(
        get: fn (string $value) => strtolower($value),
        set: fn (string $value) => strtolower(trim($value)),
    );
}

Model Methods

模型方法

Simple helper methods are acceptable:
php
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;
    }
}
But NOT business logic:
php
// ❌ 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
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;
    }
}
但不要包含业务逻辑:
php
// ❌ 错误 - 业务逻辑放在模型中
class Order extends Model
{
    public function cancel(): void
    {
        DB::transaction(function () {
            $this->update(['status' => OrderStatus::Cancelled]);
            $this->refundPayment();
            $this->notifyCustomer();
        });
    }
}

// ✅ 正确 - 业务逻辑放在动作中
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;
        });
    }
}

Model Observers

模型观察者

For model lifecycle hooks:
php
<?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
    }
}
Register in AppServiceProvider:
php
use App\Models\Order;
use App\Observers\OrderObserver;

public function boot(): void
{
    Order::observe(OrderObserver::class);
}
用于模型生命周期钩子:
php
<?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中注册:
php
use App\Models\Order;
use App\Observers\OrderObserver;

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

Model Concerns (Traits)

模型关注点(Traits)

Extract reusable behavior:
View full implementation →
Use in models:
php
class Order extends Model
{
    use HasUuid;
}
提取可复用的行为:
查看完整实现 →
在模型中使用:
php
class Order extends Model
{
    use HasUuid;
}

Route Model Binding

路由模型绑定

Implicit Binding

隐式绑定

php
// Route
Route::get('/orders/{order}', [OrderController::class, 'show']);

// Controller - automatically receives Order model
public function show(Order $order) { }
php
// Route
Route::get('/orders/{order}', [OrderController::class, 'show']);

// Controller - automatically receives Order model
public function show(Order $order) { }

Custom Key

自定义键

php
Route::get('/orders/{order:uuid}', [OrderController::class, 'show']);
php
Route::get('/orders/{order:uuid}', [OrderController::class, 'show']);

Custom Resolution

自定义解析逻辑

php
public function resolveRouteBinding($value, $field = null)
{
    return $this->where($field ?? 'id', $value)
        ->where('is_active', true)
        ->firstOrFail();
}
php
public function resolveRouteBinding($value, $field = null)
{
    return $this->where($field ?? 'id', $value)
        ->where('is_active', true)
        ->firstOrFail();
}

Mass Assignment Protection

批量赋值保护

All models should be unguarded by default.
所有模型默认应取消保护。

AppServiceProvider Setup

AppServiceProvider 设置

In your
AppServiceProvider::boot()
method, call
Model::unguard()
:
php
<?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();
    }
}
AppServiceProvider::boot()
方法中,调用
Model::unguard()
php
<?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();
    }
}

Model Configuration

模型配置

Do NOT use
$fillable
or
$guarded
properties
on your models:
php
// ✅ 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
属性:
php
// ✅ 正确 - 无fillable/guarded
class Order extends Model
{
    protected function casts(): array
    {
        return [
            'status' => OrderStatus::class,
        ];
    }
}

// ❌ 错误 - 不要使用fillable
class Order extends Model
{
    protected $fillable = ['name', 'email'];
}

// ❌ 错误 - 不要使用guarded
class Order extends Model
{
    protected $guarded = [];
}

Why Unguard?

为什么取消保护?

  • Simplicity: No need to maintain fillable/guarded arrays
  • Flexibility: All attributes can be mass-assigned
  • Trust: With proper validation in Form Requests and Actions, mass assignment protection is redundant
  • Cleaner Models: Less boilerplate code
Important: Always validate input in Form Requests before passing to Actions/Models.
  • 简洁性:无需维护fillable/guarded数组
  • 灵活性:所有属性都可以批量赋值
  • 信任:在Form Requests和Actions中进行适当验证后,批量赋值保护是多余的
  • 更简洁的模型:更少的样板代码
重要提示: 在将输入传递给Actions/Models之前,始终在Form Requests中验证输入。

Timestamps

时间戳

php
// Disable timestamps
public $timestamps = false;

// Custom timestamp columns
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'updated_date';
php
// Disable timestamps
public $timestamps = false;

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

Soft Deletes

软删除

php
use Illuminate\Database\Eloquent\SoftDeletes;

class Order extends Model
{
    use SoftDeletes;
}
Usage:
php
$order->delete();      // Soft delete
$order->forceDelete(); // Permanent delete
$order->restore();     // Restore

Order::withTrashed()->find($id);
Order::onlyTrashed()->get();
php
use Illuminate\Database\Eloquent\SoftDeletes;

class Order extends Model
{
    use SoftDeletes;
}
用法:
php
$order->delete();      // Soft delete
$order->forceDelete(); // Permanent delete
$order->restore();     // Restore

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

Collections

集合

Query results return Collections:
php
$orders = Order::all(); // Illuminate\Database\Eloquent\Collection

$orders->filter(fn($order) => $order->isPending());
$orders->map(fn($order) => $order->total);
$orders->sum('total');
查询结果返回集合:
php
$orders = Order::all(); // Illuminate\Database\Eloquent\Collection

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

Model Organization

模型组织

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

Testing Models

测试模型

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

Summary

总结

Models should:
  • Be unguarded globally via
    Model::unguard()
    in AppServiceProvider
  • Define structure (casts, relationships)
  • Use custom query builders (not scopes)
  • Have simple helper methods
  • Use observers for lifecycle hooks
Models should NOT:
  • Use
    $fillable
    or
    $guarded
    properties
  • Contain business logic (use Actions)
  • Have complex methods (use Actions)
  • Use local scopes (use custom builders)
模型应:
  • 通过AppServiceProvider中的
    Model::unguard()
    全局取消保护
  • 定义结构(类型转换、关联关系)
  • 使用自定义查询构建器(而非作用域)
  • 包含简单的辅助方法
  • 使用观察者处理生命周期钩子
模型不应:
  • 使用
    $fillable
    $guarded
    属性
  • 包含业务逻辑(使用Actions)
  • 包含复杂方法(使用Actions)
  • 使用本地作用域(使用自定义构建器)