March 22, 2025

Understanding Model Properties in Laravel

Improve Your Laravel Models with Proper PHPDoc Annotations for Attributes and Relationships

Tutorial
Understanding Model Properties in Laravel

How @property and @property-read Improve Code Readability and IDE Support

Introduction

In Laravel models, we often deal with dynamically resolved properties, such as Eloquent attributes, casts, and accessors. IDEs may struggle to recognize these properties unless we explicitly document them using PHPDoc annotations like @property and @property-read.

This article explains:

  • The purpose of @property and @property-read
  • How they improve IDE support
  • When to use each one with examples

Why Use @property or @property-read in Laravel Models?

Laravel models dynamically handle many properties based on database columns and relationships. However, IDEs do not inherently recognize them. Without explicit annotations, you lose features like autocomplete, type hinting, and static analysis.

By defining these annotations in the model’s PHPDoc, you can:

  • Improve developer experience with better autocomplete.
  • Avoid unnecessary method calls like $this->getAttribute().
  • Enable static analysis tools like PHPStan or Psalm to detect type errors.

Understanding @property and @property-read

@property

The @property annotation is used for readable and writable properties. It tells the IDE that a property can be both accessed and modified.

Example:

/** * @property int $id * @property string $name * @property string $email * @property Carbon $created_at * @property Carbon $updated_at */ class User extends Model { }

Here, we assume $id, $name, $email, $created_at, and $updated_at exist as fillable or guarded attributes in the database and can be modified.

When to Use @property

  • When the property is a database column and can be both read and written.
  • When using Eloquent attributes that are mass assignable.
  • When defining casted attributes.

@property-read

The @property-read annotation is used for read-only properties, meaning they can be accessed but not modified.

Example:

/** * @property-read int $id * @property-read string $full_name * @property-read CarbonImmutable $created_at * @property-read CarbonImmutable $updated_at */ class User extends Model { public function getFullNameAttribute() { return "{$this->first_name} {$this->last_name}"; } }

In this example:

  • $id is read-only because it's an auto-incremented primary key.
  • $full_name is an accessor, meaning it's computed dynamically and cannot be set directly.
  • $created_at and $updated_at are CarbonImmutable instances, meaning they should not be modified.

When to Use @property-read

  • For accessors (custom attributes defined with getAttributeNameAttribute).
  • For immutable properties (e.g., timestamps cast to CarbonImmutable).
  • For properties that exist but cannot be assigned directly.

Example: Combining @property and @property-read

Consider a User model with the following attributes and computed properties:

/** * @property int $id * @property string $name * @property string $email * @property Carbon $created_at * @property Carbon $updated_at * @property-read string $full_name */ class User extends Model { protected $fillable = ['name', 'email']; public function getFullNameAttribute() { return "{$this->first_name} {$this->last_name}"; } }

Explanation:

  • $id, $name, and $email are writable database attributes, so we use @property.
  • $created_at and $updated_at are castable to Carbon, so they are marked as @property.
  • $full_name is a computed attribute (accessor) that cannot be modified, so we use @property-read.

Example: Model with Relationships

If a model has relationships, you can document them using @property-read.

/** * @property int $id * @property string $title * @property-read User $author * @property-read Collection<Comment> $comments */ class Post extends Model { public function author() { return $this->belongsTo(User::class, 'user_id'); } public function comments() { return $this->hasMany(Comment::class); } }

Why Use @property-read for Relationships?

  • $author and $comments are Eloquent relationships, which means they are not real database columns but dynamically loaded.
  • They cannot be assigned directly, only retrieved via the relationship methods.

Best Practices for Using @property and @property-read

  1. Use @property for real attributes stored in the database that can be modified.
  2. Use @property-read for computed properties (accessors) and relationships.
  3. Always type-hint properties properly (int, string, Carbon, Collection).
  4. Document relationships to improve IDE support.
  5. If an attribute is writable sometimes and read-only in other cases, prefer @property.

Summary

Using @property and @property-read in Laravel models:

  • Enhances IDE support with autocompletion and type checking.
  • Helps static analysis tools detect issues early.
  • Makes relationships, accessors, and attributes more readable.

Use @property for real database columns that can be modified.
Use @property-read for accessors, computed properties, and relationships that should not be directly modified.

By following these practices, your Laravel models will be cleaner, more maintainable, and easier to work with.

Did you find this article helpful? Share it!