Skip to content

Using Models ​

Introduction ​

PHPDocs are essential when working with Eloquent models in Laravel. Accurate, complete PHPDocs do more than document code—they improve day-to-day development, especially type safety and IDE support.

Basic PHPDocs structure for a model ​

Example PHPDocs for a User model:

php
/**
 * @property int $id
 * @property string $name
 * @property string $email
 * @property string|null $password
 * @property CarbonImmutable|null $email_verified_at
 * @property Collection<int, Post> $posts
 * @property Profile|null $profile
 */
final class User extends Model {}

Important rules and guidelines ​

1. Always document @property ​

  • Required: Declare every column from the corresponding database table as a @property in the model's PHPDocs.
  • Type safety and type hinting: This helps IDEs (such as PhpStorm) suggest code, check types when accessing attributes, and helps static analysis tools like PHPStan catch potential errors.
  • Correct types: Use precise types. For nullable fields, use a union type (for example, string|null).

2. Use casts() for typing ​

  • Laravel 10+: Prefer the casts() method over the $casts property for cast declarations.
  • Datetime casting: Always cast datetime fields (for example, created_at, updated_at, deleted_at, and fields ending in _at or _date) to datetime.
  • If CarbonImmutable is registered in AppServiceProvider via Date::use(CarbonImmutable::class), a datetime cast returns a CarbonImmutable instance.
  • Cast other fields when needed (for example, boolean, integer, float, array, object, collection, Enum).

Example casts() method on a WorkOrder model:

php
protected function casts(): array
{
    return [
        'start_time' => 'datetime',
        'end_time' => 'datetime',
        'forced_stop_at' => 'datetime',
        'has_oqc' => 'boolean',
        'status' => WorkOrderStatus::class,
        'options' => 'array',
    ];
}

3. Keep business logic out of models ​

  • Models should define data structure, casts, relationships, and local scopes.
  • Avoid placing complex business logic such as price calculation, order processing, or email sending directly on the model.
  • Move business logic into dedicated classes such as Actions, Jobs, or Services under an action-based architecture.

4. Document relationships in PHPDocs ​

  • Use @property to declare relationships defined on the model.
  • For HasMany, BelongsToMany, MorphMany, and MorphToMany, use a typed Collection, for example: @property Collection<int, Post> $posts.
  • For HasOne, BelongsTo, MorphOne, and MorphTo, return a model or null, for example: @property Profile|null $profile.

5. Keep PHPDocs in sync when models change ​

Keep PHPDocs aligned with the database table structure and model changes (adding, removing, or updating attributes, relationships, and scopes). Tools such as barryvdh/laravel-ide-helper can generate basic PHPDocs automatically; then add complex relationships manually.

Type hinting and strict types ​

  • Add declare(strict_types=1); at the top of every PHP file.
  • Declare explicit return types on every method.
  • Provide PHPStan-compatible PHPDoc for all properties and methods.

Example model with a custom builder:

php
/**
 * @method static ProductBuilder query()
 */
final class Product extends Model
{
    public function newEloquentBuilder($query): ProductBuilder
    {
        return new ProductBuilder($query);
    }
}

Class design ​

  • Mark classes as final unless extension is genuinely required.
  • Avoid inline comments; rely on clear variable and method names.
php
final class OrderService
{
    public function getOrderItems(): Collection
    {
        return $this->fetchOrderItems();
    }
}

Strict model ​

To catch Eloquent model issues early—such as unintended lazy loading, or silently overriding or discarding attributes—Laravel provides strict mode. Enabling it throws exceptions when:

  • Lazy loading: Automatically querying a relation that was not eager-loaded with ->with(), which often leads to N+1 problems.
  • Silently discarding attributes: Ignoring attributes that are not mass-assignable without notice.
  • Accessing missing attributes: Reading attributes that do not exist on the model.

Register in AppServiceProvider ​

php
Model::shouldBeStrict(true);

Details of the three methods ​

preventLazyLoading($shouldBeStrict) ​

  • When enabled, Eloquent throws \Illuminate\Database\Eloquent\LazyLoadingViolationException if you access a relation that was not eager-loaded.
php
$user = User::query()->first();
$user->posts; // ✗ throws LazyLoadingViolationException

preventSilentlyDiscardingAttributes($shouldBeStrict) ​

  • When enabled, mass assignment with attributes outside $fillable throws \Illuminate\Database\Eloquent\MassAssignmentException instead of silently discarding them.
php
$data = ['id' => '123', 'name' => 'Test'];
User::query()->create($data);

preventAccessingMissingAttributes($shouldBeStrict) ​

  • When enabled, accessing a missing attribute throws \Illuminate\Database\Eloquent\MissingAttributeException.
php
$user = User::query()->first();
$user->nonExisting; // ✗ throws MissingAttributeException

Potential model-related failure modes are surfaced during development, which reduces bugs and improves application stability. Require Laravel >= 9.0 to use these strict methods.

In short, accurate model PHPDocs keep code clear, strengthen type safety, and improve automated test quality. Combine them with strict types, PSR-12, and Laravel Pint for a consistent codebase.

Internal engineering documentation