How to use sync on a 3 model relationship? - Laravel - php

I have 3 models:
Match Team Player
And i want to create a table with the following structure:
id | match_id | team_id | player_id
So that i can associate the 3 models i refered.
I created a 4th model MatchPlayers for the table i referred and I can use the 'search' functions without a problem. Like this:
$match->matchPlayers()->first()->team()->get()
And it returns the excpected result, but I cant do a
$match->matchPlayers()->sync([])
So, how should i solve this? Is my relationship wrong or the sync method isnt allowed on a 3 model relationship and I shoud use other method?
Thanks in advance
Edit:
Match.php
public function teamPlayers(){
return $this->hasMany('\Modules\Matchs\Entities\MatchPlayer');
}
Team.php
public function matchTeamPlayers(){
return $this->hasMany('\Modules\Matchs\Entities\MatchPlayer');
}
Player.php
public function matchTeamPlayers(){
return $this->hasMany('\Modules\Matchs\Entities\MatchPlayer');
}
MatchPlayer.php
public function player(){
return $this->belongsTo('\Modules\Players\Entities\Player');
}
public function match(){
return $this->belongsTo('\Modules\Matchs\Entities\Match');
}
public function team(){
return $this->belongsTo('\Modules\Teams\Entities\Team');
}

If you've followed the Laravel documentation on Pivot tables and Many-Many relationships found here, and it's still not working, you might have more luck with "Attach". For example;
$matchPlayer = MatchPlayer::create([...]);
$match->matchPlayers()->attach($matchPlayer)
A good example of sync vs attach can be found here

Using a fourth model for this kind of relationship makes sense, as it gives you a navigation property for the third relation on your pivot table. This way you can form more complex queries this way.
For your particular problem, syncing based on match_id and team_id, I would simply do something like this:
$matchId = 123;
$teamId = 234;
$rows = [
['match_id' => $matchId, 'team_id' => $teamId, 'player_id' => 345],
['match_id' => $matchId, 'team_id' => $teamId, 'player_id' => 346],
['match_id' => $matchId, 'team_id' => $teamId, 'player_id' => 347],
];
// remove all previously stored connections
MatchPlayer::where('match_id', $matchId)
->where('team_id', $teamId)
->delete();
// insert the news ones
// (you could also use MatchPlayer::create() per item or
// $matchPlayer->save(), it doesn't matter)
MatchPlayer::insert($rows);
If this operation occurs very frequently, you will potentially burn through a lot of id values of the pivot table. In this case you could also perform a more efficient sync, which is slightly more complex:
$matchId = 123;
$teamId = 234;
$rows = [
['match_id' => $matchId, 'team_id' => $teamId, 'player_id' => 345],
['match_id' => $matchId, 'team_id' => $teamId, 'player_id' => 346],
['match_id' => $matchId, 'team_id' => $teamId, 'player_id' => 347],
];
// delete all players that are not among the new data anymore
MatchPlayer::where('match_id', $matchId)
->where('team_id', $teamId)
->whereNotIn('player_id', array_pluck($rows, 'player_id'))
->delete();
// remove rows from new data that already exist
$exist = MatchPlayer::where('match_id', $matchId)
->where('team_id', $teamId)
->pluck('player_id')
->toArray();
$rows = array_filter($rows, function ($value, $key) use ($exist) {
return ! in_array($value['player_id'], $exist);
});
// then we store the remaining data
MatchPlayer::insert($rows);

Related

How to use updateOrCreate with hasMany relationship?

How to use updateOrCreate with hasMany relationship. For example I have first model Code:
class Code {
public function item()
{
return $this->hasOne(UserItem::class, 'code_id')
}
}
And for the nested relationship UserItem:
class UserItem {
public function serials()
{
return $this->hasMany(ItemSerial::class, 'user_item_id', 'id');
}
}
And I need to updateOrCreate values of serials relationship. I tried this:
foreach ($data['item_serials'] as $serial) {
$code->item->serials()->updateOrCreate([
'serial' => $serial
]);
}
But this doesn't work how I need, because it changes both serial values to same value. This is how serials table looks like:
id user_item_id serial
1 1 lorem
2 1 ipsum
And then I recieve request:
'item_serials' =>
array (
0 => 'test1',
1 => 'test2',
2 => 'test3'
),
And with this request I want to update serials table like this:
id user_item_id serial
1 1 test1
2 1 test2
3 1 test3
I hope I explained understandably. How I should approach this?
Update or create takes two arguments
1-updated data
2-condition
like
$flight = Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99, 'discounted' => 1]
);
see the docs https://laravel.com/docs/8.x/eloquent
Add an array as the first argument inside updateOrCreate(), it'll be used to retrieve/find an existing serial if it does update but if not then create it.
foreach ($data['item_serials'] as $serial) {
$code->item->serials()->updateOrCreate(
['serial' => $serial],
['serial' => $serial]
);
}

