Eloquent nested complex relationship - php

I have some trouble with chaining relationships. I want to chain three of them, but this is not working properly:
return UserModel::with('cars.pieces.attributes')
I want to retrieve a user with its cars. He chose a car which have pieces and for each pieces he chose an attribute.
With only cars.pieces. I have my user, then the array of cars then the array of pieces for this car. When I add attributes, I have attributes not for pieces of cars of users but attributes for pieces whatever cars it is.
It seems like the relationship is only looking for the previous relation and not the whole packet.
public function cars(){
return $this->belongsToMany(CarsModel::class, 'user_cars', 'id_user','id_cars');
}
Then
public function pieces(){
return $this->belongsToMany(PiecesModel::class, 'cars_pieces', 'id_cars','id_pieces')
}
And finally :
public function attributes(){
return $this->belongsToMany(AttributeModel::class, 'user_cars_pieces_attributes', 'id_attribute', 'id_piece')
}
The last entity is using 4 fields for the primary key :
id_user, id_car, id_attribute, id_piece
What could be a way to retrieve attributes for pieces of cars of the user?
Thank you for helping!

You can pass a function to your eager loading attributes:
return UserModel::with(['cars.pieces.attributes' => function ($query) {
$query->where('id_user', DB::raw('users.id'))->where('id_car', DB::raw('cars.id'));
}]);
I haven't tested this but I think it should work.
Remember to import DB Facade: use Illuminate\Support\Facades\DB;

Related

Is it better to duplicate values in a pivot table and add different fields or create another table connected to the pivot table?

I currently have this schema:
trainings
id
name
speakers
id
first_name
last_name
training_speaker
id
training_id
speaker_id
training_speaker_dates
id
training_speaker_id
date
time
When getting trainings with their speakers, I use the training_speaker pivot table.
class Training {
public function speakers() {
return $this->belongsToMany('App\Speaker')->using('App\TrainingSpeaker');
}
}
A speaker in a training can have multiple dates and times. That's why I have added training_speaker_dates though it's getting hard to query the training_speaker_dates from the speakers method of the Training class.
I'm thinking of this approach
class Training {
public function speakers() {
return $this->hasMany('App\TrainingSpeaker')->with('speaker')->with('dates');
}
}
class TrainingSpeaker {
public function speaker() {
return $this->belongsTo('App\Speaker');
}
public function dates() {
return $this->hasMany('App\TrainingSpeakerDate');
}
}
In that way, I can easily get the speakers with their dates. But is there a better approach?
I'm also thinking of removing training_speaker_dates table and add the date and time fields in the training_speaker table since I can just duplicate values (same training_id and speaker_id) but with unique primary keys.
I think you have better structure now.
You will lose convinience of belongToMany relation and it's convinient methods.
As I understand you have a problem with eager loading.
You could solve it with this solution: https://github.com/laravel/ideas/issues/1089.
Then you could eager load pivot relations in with() and load() methods:
$trainings = Training::with('speakers.pivot.dates')->get();
#foreach($trainings->speakers as $speaker)
#foreach($speaker->pivot->dates as $date)
#endforeach
#endforeach
Pay attention, that example from github uses deprecated helpers str_* and arr_*. You should replace it with Str:: and Arr:: facades.

how to get a belongsToMany() query from a collection ? MySQL/Laravel

I'm using Laravel 5.4.22 (the newest one). In MySQL, I have two tables, tag_categories and tags, which form a many-to-many relationship. What I need is a query which returns all the tags for the selected categories. I know how to solve this when I have only one object, and I know how to solve this with querying and looping each of those objects, but there has to be a query or eloquent based solution for the whole thing?
I understand the code below doesn't work because I'm using ->belongsToMany on a collection rather than an object, but how to I bridge this gap the simplest way?
$resultingTags = TagCategory::whereIn('id', $chosenCategoriesIds)
->belongsToMany(Tag::Class)->get();
dd($resultingTags);
belongsToMany generally belongs in the model class, not a method called on the fly. When looking to eager load the relationship, you then call the with() method on the query builder.
https://laravel.com/docs/5.4/eloquent-relationships#many-to-many
ex:
class User extends Model
{
/**
* The roles that belong to the user.
*/
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
// Query
$users = User::with('roles')->get();
$rolesOfFirstUser = $users->first()->roles;
If you're trying to get all the tags of the given categories, then you should be querying tags, not tag_categories.
Tag::whereHas('categories', function ($query) use ($chosenCategoriesIds) {
$query->whereIn('id', $chosenCategoriesIds);
})->get();
This is One-to-many relation
Define relation at TagCategory model at app/TagCategory.php
public function tags()
{
return $this->hasMany('App\Tag');
}
And handle at your Controller
$resultingTags = TagCategory::whereIn('id', $chosenCategoriesIds)->with(['tags'])->get();
If you want define Many-To-Many relation for this case
You need to have 3 tables tags, tag_categories, tag_tag_category
Define relation at TagCategory model at app/TagCategory.php
public function tags()
{
return $this->belongsToMany('App\Tag', 'tag_tag_category', 'tagcategory_id', 'tag_id');
}
And handle at your Controller
$resultingTags = TagCategory::whereIn('id', $chosenCategoriesIds)->with(['tags'])->get();

Yii2. Models related

I have 3 models: Items, Serials and SerialsCategories. When I show Item form (to create or update) I need to show the serials which belongs to a categoryId selected in a previous step. A serial can belong to more than one category.
Right now I have on my Item model:
public function getSerialsTypeByCategory() {
return (new SerialType)->getByCategory($this->itemCategoryId);
}
On my SerialType model:
public function getByCategory($itemCategoryId) {
return SerialTypeItemCategory::find()->select(['serialTypeId'])->where(['itemCategoryId' => $itemCategoryId])->all();
}
This is working, it does what I need but ... Is this the proper way? is there a better way?
it's not wrong what you are doing. but there is something more -
check this link:
Working with Relational Data
if you use ->hasOne and ->hasMany to define relations, your model gains some extra benefits, like joining with lazy or eager loading:
Item::findOne($id)->with(['categories'])->all();
with a relation, you can also use ->link and ->unlink, to add/delete related data without having to think about linked fields.
Further, it is easy to define relations via junction table:
class Order extends ActiveRecord
{
public function getItems()
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->viaTable('order_item', ['order_id' => 'id']);
}
}

