I have a Laravel 7 project with the need of a lot of data transformation from the model to the views before being displayed.
I thought about using Laravel Accessors and use them directly in my blade.php files.
But when I finished dealing with a simple html table, I looked at my code and I think there is too many accessors and even some of them have a difficult name to read.
@foreach($races as $race)
<tr>
<td>{{ $race->display_dates }}</td>
<td>{{ $race->display_name }}</td>
<td>{{ $race->type->name }}</td>
<td>{{ $race->display_price }}</td>
<td>{{ $race->display_places }}</td>
<td>{{ $race->online_registration_is_open ? 'Yes' : 'No' }}</td>
</tr>
@endforeach
public function show(Group $group)
{
$races = $group->races;
$races->loadMissing('type'); // Eager loading
return view('races', compact('races'));
}
// Accessors
public function getOnlineRegistrationIsOpenAttribute()
{
if (!$this->online_registration_ends_at && !$this->online_registration_starts_at) return false;
if ($this->online_registration_ends_at < now()) return false;
if ($this->online_registration_starts_at > now()) return false;
return true;
}
public function getNumberOfParticipantsAttribute()
{
return $this->in_team === true
? $this->teams()->count()
: $this->participants()->count();
}
// Accessors mainly used for displaying purpose
public function getDisplayPlacesAttribute()
{
if ($this->online_registration_ends_at < now()) {
return "Closed registration";
}
if ($this->online_registration_starts_at > now()) {
return "Opening date: " . $this->online_registration_starts_at;
}
return "$this->number_of_participants / $this->max_participants";
}
public function getDisplayPriceAttribute()
{
$text = $this->online_registration_price / 100;
$text .= " €";
return $text;
}
public function getDisplayDatesAttribute()
{
$text = $this->starts_at->toDateString();
if ($this->ends_at) { $text .= " - " . $this->ends_at->toDateString(); }
return $text;
}
public function getDisplayNameAttribute()
{
$text = $this->name;
if ($this->length) { $text .= " $this->length m"; }
if ($this->elevation) { $text .= " ($this->elevation m)"; }
return $text;
}
This code is working but I think it has a lot of cons : readability, mistake are possible, for example if the associated DB table has a name
column while I am creating a getDisplayNameAttribute
accessor here.
And that's only a start, I think I will need 30-40 more accessors for the other views...
Also, I will need to use some of them many time, for example getDisplayNameAttribute
could be used in a regular page and an admin page (and maybe more).
I also looked at JsonResource and ViewComposer but JsonResource
seems to be for APIs
while ViewComposer
seems to be especially for Views
.
I also thought about prefixing the accessors with something like acc_
to reduce mistakes with existing db column :
public function getAccDisplayNameAttribute() { ... };
But I really don't think that's a solution, I'm not even sure if what I am doing is right or wrong. I also searched for best practices over the internet, without success.
Accessors and mutators allow you to format Eloquent attributes when retrieving them from a model or setting their value. For example, you may want to use the Laravel encrypter to encrypt a value while it is stored in the database, and then automatically decrypt the attribute when you access it on an Eloquent model.
Laravel Accessors and Mutators are custom, user defined methods. Accessors are used to format the attributes when you retrieve them from database. Whereas, Mutators are used to format the attributes before saving them into the database.
Defining A Mutator The mutator will receive the value that is being set on the attribute, allowing you to manipulate the value and set the manipulated value on the Eloquent model's internal $attributes property. So, for example, if we attempt to set the first_name attribute to Sally : $user = App\User::find(1);
Laravel accessors and mutators are custom, user defined methods that allow you to format Eloquent attributes. Accessors are used to format attributes when you retrieve them from the database, while mutators format the attributes before saving them to the database.
Laravel provides Accessors and Mutators in Eloquent. First of all, I will give you a simple definition of what is Accessors and Mutators in Laravel Eloquent, Then I will give you a very simple example where you will understand what it works. "Accessors create a custom attribute on the object which you can access as if it were a database column."
Accessors, mutators, and attribute casting allow you to transform Eloquent attribute values when you retrieve or set them on model instances. For example, you may want to use the Laravel encrypter to encrypt a value while it is stored in the database, and then automatically decrypt the attribute when you access it on an Eloquent model.
Blade Templates - Laravel - The PHP Framework For Web Artisans Laravel is a PHP web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing you to create without sweating the small things. Skip to content Prologue Release Notes Upgrade Guide
Check out Laravel Livewire. Displaying Data You may display data that is passed to your Blade views by wrapping the variable in curly braces. For example, given the following route: Route::get('/', function(){ returnview('welcome',['name'=>'Samantha']); You may display the contents of the namevariable like so: Hello, {{$name}}.
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
$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
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.
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;
}
}
class Race {
// The Accessors class instance
public $accessors;
function __construct()
{
$this->accessors = new \App\Accessors\RaceAccessors($this);
}
}
@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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With