This article was originally published at: https://phpfour.com/structuring-laravel-model-accessor-mutator-getter-setter/
In a few recent projects, we have experimented with a slight alteration of Eloquent Models and have found some great value. I thought to share this in case someone else benefits the same way we did ๐
Letโs first bring a Product model to life for an imaginary e-commerce store:
php artisan make:model Product
We now have our brand new Product model under App namespace:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
//
}
We do some initial setup at the beginning:
- Enforcing strict types
- Marking class as
final
- Adding logical code sections
...resulting in this:
<?php
declare(strict_types=1);
namespace App;
use Illuminate\Database\Eloquent\Model;
final class Product extends Model
{
#-----------------------------------------------------------------
# Class Constants
#-----------------------------------------------------------------
//
#-----------------------------------------------------------------
# Class Properties
#-----------------------------------------------------------------
//
#-----------------------------------------------------------------
# Relationships
#-----------------------------------------------------------------
//
#-----------------------------------------------------------------
# Accessors and Mutators
#-----------------------------------------------------------------
//
}
Next we adhere to some general team conventions regarding table and primary key name and include the necessary relations:
<?php
//
final class Product extends Model
{
//
#-----------------------------------------------------------------
# Properties
#-----------------------------------------------------------------
/**
* @var string
*/
protected $table = 'products';
/**
* @var string
*/
protected $primaryKey = 'product_id';
#-----------------------------------------------------------------
# Relationships
#-----------------------------------------------------------------
/**
* @return HasOne
*/
public function currency(): HasOne
{
return $this->hasOne(Currency::class, 'currencyCode', 'currency');
}
//
}
You may be wondering whatโs new here, this is standard Laravel - youโre entirely correct. Iโve included these details to make sure someone new can also follow along ๐
At this stage we introduce our own style accessors and mutators to the model (also known as getters/setters):
<?php
//
final class Product extends Model
{
//
#-----------------------------------------------------------------
# Accessors and Mutators
#-----------------------------------------------------------------
/**
* @return string
*/
public function getName(): string
{
return $this->getAttribute('name');
}
/**
* @param string $name
*/
public function setName(string $name): void
{
$this->setAttribute('name', $name);
}
/**
* @return float|null
*/
public function getPrice(): ?float
{
return $this->getAttribute('price');
}
/**
* @param float|null $value
*/
public function setPrice(?float $value): void
{
$this->setAttribute('price', $value);
}
/**
* @return bool
*/
public function isActive(): bool
{
return $this->getAttribute('is_active');
}
//
}
Thatโs pretty straight-forward, but you may be wondering about the verbosity and why would someone write all these methods by hand.
What's the benefit?
Well, we have seen these benefit since introducing this overall structure:
- Model is divided into logical sections, so easy to navigate
- Model properties are available through IDE auto-completion
- Model properties are type-hinted, thus using the power of strict type
- It still preserves the power of Eloquent, so no hacky overrides
In our team we have seen overall improvement in DX (developer experience) as a result of these small changes. There are less errors, code comprehension has improved and cognitive load has reduced while working with someone elseโs code.
Why not just use the public properties?
One question that comes up naturally when I present this to anyone: why not just use the public properties? Well, in my opinion there are a few limitations:
The data type of a given property cannot be enforced, you can set a float value to a property that is defined to store int in the DB
The return type of a given property cannot be determined, so on a language level you don't know if a status field is supposed to return string or int
Property level data validation is difficult, you can set a negative value to a property that is clearly invalid (ex: negative price of a product)
If a property column name is changed, that change needs to be done manually in all places where the property was accessed; as the properties are dynamic, thus cannot make use of IDE usage search
Closing Thoughts
You may want to give this a try and see if this benefits in any way. This heavily favors the strong typing of PHP 7 so teams who has preference towards that would get the most out of this. And lastly, your mileage may vary ๐
Share your ideas and thoughts with me in Twitter!
Top comments (0)