Get ID of saved pivot-item - laravel

I would like to solve the following problem:
I have a model (Car) that is loaded as a relation to another model (Driver) with a pivot table.
Car -> belongsToMany Driver (with pivot) -> belongsToMany Accessoires (with Pivot).
The model in turn has relations (accessories) that are loaded with pivot.
The problem: The model Accessories has two pivot tables which can be loaded depending on other factors. If the model Accessories is loaded as a relation of Driver as a relation of Car, the pivot tables should be output correctly.
This is what it looks like at the moment:
collect($request->input('data.driver'))->each(function ($driver) use
(
$car
) {
$ndriver = Driver::findOrFail($driver['id']);
$added = $car->driver()->save($driver, [
'custom' => $driver['custom'],
'title' => $driver['title'],
'factor' => $driver['factor'],
]);
/*Save attributes*/
$added->syncDriverAttributes($driver['attributes']],
$car->id);
});
Now I should actually specify in $added->syncDriverAttributes($driver['attributes'], $car->id); the pivot ID of the created pivot data in $added. Unfortunately I can't find out what the pivot ID is at the moment, because it is not returned in $added.
The problem: I can't find out the last pivot ID from the timestamp either, because all entries have the same timestamp.
So my question is: How can I solve that?

You can of course do it but you should use withPivot method when defining your relationship to tell what additional fields you want to have:
public function driver()
{
return $this->belongsToMany(Driver::class)->withPivot(['id']);
}

Related

Broken Eloquent relationship with extended model

I have a model Asset with documents() { $this->hasMany(Document::class); } through a table data_asset_document. I extend Asset into multiple models, one of which is Equipment. In my seeder for Equipment, I attempt to create a Document bound to the Equipment record:
$asset = Equipment::create([...]);
$document = Document::create([
'name' => "$type Purchase Order",
'tracking_number' => app('incrementer')->current()
]);
$asset->documents()->save($document);
Eloquent produces this query:
update `data_document` set `equipment_id` = 1, `data_document`.`updated_at` = 2019-09-20 14:39:48 where `id` = 1
This is obviously incorrect, since data_document does not have an equipment_id column (Documents "belong to" several models besides Asset). How do I rewrite Asset::documents so that produces the correct mapping, even in its extensions? Or do I need to save my Document through a means other than Asset::documents?
Since your extended asset model is called Equipment, Laravel expects your foreign key to be called equipment_id. You will need to specify the actual foreign key of asset_id in your relationship.
https://laravel.com/docs/6.x/eloquent-relationships#one-to-many
hasMany
documents() {
$this->hasMany(Document::class, 'asset_id');
}
The problem is, I'm not convinced your relationship is really hasMany since you mention what looks like a pivot table data_asset_document as being involved. Many-to-many relationships, like mentioned in your title, would use the belongsToMany method.
https://laravel.com/docs/6.x/eloquent-relationships#many-to-many
https://laravel.com/docs/6.x/eloquent-relationships#has-many-through

Laravel - Eloquent relation whereHas one or more other relations