Making a join with Eloquent based on a list of ids

I have set up two Eloquent models with a belongsToMany relation in both directions. This works perfectly fine but now I need to make a more detailed query within the relationship. To keep things simple, let's say the tables have the following columns:
wigs:
- id
- name
- type
heads:
- id
- name
heads_wigs:
- head_id
- wig_id
Now I need to fetch a series of wigs with a given type within a list of given head id's. So what I have is:
a wig type
an array with head id's
I am using Eloquent outside of laravel so I want to start building the ORM query on the model. Something like:
Wig::where( 'type', $type )-> ... //here the code to make the join on head id's
This is where my understanding of SQL lacks but I suppose this should not be too hard to achieve.
UPDATE:
To rephrase it in a sentence: get all wigs with type=wig_type that have a belongsToMany relationship with the heads [1,2,3,5,6,8,9]. I want to end up with a collection of wigs by performing a single query.
You could do something like this
Head::whereIn('id', $head_id_array)->load(['wig' => function($query) use ($wig_type) {
$query->where('type', $wig_type);
}])->get();
or
Wig::where('type', $wig_type)->load(['heads' => function($query) use ($head_id_array) {
$query->whereIn('id', $head_id_array);
}])->get();
if I understand your question correctly.
Or
$wig = Wig::where('type', $wig_type)->get();
$heads = $wig->heads()->whereIn('id', $head_id_array)->get();
$matching_head_ids = $heads->lists('id');
$wig->matching_head_ids = $matching_head_ids;
That way, the wig object returned will have an array of matching head ids.
you could put this in a method on your wig model:
class Wig extends Eloquent {
public function heads()
{
return $this->belongsToMany('Head');
}
/**
* #param array $head_ids Provided head id array
* #return array Array of this wigs head id's which match the provided head ids
*/
public function matchingHeadIds($head_ids)
{
return $this->heads()->whereIn('id', $head_ids)->get()->lists('id');
}
}
then use it like so
$wig = Wig::where('type', $wig_type);
$wig->matchingHeadIds($head_ids);
Edit
This is not a simple task for an ORM like eloquent, since it treats each record like a row from the table, so something like this wouldn't work:
$wigs = Head::whereIn('id', $head_ids)->wigs()->where('type', $wig_type)->get();
There is a whereHas method available which you can use like so:
Wig::where('type', $wig_type)->whereHas('heads', function ($query) use ($head_ids) {
$query->whereIn('id', $head_ids);
})->get();
which should give you the results you need.

Laravel Pivot Tables - accessing from inverse relation-table

UPDATE: Apparently $problem->userRatings()->count(); returns 1, so a record does exist, but the script still doesn't enter the foreach loop for some reason...
UPDATE 2/Solution: I feel incredibly foolish, but the reason it isn't working, is that I was calling foreach($problem->userRatings() as $rating){ when it should be foreach($problem->userRatings as $rating){ (dynamic property)
In my database I have problem_ratings, problems, and users. I have models for users and problems, and problems_ratings is a pivot table between problems and users with an extra 'content' column that stores the numeric rating value (1-5).
The many-to-many relation in my Problem model:
public function userRatings(){
return $this->belongsToMany('User', 'problem_ratings', 'author_id', 'problem_id')->withPivot('content');
}
The many-to-many relation in my User model:
public function problemRatings(){
return $this->belongsToMany('Problem', 'problem_ratings', 'author_id', 'problem_id')->withPivot('content');
}
When creating a problem_ratings element, I attach it to my user model like so:
$user->problemRatings()->attach($problem->id, array('content' => $val));
I can access the pivot table through the user, but when I try this:
foreach($problem->userRatings() as $rating){
echo "In average loop";
$newAverage = ($rating->pivot->content) + $newAverage;
}
it doesn't find any records, even though some exist in the database. Am I using these relations correctly?
Thanks in advance!
In the Problem model I think your relationship should looks something like :
public function userRatings(){
return $this->belongsToMany('User', 'problem_ratings', 'problem_id', 'author_id')->withPivot('content');
}
When using a foreach loop over something like this, you need to use dynamic properties (ie. without parentheses):
foreach($problem->userRatings as $rating){ instead of foreach($problem->userRatings() as $rating){

Categories