I am using Laravel 5.1 and would like to access an array on the Model from the Trait when the Model before the model uses the appends
array.
I would like to add certain items to the appends array if it exists from my trait. I don't want to edit the model in order to achieve this. Are traits actually usable in this scenario or should I use inheritance?
array_push($this->appends, 'saucedByCurrentUser');
Here is how my current setup works.
Trait
<?php namespace App;
trait AwesomeSauceTrait {
/**
* Collection of the sauce on this record
*/
public function awesomeSauced()
{
return $this->morphMany('App\AwesomeSauce', 'sauceable')->latest();
}
public function getSaucedByCurrentUserAttribute()
{
if(\Auth::guest()){
return false;
}
$i = $this->awesomeSauced()->whereUserId(\Auth::user()->id)->count();
if ($i > 0){
return true;
}
return false;
}
}
Model
<?php namespace App;
use App\AwesomeSauceTrait;
use Illuminate\Database\Eloquent\Model;
class FairlyBlandModel extends Model {
use AwesomeSauceTrait;
protected $appends = array('age','saucedByCurrentUser');
}
What I would like to do is something to achieve the same effect as extending a class. I have a few similar traits, so using inheritance gets somewhat ugly.
trait AwesomeSauceTrait {
function __construct() {
parent::__construct();
array_push($this->appends, 'saucedByCurrentUser');
}
}
I have seen some workarounds for this, but none of them seem better/cleaner than just adding the item to the array manually. Any ideas are appreciated.
Update
I discovered this way of accomplishing what I need for one trait, but it only works for one trait and I don't see an advantage of using this over inheritance.
trait
protected $awesomeSauceAppends = ['sauced_by_current_user'];
protected function getArrayableAppends()
{
array_merge($this->appends, $this->awesomeSauceAppends);
parent::getArrayableAppends();
}
How I am currently handling my Model, for what it is worth.
model
public function __construct()
{
array_merge($this->appends, $this->awesomeSauceAppends);
}
Traits are sometimes described as "compiler-assisted copy-and-paste"; the result of using a Trait can always be written out as a valid class in its own right. There is therefore no notion of
parent
in a Trait, because once the Trait has been applied, its methods are indistinguishable from those defined in the class itself, or imported from other Traits at the same time.Similarly, as the PHP docs say:
As such, they are not very suitable for situations where you want to mix in multiple variants of the same piece of behaviour, because there is no way for base functionality and mixed in functionality to talk to each other in a generic way.
In my understanding the problem you're actually trying to solve is this:
$appends
array matching these methodsOne approach would be to continue to use Traits, and use Reflection to dynamically discover which methods have been added. However, beware that Reflection has a reputation for being rather slow.
To do this, we first implement a constructor with a loop which we can hook into just by naming a method in a particular way. This can be placed into a Trait of its own (alternatively, you could sub-class the Eloquent
Model
class with your own enhanced version):Then any number of Traits implementing differently named
extraConstruct
methods:Finally, we mix in all the traits into a plain model, and check the result:
We can set the initial content of
$appends
inside the decorated model itself, and it will replace theBaseModel
definition, but not interrupt the other Traits:However, if you over-ride the constructor at the same time as mixing in the
AppendingGlue
, you do need to do a bit of extra work, as discussed in this previous answer. It's similar to callingparent::__construct
in an inheritance situation, but you have to alias the trait's constructor in order to access it:This can be avoided by inheriting from a class which either exists instead of the
AppendingGlue
trait, or already uses it:Here's a live demo of all of that put together.
I thought I'd add an update for 2019 since this was one of the first discussions that popped up when trying to do a similar thing. I'm using Laravel 5.7 and nowadays Laravel will do the reflection that IMSoP mentioned.
After the trait has been booted, Laravel will then call initializeTraitName() on the constructed object (where TraitName is the full name of the trait).
To add extra items to
$appends
from a trait, you could simply do this...KISS:
I don't see any reason why you should use trait when your are simply appending attributes.
I would only recommend using trait without a constructor like you were doing, only if you model is getting pretty bulky and you wish to slim down things.
Please also note this not the correct way of appending attribute
You could do this:
Appends attribute names should the snake_case of its method Name
Edited:
The idea behind appends is to dynamically add fields that doesn't exist in your database table to your model so after you can do like: