I haven\'t had much luck sorting this out the Laravel way. So I pose two questions.
Given that I have a Car and that Car can have many Features, but that Features are al
You could use a Many-to-Many relationship between the Listing
and the ListingFeatureValue
models, then group the related listing features for a given listing by their type using the groupBy
Collection method.
The Listing model:
class Listing extends Model {
protected $hidden = [
'features'
];
protected $appends = [
'feature_types'
];
public function features(){
return $this->belongsToMany(ListingFeatureValue::class, 'listings_features', 'listing_id', 'feature_id');
}
public function getFeatureTypesAttribute()
{
return $this->features->groupBy(function ($feature, $key) {
return $feature->type->id;
})->map(function($features, $key){
$type = ListingFeatureType::find($key);
$type->features = $features;
return $type;
})->values();
}
}
The getFeatureTypesAttribute()
is the star of the show here, because you can combine that with the appends
array to force the Eloquent model to append that to any toArray()
calls to the model instance, which is what toJson()
uses when converting your model to JSON.
It may seem a little convoluted to first fetch all the listing values then divide them using the groupBy
and map
collection methods, but there is no native Eloquent mechanism for using hasManyThrough
via many-to-many relationships. There's a different approach here if you don't like this one.
The ListingFeatureValue model:
class ListingFeatureValue extends Model
{
public $table = 'listings_features_values';
public function type()
{
return $this->belongsTo(ListingFeatureType::class, 'feature_type_id');
}
}
I'm showing this model here because the type()
relationship is called in the getFeaturesByTypeAttribute()
method above and didn't want there to be any confusion.
And, just for the sake of completeness, the ListingFeatureType model:
class ListingFeatureType extends Model
{
public $table = "listings_features_types";
public function listings()
{
return $this->hasMany(ListingFeatureValue::class, 'listing_feature_type_id');
}
}
If you wanted to eager-load the listings with their features and types for a full output of all your listings, you could do so like this:
App\Listing::with('features.type')->get()->toJson();
My migration files look like this:
//create_listings_table
Schema::create('listings', function (Blueprint $table) {
$table->increments('id');
$table->string('uuid');
$table->timestamps();
});
//create_listings_features_values_table
Schema::create('listings_features_values', function (Blueprint $table) {
$table->increments('id');
$table->string('listing_feature_text');
$table->integer('listing_feature_type_id')->unsigned();
$table->timestamps();
});
//create_listings_features_types_table
Schema::create('listings_features_types', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
//create_listings_features_table
Schema::create('listings_features', function(Blueprint $table){
$table->integer('listing_id')->unsigned();
$table->integer('listing_feature_id')->unsigned();
});
You can learn more about the Collection methods here:
https://laravel.com/docs/5.3/collections#available-methods
...and eager-loading here:
https://laravel.com/docs/5.3/eloquent-relationships#eager-loading
...and many-to-many relationships here:
https://laravel.com/docs/5.3/eloquent-relationships#many-to-many