I'm learning Laravel and Laravel eloquent at the moment and now I try to solve a problem using relations in Laravel.
This is what I want to archive:
The database holds many sport clubs. A sport club has a lot of teams. Each team has games. The teams table has a column named club_id. Now I want to create Eloquent relations to get all games of a club.
Here is what I got so far:
Club model
id => PRIMARY
public function games()
{
return $this->hasMany('App\Models\Games')->whereHas('homeTeam')->orWhereHas('guestTeam');
}
Game model
home_id => FOREIGN KEY of team ; guest_id => FOREIGN KEY of team
public function homeTeam()
{
return $this->belongsTo('App\Models\Team','home_id')->where('club_id','=', $club_id);
}
public function guestTeam()
{
return $this->belongsTo('App\Models\Team','guest_id')->where('club_id','=', $club_id);
}
Team model
id => PRIMARY ; club_id => FOREIGN
In my controller all I want to do is Club::findOrFail($id)->games()
Executing the code above returns a SQL error that the games table does not have a column named club_id.
What is the correct way to create this kind of relation?
Thanks!
EDIT
Thanks to Nikola Gavric I've found a way to get all Games - but only where club teams are the home or away team.
Here is the relation:
public function games()
{
return $this->hasManyThrough('App\Models\Game','App\Models\Team','club_id','home_id');
}
How is it possible to get the games where the home_id OR the guest_id matches a team of the club? The last parameter in this function does not allow an array.
There is method to retrieve a "distant relationship with an intermediary" and it is called Has Many Through.
There is also a concrete example on how to use it which includes Post, Country and User, but I think it will be sufficient to give you a hint on how to create games relationship inside of a Club model. Here is a link, but when you open it, search for hasManyThrough keyword and you will see an example.
P.S: With right keys naming you could achieve it with:
public function games()
{
return $this->hasManyThrough('App\Models\Games', 'App\Models\Teams');
}
EDIT #01
Since you have 2 types of teams, you can create 2 different relationships where each relationship will get you one of the type you need. Like this:
public function gamesAsHome()
{
return $this
->hasManyThrough('App\Models\Games', 'App\Models\Teams', 'club_id', 'home_id');
}
public function gamesAsGuests()
{
return $this
->hasManyThrough('App\Models\Games', 'App\Models\Teams', 'club_id', 'guest_id');
}
EDIT #02
Merging Relationships: To merge these 2 relationships, you can use merge() method on the Collection instance, what it will do is, it will append all the records from second collection into the first one.
$gamesHome = $model->gamesAsHome;
$gamesGuests = $model->gamesAsGuests;
$games = $gamesHome->merge($gamesGuests);
return $games->unique()->all();
Thanks to #HCK for pointing out that you might have duplicates after the merge and that unique() is required to get the unique games after the merge.
EDIT #03
sortBy also offers a callable instead of a attribute name in cases where Collection contains numerical indexing. You can sort your Collection like this:
$merged->sortBy(function($game, $key) {
return $game->created_at;
});
When you define that Club hasMany games you are indicating that game has a foreign key called club_id pointing to Club. belongsTo is the same but in the other way. These need to be coherent with what you have on your database, that means that you need to have defined those keys as foreign keys on your tables.
Try this...
Club model
public function games()
{
return $this->hasMany('App\Models\Games');
}
Game model
public function homeTeam()
{
return $this->belongsTo('App\Models\Team','home_id');
}
public function guestTeam()
{
return $this->belongsTo('App\Models\Team','guest_id');
}
Your Query like
Club::where('id',$id)->has('games.guestTeam')->get();

Laravel relationships: hasManyThrough a model with on an pivot table