Dynamically add columns to query results via CakePHP 3 ORM queries

I'm trying to write a query using CakePHP 3.7 ORM where it needs to add a column to the result set. I know in MySQL this sort of thing is possible: MySQL: Dynamically add columns to query results
So far I've implemented 2 custom finders. The first is as follows:
// src/Model/Table/SubstancesTable.php
public function findDistinctSubstancesByOrganisation(Query $query, array $options)
{
$o_id = $options['o_id'];
$query = $this
->find()
->select('id')
->distinct('id')
->contain('TblOrganisationSubstances')
->where([
'TblOrganisationSubstances.o_id' => $o_id,
'TblOrganisationSubstances.app_id IS NOT' => null
])
->orderAsc('Substances.app_id')
->enableHydration(false);
return $query;
}
The second custom finder:
// src/Model/Table/RevisionSubstancesTable.php
public function findProductNotifications(Query $query, array $options)
{
$date_start = $options['date_start'];
$date_end = $options['date_end'];
$query = $this
->find()
->where([
'RevisionSubstances.date >= ' => $date_start,
'RevisionSubstances.date <= ' => $date_end
])
->contain('Substances')
->enableHydration(false);
return $query;
}
I'm using the finders inside a Controller to test it out:
$Substances = TableRegistry::getTableLocator()->get('Substances');
$RevisionSubstances = TableRegistry::getTableLocator()->get('RevisionSubstances');
$dates = // method to get an array which has keys 'date_start' and 'date_end' used later.
$org_substances = $Substances->find('distinctSubstancesByOrganisation', ['o_id' => 123);
if (!$org_substances->isEmpty()) {
$data = $RevisionSubstances
->find('productNotifications', [
'date_start' => $dates['date_start'],
'date_end' => $dates['date_end']
])
->where([
'RevisionSubstances.substance_id IN' => $org_substances
])
->orderDesc('RevisionSubstances.date');
debug($data->toArray());
}
The logic behind this is that I'm using the first custom finder to produce a Query Object which contains unique (DISTINCT in SQL) id fields from the substances table, based on a particular company (denoted by the o_id field). These are then fed into the second custom finder by implementing where(['RevisionSubstances.substance_id IN' ....
This works and gives me all the correct data. An example of the output from the debug() statement is as follows:
(int) 0 => [
'id' => (int) 281369,
'substance_id' => (int) 1,
'date' => object(Cake\I18n\FrozenDate) {
'time' => '2019-09-02T00:00:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'comment' => 'foo',
'substance' => [
'id' => (int) 1,
'app_id' => 'ID000001',
'name' => 'bar',
'date' => object(Cake\I18n\FrozenDate) {
'time' => '2019-07-19T00:00:00+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
}
]
],
The problem I'm having is as follows: Each of the results returned contains a app_id field (['substance']['app_id'] in the array above). What I need to do is perform a count (COUNT() in MySQL) on another table based on this, and then add that to the result set.
I'm unsure how to do this for a couple of reasons. Firstly, my understanding is that custom finders return Query Objects, but the query is not executed at this point. Because I haven't executed the query - until calling $data->toArray() - I'm unsure how I would refer to the app_id in a way where it could be referenced per row?
The equivalent SQL that would give me the required results is this:
SELECT COUNT (myalias.app_id) FROM (
SELECT
DISTINCT (tbl_item.i_id),
tbl_item.i_name,
tbl_item.i_code,
tbl_organisation_substances.o_id,
tbl_organisation_substances.o_sub_id,
tbl_organisation_substances.app_id,
tbl_organisation_substances.os_name
FROM
tbl_organisation_substances
JOIN tbl_item_substances
ON tbl_organisation_substances.o_sub_id = tbl_item_substances.o_sub_id
JOIN tbl_item
ON tbl_item.i_id = tbl_item_substances.i_id
WHERE
tbl_item.o_id = 1
AND
tbl_item.date_valid_to IS NULL
AND
tbl_organisation_substances.app_id IS NOT NULL
ORDER BY
tbl_organisation_substances.app_id ASC
) AS myalias
WHERE myalias.app_id = 'ID000001'
This does a COUNT() where the app_id is ID000001.
So in the array I've given previously I need to add something to the array to hold this, e.g.
'substance' => [
// ...
],
'count_app_ids' => 5
(Assuming there were 5 rows returned by the query above).
I have Table classes for all of the tables referred to in the above query.
So my question is, how do you write this using the ORM, and add the result back to the result set before the query is executed?
Is this even possible? The only other solution I can think of is to write the data (from the query I have that works) to a temporary table and then perform successive queries which UPDATE with the count figure based on the app_id. But I'm really not keen on that solution because there are potentially huge performance problems of doing this. Furthermore I'd like to be able to paginate my query so ideally need everything confined to 1 SQL statement, even if it's done across multiple finders.
I've tagged this with MySQL as well as CakePHP because I'm not even sure if this is achievable from a MySQL perspective although it does look on the linked SO post like it can be done? This has the added complexity of having to write the equivalent query using Cake's ORM.

Laravel, sync data with Pivot

Laravel 5.3, I have this 2 models:
User:
public function newFunctions()
{
return $this
->belongsToMany('App\NewFunctions', 'user_newfunctions')
->withPivot(['function_count', 'days_count']);
}
NewFunctions:
public function users()
{
return $this
->belongsToMany('App\User', 'user_newfunctions', 'new_function_id', 'user_id')
->withPivot(['function_count', 'days_count']);
}
I now how can I save new data to User, with this:
$user = User::findOrFail($id);
$user->name = $request->input('name');
$user->save();
But now I have to update some values of a pivot table. the pivot table is this:
user_id | new_functions_id | function_count | days_count
---------------------------------------------------------
814 | 1 | 5 |2019-07-19 12:26:19
814 | 3 | 7 |2019-07-19 12:26:19
I have more than 1 row per user_id. I was trying to use:
$user
->newFunctions()
->sync([
'days_count' => $test_date,
'function_count' => $test_int_number
]);
But I'm getting error like:
Ilegal offset type
because is trying to update with this:
array(
'records' => array(
'days_count' => object(Carbon), 'function_count' => '66'),
'results' => array(),
'id' => object(Carbon),
'attributes' => array()
)
)
in BelongsToMany.php
So:
How could I update the values for each user_id on the pivot table?
And how should use syncto update just 'function_count' and 'days_count'? they come from request.
->sync() isn't used like that; it's used to attach() and detach() related new_function_ids until only the ids in sync() are present. You're probably looking for updateExistingPivot()
An example of ->sync() would be using the array:
$user->newFunctions()->sync([
"new_function_id" => 1,
"function_count" => 6,
"days_count" => "2019-07-08 12:00:00",
]);
This would remove the record where new_function_id is 3, and updating the values where new_function_id is 1.
To update function_count and days_count for either new_function_id of 1 or 3, use ->updateExistingPivot() (pass the id you want to update as the first parameter):
$user
->newFunctions()
->updateExistingPivot("1", [
"function_count" => 6,
"days_count" = "2019-07-08 12:00:00"
]);
// or $user->newFunctions()->updateExistingPivot("3", ...);
This will update the pivot table where new_function_id is 1, while leaving the row where new_function_id is 3.
Edit: If you're looking to update all existing records in the pivot table, you'll need to do this in a loop, call a sync with all current records in a single array, or run a manual query.

Laravel ORM for Join Multiple Table

I have 9 tables that will love to join together all have the foreign key employee_id from employee table. How can I get ORM distribution for it. Below is the DB function that join all the function.
$modelEmployee = \DB::table('employees')
->select('*')
->join('employee_finances', 'employees.id', '=', 'employee_finances.employee_id')
->join('employee_addresses', 'employees.id', '=', 'employee_addresses.employee_id')
->join('employee_jobs', 'employees.id', '=', 'employee_jobs.employee_id')
->join('employee_admins', 'employees.id', '=', 'employee_admins.employee_id')
->join('employee_personals', 'employees.id', '=', 'employee_personals.employee_id')
->join('employee_memberships', 'employees.id', '=', 'employee_memberships.employee_id')
->where('employees.id', $id)
->get();
Step 1
First, create model for you employee table and add various relations to other table models
Eloquent Model for employees table
namespace App\Models;
class Employee extends \Illuminate\Database\Eloquent\Model {
public function employee_finances()
{
return $this->hasMany(\App\Models\EmployeeFinance);
}
public function employee_addresses()
{
return $this->hasMany(\App\Models\EmployeeAddress);
}
public function employee_jobs()
{
return $this->hasMany(\App\Models\EmployeeJob);
}
public function employee_admins()
{
return $this->hasMany(\App\Models\EmployeeAdmin);
}
public function employee_personals()
{
return $this->hasMany(\App\Models\EmployeePersonal);
}
public function employee_memberships()
{
return $this->hasMany(\App\Models\EmployeeMembership);
}
}
Step 2
Now create models for other join tables. Below is an example of employee_finances table. (similarly, create other models)
namespace App\Models;
class EmployeeFinance extends \Illuminate\Database\Eloquent\Model {
...
}
Step 3
Then for your query, you can use relations using with and whereHas functions of query builder. This equivalent to the result of the query mentioned in the question but the structure of the outcome will be different;
\App\Models\Employee::with('employee_finances','employee_addresses','employee_jobs','employee_admins','employee_personals','employee_memberships')
->whereId($employeeid)
->whereHas('employee_finances')
->whereHas('employee_addresses')
->whereHas('employee_jobs')
->whereHas('employee_admins')
->whereHas('employee_personals')
->whereHas('employee_memberships')
->get();
Result
Original Result object
The original resultant will be an object of common builder object where you cannot fire further relation actions which can be defined in Model level.
The original result will also be a flat array of the result and may have less. One example here would be the id column value would be replaced by the primary employee's table column id.
[
0 => [
'id' => 1,
'employee_name' => 'Employee',
'employee_finance_content' => 'finance_content',
'employee_personal_content' => 'personal_content',
'employee_jobs_content' => 'employee_jobs',
'employee_addresses_content' => 'employee_addresses',
'employee_admins_content' => 'employee_admins',
]
....
]
New result using Models
The result would be an instance of Employee model. The final result would be as an associative array where each relation would be an index of the array but the result will be an instance of the related Model, for example, the employee_finances would be an index or represent a column and the value contained within it will be an instance of EmployeeFinance on which you can further do ORM level activities.
[
0 => [
'id' => 1,
'employee_name' => 'Employee'
'employee_finances' => [
'id' => 2,
'employee_id' => 1,
'employee_finance_content' => 'finance_content'
],
'employee_addresses' => [
'id' => 10,
'employee_id' => 1,
'employee_address_content' => 'employee_address'
]
],
.....
]
you can use from this :
$row = $this->model->
where("id",$id)
->with("employee_finances")
->with("employee_addresses")
->with("employee_jobs")
->with("employee_admins")
->with("employee_personals")
->with("employee_memberships")
->with("employee_finances")
->get();
return $row->isEmpty() ? [] : $row->toArray();
plz define relations in your models with these names and use form that, here.

Eloquent ORM many to many relationship best practices

I have 2 tables
cars (id, title)
parts (id, title)
and I want to assosiate every car with a lot of parts and the price of this part for the specific car...
I believe the best way is to associate them through a table:
car_parts(id, car_id, part_id, price)
How do I define such a relation in Laravel's Eloquent?
I want to do
$car = Car::find(1);
$parts = $car->parts;
and I want to get an array of objects like so
{
'id' => 1,
'title' => 'rear flash',
'price' => '10.00',
},{
...
}
if I try the
public function parts(){
return $this->belongsToMany('Part', 'car_parts', 'car_id', 'part_id');
}
I dont get the price...
TIA
The price is available on the pivot model if you include it in withPivot:
public function parts ()
{
return $this->belongsToMany('Part', 'car_parts', 'car_id', 'part_id')
->withPivot('price');
}
Then you can map over the parts collection to get the arrays you want:
$parts = Car::find(1)->parts->map(function ($part)
{
return [
'id' => $part->id,
'title' => $part->title,
'car_id' => $part->pivot->car_id,
'part_id' => $part->pivot->part_id,
'price' => $part->pivot->price,
];
});

Categories