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

  1. 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

  1. 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.