I have 3 models in Laravel:
Item
ModifierGroup
Modifier
An Item can have many ModifierGroups via an intermediate table:
public function modifierGroups()
{
return $this->belongsToMany(
'App\ModifierGroup',
'menu_item_modifiers',
'item_id',
'group_id'
)->using('App\MenuItemModifier')
->orderBy('position', 'ASC')
->withPivot('position');
}
A ModifierGroup has many Modifiers:
public function modifiers()
{
return $this->hasMany(
'App\Modifier',
'group_id',
'id'
)->orderBy('position', 'ASC');
}
My question is whether it's possible to have a function on the Item that gets to the Modifiers, going through the ModifierGroup (and its pivot)? HasManyThrough doesn't seem to fit with an pivot table involved or does it?
It's possible with a BelongsToMany relationship by "skipping" the ModifierGroup table:
public function modifiers() {
return $this->belongsToMany(
'App\Modifier',
'menu_item_modifiers',
'item_id',
'group_id',
null,
'group_id'
);
}
This problem can be solved using pivot tables, but not in the way that you are using them. As I assume you have three tables - each associated with your models (Item, ModifierGroup, Modifier), you haven't set up pivot functionality as you don't actually have pivot tables. To do this you would require one table to link Item to ModifierGroups and another to link ModifierGroups to Modifier.
To start, lets create the tables using Artisan:
php artisan generate:pivot Item ModifierGroup
now, we have a table called item_modifier_group (I believe?). This table should have two columns, item_id and modifier_group_id. These are the keys Eloquent will use to connect to the items and modifier_groups tables. Now, in our query we can access and item's modifier groups by using the following query:
public function modifierGroups()
{
return $this->belongsToMany('App\ModifierGroup');
}
This means that when you call $item->modifierGroups() you will get a collection of all modifier groups having an id of $item->id.
This process can then be repeated for the ModifierGroups to Modifiers relationship:
php artisan generate:pivot ModifierGroup Modifiers
Now define the modifiers method in the ModifierGroup model:
public function modifiers()
{
return $this->belongsToMany('App\Modifier');
}
Now we have our pivot tables set up, and our relationships defined in one direction (just add similar methods to models to get Items via Modifiers)
The final piece of the pie is Eager Loading (it's amazing). Get item, with modifiers through the appropriate groups like so:
Item::with('modifierGroups.modifiers')->findOrFail('id');
Now there are other ways you could have gone about this problem, and some may even be more valid, however the huge benefit to doing it this way is the flexibility. You now have a way to connect Items, ModifierGroup and Modifiers with pivot tables and with a simple eager loaded query, can get any collection combination you deem necessary. Hope this helped! This is definitely the longest solution I've ever written on here...
For future reading I recommend:
Eager Loading - Nested Eager Loading
Defining Relationships - Many-to-Many

Store relationship with pivot table in laravel 4

Let me explain what i try to achieve.
lets say i have 20 car types for example sport car or family car etc, and 5 cars for example porsche.
When i create a car i have the option to check multiple car types at the same time that belongs to the car and than save it.
I have done some homework and it looks like using a pivot table is the way to go for this inside laravel.
I have this method inside my cars model:
public function types()
{
return $this->belongsToMany('types', 'car_types');
}
And this method inside my types model:
public function cars()
{
return $this->belongsToMany('Car');
}
My tables looks like this:
cars
- id
- name
- created_at
- updated_at
types
- id
- name
- created_at
- updated_at
car_types
- car_id
- type_id
What im trying to do inside my controller is:
$car = new Car();
Car::create( Input::except('types') );
foreach(Input::get('types') as $type)
{
$car->types()->associate($type);
$car->save();
}
This is giving me the following error:
Call to undefined method Illuminate\Database\Query\Builder::associate()
I hope someone can help me out with this.
Thanks in advance.
Well you are almost there. You're right that the two models, Car and Type are in a many to many relation.
In your models you have code that says:
return $this->belongsToMany('types', 'car_types');
and
return $this->belongsToMany('Car');
Error #1:
In the types() method of the Car model as the first parameter you should pass the name of the model, and not the name of the table. So you should change that to something like:
return $this->belongsToMany('Type', 'car_types');
Error #2
In your Car model you're defining the pivot table as car_types, but the pivot definition is missing from the Type model. Either remove , 'car_types' from the types() method in Car model and rename your pivot table to simply car_type, or add , 'car_types' to your cars() method in Type model.
Error #3
Once all the model stuff is set up correctly, you could do in your controller this:
$car = new Car();
$car->name = Input::get('car_name_form_field_name_attribute');
$car->save();
$types = Input::get('types');
$car->types()->sync($types);
Error #4
I'm not sure if this is just a copy/paste error, but your pivot table seems to be missing a primary key field. Every table needs a primary key field, so your car_types table should look something like this:
car_types
id
car_id
type_id
You could also use attach() instead of sync(). More about the difference between these two here
Please try out this code and let us know if it works out.

Laravel save / update many to many relationship

Can anyone help me on how to save many to many relationship? I have tasks, user can have many tasks and task can have many users (many to many), What I want to achieve is that in update form admin can assign multiple users to specific task. This is done through html multiple select input
name="taskParticipants[]"
The catch here is that through the same form (input) you can add/remove users, that's why I have to use sync().
Maybe I should start from the beginning but don't know where to start...
This is my User model:
public function tasks()
{
return $this->belongsToMany('Task','user_tasks');
}
Task model
public function taskParticipants()
{
return $this->belongsToMany('User','user_tasks');
}
TaskController
public function update($task_id)
{
if (Input::has('taskParticipants'))
{
foreach(Input::get('taskParticipants') as $worker)
{
$task2 = $task->taskParticipants->toArray();
$task2 = array_add($task2,$task_id,$worker);
$task->taskParticipants()->sync(array($task2));
}
}
}
This is structure of tables
tasks
id|title|deadline
user_tasks
id|task_id|user_id
tldr; Use sync with 2nd param false
Many-to-many relationship is belongsToMany on both models:
// Task model
public function users()
{
return $this->belongsToMany('User', 'user_tasks'); // assuming user_id and task_id as fk
}
// User model
public function tasks()
{
return $this->belongsToMany('Task', 'user_tasks');
}
In order to add new relation use attach or sync.
Difference between the two is:
1 attach will add new row on the pivot table without checking if it's already there. It's good when you have additional data linked to that relation, for example:
User and Exam linked with pivot table attempts: id, user_id, exam_id, score
I suppose this is not what you need in your situation:
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6]
$user->tasks()->attach([5,6,7]);
// then
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6,5,6,7]
2 sync on the other hand, will either remove all relations and set them up anew:
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6]
$user->tasks()->sync([1,2,3]);
// then
$user->tasks()->getRelatedIds(); // [1,2,3]
or it will setup new relations without detaching previous AND without adding duplicates:
$user->tasks()->sync([5,6,7,8], false); // 2nd param = detach
// then
$user->tasks()->getRelatedIds(); // [1,2,3,4,5,6,7,8]
Here's my notes on how to save and update on all the Eloquent relationships.
in One to One:
You have to use HasOne on the first model and BelongsTo on the second model
to add record on the first model (HasOne) use the save function
example:    $post->comments()->save($comment);
to add record on the second model (BelongsTo) use the associate function
example:    $user->account()->associate($account);    $user->save();
in One to Many:
You have to use HasMany on the first model and BelongsTo on the second model
to add record on the first table (HasMany) use the save or saveMany functions
example:    $post->comments()->saveMany($comments);
to add record on the second model (BelongsTo) use the associate function
example:    $user->account()->associate($account);    $user->save();
in Many to Many:
You have to use BelongsToMany on the first model and BelongsToMany on the second model
to add records on the pivot table use attach or sync functions
both functions accepts single ID or array of ID’s 
the difference is attach checks if the record already exist on the pivot table while sync don’t
example: $user->roles()->attach($roleId);
in Polymorphic One to Many:
You have to use MorphMany on the main model and MorphTo on all the (***able) models
to add records on all the other models use the save
example:    $course->tags()->save($tag);
the pivot table should have the following columns:
. main model ID
. (***able) ID
. (***able) Type
in Polymorphic Many to Many:
You have to use MorphByMany on the main model and MorphToMany on all the (***able) models
to add records on all the other models use the save or saveMany
example:    $course->tags()->save($tag);
example:    $course->tags()->saveMany([$tag_1, $tag_2, $tag_3]);
the pivot table should have the following columns:
. main model ID
. (***able) ID
. (***able) Type
in Has Many Through (shortcut):
You have to use HasManyThrough on the first table and have the normal relations on the other 2 tables
this doesn’t work for ManyToMany relationships (where there’s a pivot table)
however there’s a nice and easy solution just for that.
Here's an article I wrote, inspired by this answer. Important to check it: https://hackernoon.com/eloquent-relationships-cheat-sheet-5155498c209
syncWithoutDetaching([$id_one, $id_two, $id_three]); is what you are looking for. Actually it does the exact thing [sync with 2nd param false] does!
Solved: Use the updateOrInsert(array $attributes, array $values = [])
DB::table('your_pivot_table')->updateOrInsert([
'col' => $someValue
],[
'otherColumn' => $otherVlaue,
]);
}
The sync function obliterates the exiting relationships and makes your array the entire list of relations. You want attach instead to add relations without removing others.
for those who are searching for adding pivot attributes (the middle table attributes), you can use syncWithPivotValues and it also has the second parameter like this
$user->tasks()->syncWithPivotValues($tasksIDs,['day_number' => $day],false);

Resources