Laravel pivot sync() attaches wrong values - laravel

I've been dealing with this issue on almost every sync() to the pivot tables where, in Laravel 5.6, when I perform the following it adds one row that is non existent in the request:
$firm_data = request()->all(); // get the request data
$firm = Firm::where('id', request('firm_id')); // get the Firm model
$firm->update($firm_data); // update the model
// add the firm address with a belongsToMany() relationship set on Models\Firm and on Models\Address
$address = $firm->with('addresses')->first();
$addresses = request('addresses'); // get the request data for address
$address->update($addresses); // all ok till here
// deal with the pivot table
// here's the issue...
$address->cities()->sync([request('firm_id'), $addresses['city_id']]);
The firm data and the address data state is saved correctly in the DB, although, the pivot table is filled with two columns instead of one.
I have, in the whole request, one only city_id = 3034856 but to the pivot table it is added the first row firm_id = 1, city_id = 1, "which comes from nowhere":
The pivot table - firm_city
+---------+---------+
| firm_id | city_id |
+---------+---------+
| 1 | 1 |
| 1 | 3034856 |
+---------+---------+
Any thoughts on why this might be happening?
Just to make sure it's right, here the methods for each model Firm and Address:
// On the Firm model
public function cities()
{
return $this->belongsToMany(City::class, 'firm_city');
}
// on the City model
public function firms()
{
$this->belongsToMany(Firm::class, 'firm_city');
}
Thanks in advance for any insight on why this might be happening,

Remove the firm_id:
$address->cities()->sync([$addresses['city_id']]);
Eloquent gets the firm_id from $address, you don't have to specify it.
If you specify
it, Eloquent assumes that you want to sync a City with id=1.

Related

Load model one to many relation eloquent way without primary key but on multiple overlapping fields

I'm working on an older project that I've been tasked to speed up certain parts of while we work on a complete re-write since the code is just badly maintained, poorly written and outdated for what it's suppose to do.
I stumbled into an issue to the core of the project and because of this I can't change it without breaking almost everything else. So I need to load a "relation" the eloquent way (using Planning:with('availability') but there isn't a real foreign ID, it rather laps with multiple fields.
Would there be a way to load it all in one query with the overlapping fields rather than have it load separately creating an n+1 problem?
+--------------+-----------------+
| Planning | Availability |
+--------------+-----------------+
| planning_id | availability_id |
| date | date |
| startHour | startHour |
| stopHour | stopHour |
| candidate_id | candidate_id |
| section_id | section_id |
+--------------+-----------------+
From the above example you can see the overlapping fields are date, startHour, stopHour, candidate_id and section_id.
I tried get...attribute but that still loads with n+1, I tried including it with ->with(['availabilities']) but that doesn't work since I ask for the
model and not the relation:
Edit for more clarity:
Planning Model:
public function availabilities()
{
return Availability::where('section_id', $this->section_id)
->where('candidate_id', $this->candidate_id)
->where('planningDate', $this->planningDate)
->where('startHour', $this->startHour)
->where('stopHour', $this->stopHour)
->get();
}
public function availabilities2()
{
return $this->hasMany('App\Models\Availability', 'candidate_id', 'candidate_id')
}
Controller:
$plannings = Planning::with(['availabilities'])->get();
$plannings = Planning::with(['availabilities2' => function ($query) {
// $this is suppose to be Planning model but doesn't work
$query->where('section_id', $this->section_id)
->where('planningDate', $this->planningDate)
->where('startHour', $this->startHour)
->where('stopHour', $this->stopHour);
// ---- OR ---- //
// Don't have access to planning table here
$query->where('section_id', 'planning.section_id')
->where('planningDate', 'planning.planningDate')
->where('startHour', 'planning.startHour')
->where('stopHour', 'planning.stopHour');
}])->get();
First of all to be able to load my relation I took one of the keys that matched and took the one which had the least matches which in my case was section_id.
So on my Planning model I have a function:
public function availabilities()
{
return $this->hasMany('App\Models\Availability', 'section_id', 'section_id');
}
This way I can load the data when needed with: Planning:with('availability').
However since I had a few other keys that needed to match I found a way to limit this relation by adding a subquery to it:
$planning = Planning::with([
'availabilities' => function ($query) {
$query->where('candidate_id', $this->candidate_id)
->where('startHour', $this->startHour)
->where('stopHour', $this->stopHour);
},
// Any other relations could be added here
])
->get();
It's not the best way but it is the only way I found it not getting too much extra data, while also respecting my relationship
When you want to use multiple fields in where() method you most insert a array in the where() method:
This document can help you
change your code to this:
return Availability::where([
['section_id', $this->section_id],
['candidate_id', $this->candidate_id],
['planningDate', $this->planningDate],
['startHour', $this->startHour],
['stopHour', $this->stopHour]
])->firstOrFail();

Laravel | Polymorphic relationship from morphed model

