Laravel - Lot of Accessors (Mutators) for rendering Views with Blade
I have 2 solutions for this case which ive tested on my local machine. Though am not sure if they comply with the best practices or design principles, that has to be seen, but i am sure that they will organize the code in a managable way.
First is you can create an accessor that is an array and club all your logic inside it which will compute and return the array. This can work for 4-5 attributes maybe, and you will have to make changes to your views to access the array instead of the properties. Code example 1 is given below
Second way is to create a separate class which will house all the different computing logic as methods. Lets say you make a ModelAccessor class, you can then create accessors in your Model class just the way you are doing now and return ModelAccessor->someMethod from inside each of them. This will add some neatness to your Model class and you can manage your computation logic from the class methods easily. Example code 2 below may make it more clear
- Example 1 for arrays as accessors
Lets call the returned attribute
$stats
public function getStatsAttribute(){
$stats = [];
if (!$this->online_registration_ends_at && !$this->online_registration_starts_at) $o=false;
if ($this->online_registration_ends_at < now()) $o = false;
if ($this->online_registration_starts_at > now()) $o = false;
$o= true;
$stats['online_registration_is_open'] = $o;
$stats['number_of_participants'] = $this->in_team === true
? $this->teams()->count()
: $this->participants()->count();
return $stats;
}
You will have to change your view files to use the stats
array
<td>{{ $race->stats['number_of_participants'] }} </td>
<td>{{ $race->stats["online_registration_is_open"] ? 'Yes' : 'No' }}</td>
This might become messy for large num of attributes. To avoid that, you can have multiple arrays grouping similar things($stats, $payment_details, $race_registration, etc) or you can use a separate class to manage all of them, as in the next example
- Example 2, Separate class with methods to set different attributes
class ModelAccessors
{
protected $race;
function __construct(\App\Race $race){
$this->race = $race;
}
public function displayPrice()
{
$text = $race->online_registration_price / 100;
$text .= " €";
return $text;
}
Then inside the model
public function getDisplayPriceAttribute()
{
$m = new ModelAccessor($this);
return $m->displayPrice();
}
Using this you wont have to update your blade files
3. In case you have 30-40 accessors then i think maintaining a separate class with all the methods will be much simpler. In addition to that you can create the array of attributes from the class itself and call it like this,
class ModelAccessors
{
protected $race;
protected $attributes;
function __construct(\App\Race $race){
$this->race = $race;
$this->attributes = [];
}
public function displayPrice()
{
$text = $race->online_registration_price / 100;
$text .= " €";
$this->attributes['display_price'] = $text;
}
public function allStats(){
$this->displayPrice();
$this->someOtherMethod();
$this->yetAnotherMethod();
// ..
// you can further abstract calling of all the methods either from
// other method or any other way
return $this->attributes;
}
// From the model class
public function getStatsAccessor()
{
$m = new ModelAccessor($this);
// Compute the different values, add them to an array and return it as
// $stats[
// "display_price"= "234 €",
// "number_of_participants" = 300;
// ]
return $m->allStats()
}
You can create a class that specifically handle your transformations. Then you can load an instance of this class in the Model constructor. You can also make your accessors classes use PHP magic getters if you wish.
Accessors Class
class RaceAccessors {
// The related model instance
private $model;
// Already generated results to avoid some recalculations
private $attributes = [];
function __construct($model)
{
$this->model = $model;
}
// Magic getters
public function __get($key)
{
// If already called once, just return the result
if (array_key_exists($key, $this->attributes)) {
return $this->attributes[$key];
}
// Otherwise, if a method with this attribute name exists
// Execute it and store the result in the attributes array
if (method_exists(self::class, $key)) {
$this->attributes[$key] = $this->$key($value);
}
// Then return the attribute value
return $this->attributes[$key];
}
// An example of accessor
public function price()
{
$text = $this->model->online_registration_price / 100;
$text .= " €";
return $text;
}
}
Model Class
class Race {
// The Accessors class instance
public $accessors;
function __construct()
{
$this->accessors = new \App\Accessors\RaceAccessors($this);
}
}
View
@foreach($races as $race)
<tr>
<td>{{ $race->accessors->price }}</td>
[...]
</tr>
@endforeach
I did not set a type for the $model
variable here because you could use the same accessors class
for other models that need to transform some fields in the same way than this accessors class does.
For example, the price accessor can work for a Race (in your case) but it could also work for other models, that way you can imagine creating groups of accessors used by many models without changing too much code to handle that.