Understanding Model Properties in Laravel
Improve Your Laravel Models with Proper PHPDoc Annotations for Attributes and Relationships

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
areCarbonImmutable
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
- Use
@property
for real attributes stored in the database that can be modified. - Use
@property-read
for computed properties (accessors) and relationships. - Always type-hint properties properly (
int
,string
,Carbon
,Collection
). - Document relationships to improve IDE support.
- 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.