I am creating a polymorphic relationship which defaults to null, as it doesn't have an association originally.
I then create the association and I want to be able to update the polymorphic table to have a link between the original and the morphed class.
post_images:
| id | post_id | width | height | created_at | updated_at
files:
| id | fileable_type | fileable_id | file_disk_id | created_at | updated_at
So basically, inside of the polymorphic table the type and the id they default to null as it has to be processed first and converted to a standard format etc.
I then create the associated row inside of the morphed table, however I am unsure how to update the polymorphic to have the type and the id from the morphed table.
I have tried to do something like $post->images()->create(...); and then try and do something like $file->where('id', $fileId)->firstOrFail()->associate($post); but this just seems to return null.
So the current process is:
Upload the file -> creates a row in the files table which contains the polymorphic relationships but columns are defaulted to null.
Standardize the file -> the file then gets converted and compressed into a standard format that is used for the site.
Create post_images row -> The site then creates a row for post_images table which then needs to link the update the files row to contain the new polymorphic relation.
Eloquent relationships:
File.php
public function fileable()
{
return $this->morphTo();
}
PostImage.php
public function file()
{
return $this->morphMany(File::class, 'fileable');
}
Post.php
public function images()
{
return $this->hasMany(PostImage::class);
}
I think you should just be able to call the save method on the relationship. For example:
$post->file()->save($file)

Laravel belongsToMany pivot with multiple columns

I currently have two tables in the DB and a pivot table to join them when I need to do a belongsToMany lookup. The basic example is one DB table is 'teams' and the other is 'members'. I can utilize the belongsToMany method on both the team and members model to pull their relationship with each other. A team can have many members, and a member can belong to many teams.
public function teams()
{
return $this->belongsToMany(Team::class);
}
public function members()
{
return $this->belongsToMany(Member::class);
}
Pivot: team_member
team_id | member_id
---------------------
1 | 1
2 | 1
3 | 2
1 | 2
How can I expand on that pivot table to include a type of member for each team? For example, member1 is a leader on team1. member1 is an assistant on team2. member1 is a generic member on team3... and so on. Can I just add a column to that same pivot table? Would it be the membertype_id? How can I relate that to another model/table?
This is pretty common, and Laravel handles it already. Add extra columns to the pivot table and expand your relationships to use withPivot():
public function teams(){
return $this->belongsToMany(Team::class)->withPivot(["teamRole", ...]);
}
Then accessing is as simple as:
$user = \App\User::with(["teams"])->first();
$team = $user->teams->first();
$teamRole = $team->pivot->teamRole;
See the Documentation for more information:
https://laravel.com/docs/5.6/eloquent-relationships
To answer the second part, which is essentially a "Triple Pivot", that requires extra functionality. See
Laravel - Pivot table for three models - how to insert related models?
for more information, and an associated Package (by the answerer on that question, not maintained, but good for an example)
https://github.com/jarektkaczyk/Eloquent-triple-pivot

How to add multiple conditions on Laravel elequent relationship

I am new to Laravel and working on 5.4 version. I have a model "A" and model "B". Model A has hasMany relationship with B. Upto here things are ok. But now, I want to add more conditions on this relationship.
By default, Laravel works only foreign key relation. I mean it matches data on the basis of only 1 condition. Here, I want more condition.
Below are the tables of both Model:
table: A
id name Year
1 ABC 2016
2 DEF 2017
table: B
id A_id name Year
1 1 tst 2016
2 2 fdf 2017
3 1 kjg 2017
By default, If I want to see records of A_id 1 from table B, then Laravel will fetch all records for A_id 1. But now, suppose, if I want to get records for 2016 then How I can I do this using Laravel relationship method?
Below are the Elequent Model for both tables:
class A extends Model{
public function b(){
return this->hasMany('B');
}
}
class B extends Model{
public function a(){
return $this->belongsTo('A');
}
}
Code to fetch records of id 1:
$data = A::with('b')->find(1)->toArray();
The above request is giving me all data from table B for id 1 but I also want to put corresponding year condition also.
Is anyone know, Ho can I do this? Please share your solutions.
Thanks
You have to constrain your eager loads, using an array as the parameter for with(), using the relationship name as the key, and a closure as the value:
$data = A::with([
'b' => function ($query) {
$query->where('year', '=', '2016');
},
])->find(1)->toArray();
The closure automatically gets injected an instance of the Query Builder, which allows you to filter your eager loaded model.
The corresponding section in the official documentation: https://laravel.com/docs/5.4/eloquent-relationships#constraining-eager-loads

Trying to achieve a hasManyThrough type relationship

I'm trying to achieve something that is similar to Laravel's hasManyThrough, but I'm not sure my DB is set up appropriately or I'm just missing something.
I am trying to display a page for admins to show all of the sites we support. I would like to have a simple column that shows a distinct count of how many customers are attached to each site. To do this, I was going to go through the orders table and retrieve a distinct list of users, then simply use the ->count() method inside my view.
Here is my DB setup (simplified):
sites table (primary key: 'id'):
id | ...
users table (primary key: 'id'):
id | first_name | last_name | ...
orders table (primary key: 'order'):
id | order | user_id | site_id | ....
Site model:
public function customers()
{
return $this->hasManyThrough('App\User', 'App\Order', 'site_id', ' id')->distinct();
}
I realize right away that the key difference between my DB setup and the documentation is I do not have an order_id in my users table, but it doesn't make sense that I do since a user can have many orders.
It is worth noting: I also have a table user_orders. I'm not sure if I should be using that instead. user_orders has the following set up:
id | user_id | order
You can see that it is simply an intermediate table to hold connections between users and orders (remember order is the PK in orders, not id).
So, can anyone help me understand what I am doing wrong?
You could get away with a Join. I give you this sample code to guide you
public function customers()
{
return $this->hasMany('App\Order')
->leftjoin('users', 'users.id', 'orders.user_id')
->groupBy('users.id'); //Is this needed?
//Above code will return you a collection of Order though, but with the user data.
//Let's try using the User model
return App\User::whereHas('orders', function($query) use ($this->id) {
$query->where('site_id', $this->id);
})->get();
}

Resources