Appearance
Sử dụng Model
Giới thiệu
PHPDocs là một công cụ cực kỳ quan trọng khi làm việc với Eloquent Model trong Laravel. Việc viết PHPDocs đầy đủ và chính xác không chỉ giúp document hóa code mà còn mang lại nhiều lợi ích thiết thực trong quá trình phát triển, đặc biệt là về type safety và hỗ trợ từ IDE.
Cấu trúc PHPDocs cơ bản cho Model
Đây là ví dụ về cách viết PHPDocs cho một Model User:
php
<?php
declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\CarbonImmutable;
/**
* Class User
* @package App\Models
*
* @property int $id
* @property string $name
* @property string $email
* @property string|null $password
* @property CarbonImmutable|null $email_verified_at
* @property string|null $remember_token
* @property CarbonImmutable $created_at
* @property CarbonImmutable $updated_at
* @property Collection<int, Post> $posts
* @property Collection<int, Role> $roles
* @property Profile|null $profile
*/
final class User extends Model
{
public function posts(): HasMany
{
return $this->hasMany(Post::class);
}
public function profile(): HasOne
{
return $this->hasOne(Profile::class);
}
}Các quy tắc và Hướng dẫn Quan trọng
1. Luôn viết PHPDocs cho @property
- Bắt buộc: Khai báo tất cả các cột trong bảng cơ sở dữ liệu tương ứng dưới dạng
@propertytrong PHPDocs của Model. - Type Safety & Type Hinting: Việc này giúp IDE (như PhpStorm) gợi ý code, kiểm tra kiểu dữ liệu khi truy cập thuộc tính và giúp các công cụ phân tích tĩnh như PHPStan phát hiện lỗi tiềm ẩn.
- Đúng kiểu dữ liệu: Sử dụng kiểu dữ liệu chính xác. Đối với các trường có thể null, sử dụng union type (ví dụ:
string|null).
2. Sử dụng casts() để định kiểu dữ liệu
- Laravel 10+: Sử dụng phương thức
casts()thay vì thuộc tính$castsđể khai báo việc ép kiểu. - Ép kiểu thời gian: Luôn ép kiểu các trường ngày giờ (ví dụ:
created_at,updated_at,deleted_at, các trường_at,_date) sang datetime. - Nếu đã đăng ký
CarbonImmutabletrongAppServiceProviderthông quaDate::use(CarbonImmutable::class), castdatetimesẽ trả về đối tượngCarbonImmutable. - Ép kiểu các trường khác khi cần thiết (ví dụ:
boolean,integer,float,array,object,collection, Enum).
Ví dụ về phương thức casts() trong model WorkOrder:
php
<?php
declare(strict_types=1);
namespace App\Models;
use App\Enums\WorkOrderStatus;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Model;
/**
* Class WorkOrder
*
* @property int $id
* @property CarbonImmutable $start_time
* @property CarbonImmutable $end_time
* @property bool $has_oqc
* @property WorkOrderStatus $status
* @property CarbonImmutable|null $forced_stop_at
*/
final class WorkOrder extends Model
{
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'start_time' => 'datetime',
'end_time' => 'datetime',
'forced_stop_at' => 'datetime',
'has_oqc' => 'boolean',
'status' => WorkOrderStatus::class,
'options' => 'array',
];
}
}3. Không viết logic nghiệp vụ trong Model
- Model chỉ nên chịu trách nhiệm định nghĩa cấu trúc dữ liệu, ép kiểu, khai báo relationships và cung cấp các local scopes.
- Tránh đặt logic xử lý nghiệp vụ phức tạp như tính toán giá, xử lý đơn hàng hoặc gửi email trực tiếp vào Model.
- Tách logic nghiệp vụ ra các lớp riêng như Actions, Jobs hoặc Services theo Action-based architecture.
4. Khai báo Relationships trong PHPDocs
- Sử dụng
@propertyđể khai báo các mối quan hệ đã định nghĩa trong Model. - Với HasMany, BelongsToMany, MorphMany và MorphToMany, sử dụng
Collectioncó type hint, ví dụ:@property Collection<int, Post> $posts. - Với HasOne, BelongsTo, MorphOne và MorphTo, trả về một Model hoặc null, ví dụ:
@property Profile|null $profile.
5. Cập nhật PHPDocs khi thay đổi Model
Giữ PHPDocs luôn đồng bộ với cấu trúc bảng trong database và các thay đổi trong Model (thêm, xóa, sửa thuộc tính, relationship, scope). Có thể dùng công cụ như barryvdh/laravel-ide-helper để tự động sinh phần PHPDocs cơ bản, sau đó bổ sung thủ công các mối quan hệ phức tạp.
Type Hinting và Strict Types
- Thêm
declare(strict_types=1);vào đầu mỗi file PHP. - Chỉ định kiểu trả về rõ ràng cho mọi phương thức.
- Cung cấp PHPDoc tương thích với PHPStan cho tất cả thuộc tính và phương thức.
Ví dụ model với custom builder:
php
<?php
declare(strict_types=1);
namespace Modules\Masterdata\Product\Infrastructure\Models;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Modules\Masterdata\Product\Infrastructure\Builders\ProductBuilder;
use Modules\Masterdata\Product\Infrastructure\Enums\ProductType;
/**
* Class Product
*
* @property string $id
* @property string $name
* @property ProductType $type
* @property string $product_category_id
* @property CarbonImmutable $created_at
* @property CarbonImmutable $updated_at
* @property-read Collection<int, Spec> $specs
*
* @method static ProductBuilder query()
*/
final class Product extends Model
{
public $incrementing = false;
protected $fillable = [
'id',
'name',
'type',
'product_category_id',
];
protected $keyType = 'string';
protected $table = 'products';
public function newEloquentBuilder($query): ProductBuilder
{
return new ProductBuilder($query);
}
protected function casts(): array
{
return [
'type' => ProductType::class,
];
}
}Class Design
- Đánh dấu các lớp là
finaltrừ khi thực sự cần mở rộng. - Tránh bình luận nội tuyến; dựa vào tên biến và tên phương thức rõ ràng.
php
<?php
declare(strict_types=1);
namespace App\Services;
final class OrderService
{
public function getOrderItems(): Collection
{
return $this->fetchOrderItems();
}
}Strict Model
Để phát hiện sớm các lỗi liên quan đến Eloquent Model như lazy loading không chủ ý, ghi đè hoặc bỏ qua thuộc tính ngầm định, Laravel cung cấp cơ chế Strict Mode. Kích hoạt chế độ này sẽ giúp tự động ném ngoại lệ khi:
- Lazy Loading: Tự động truy vấn quan hệ khi chưa được
->with()(eager load), dễ dẫn đến vấn đề N+1. - Silently Discarding Attributes: Bỏ qua các thuộc tính không được phép gán mà không thông báo.
- Accessing Missing Attributes: Truy cập vào thuộc tính không tồn tại trên model.
Đăng ký trong AppServiceProvider
php
<?php
declare(strict_types=1);
namespace App\Providers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;
final class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
Model::shouldBeStrict(true);
}
}Chi tiết 3 phương thức
preventLazyLoading($shouldBeStrict)
- Khi bật, Eloquent sẽ ném
\Illuminate\Database\Eloquent\LazyLoadingViolationExceptionnếu cố gắng truy cập quan hệ chưa eager load.
php
$user = User::query()->first();
$user->posts; // ✗ ném LazyLoadingViolationExceptionpreventSilentlyDiscardingAttributes($shouldBeStrict)
- Khi bật, việc mass assignment với các thuộc tính không nằm trong
$fillablesẽ ném\Illuminate\Database\Eloquent\MassAssignmentExceptionthay vì âm thầm bỏ qua.
php
$data = ['id' => '123', 'name' => 'Test'];
User::query()->create($data);preventAccessingMissingAttributes($shouldBeStrict)
- Khi bật, truy cập thuộc tính không tồn tại sẽ ném
\Illuminate\Database\Eloquent\MissingAttributeException.
php
$user = User::query()->first();
$user->nonExisting; // ✗ ném MissingAttributeExceptionMọi hành vi tiềm ẩn lỗi liên quan đến Model đều được phát hiện ngay trong quá trình phát triển, giúp giảm thiểu bug và bảo đảm tính ổn định cho ứng dụng. Đảm bảo Laravel >= 9.0 để sử dụng các phương thức strict này.
Tóm lại, việc viết PHPDocs chính xác cho Model giúp code rõ ràng, tăng cường type safety và cải thiện chất lượng kiểm thử tự động. Luôn kết hợp với strict types, PSR-12 và Laravel Pint để codebase nhất quán.