Laravel attach pivot to table with multiple values - laravel

Background
I'm creating a database revolving around food allergies and I have a many to many relationship between foods and allergies. There is also a pivot value called severity which has a numerical number representing the severity of the allergy for that food item.
This link table looks like this;
food_id|allergy_id|severity
-------|----------|--------
1 | 1 | 3
1 | 4 | 1
2 | 2 | 1
The problem
When trying to update the link table with Eloquent (where $allergy_ids is an array)
$food->allergies()->attach($allergy_ids);
How would I go about adding multiple values to this pivot table at once along with the pivot values?
I can add all the allergy_id's for a particular food item in one go using the above line, but how can I also add in the severity column at the same time with an array of various severity values? Maybe something like
$food->allergies()->attach($allergy_ids, $severity_ids);
Edit: There could be between 0-20 allergies for a specific food item, and a severity rating from 0-4 for each allergy, if this helps at all.

You can.
From this example in Docs (4.2, 5.0):
$user->roles()->sync(array(1 => array('expires' => true)));
Hardcoded version for the first two rows:
$food = Food::find(1);
$food->allergies()->sync([1 => ['severity' => 3], 4 => ['severity' => 1]]);
Dynamically, with your arrays $allergy_ids and $severities in a compatible state (size and sort), you shall prepare your sync data before. Something like:
$sync_data = [];
for($i = 0; $i < count($allergy_ids); $i++))
$sync_data[$allergy_ids[$i]] = ['severity' => $severities[$i]];
$food->allergies()->sync($sync_data);

You can't do it like you' like so I suggest a simple loop:
foreach ($allergy_ids as $key => $id)
{
$food->allergies()->attach($id, array_get($severity_ids, $key));
// should you need a sensible default pass it as a 3rd parameter to the array_get()
}
workaround
However if you wanted to attach multiple allergies with single severity level/id then you could do this:
$food->allergies()->attach($allergy_ids, array('severity' => $singleSeverityValue));

From version 5.1 of Laravel (Currently in Laravel 9.x) onwards it is possible to pass an array as a second argument with all the additional parameters that need to be saved in the intermediate table.
As you can read in the documentation
When attaching a relationship to a model, you may also pass an array of additional data to be inserted into the intermediate table:
$user->roles()->attach($roleId, ['expires' => $expires]);
For convenience, attach and detach also accept arrays of IDs as input:
$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);
Then you can simply do
$food->allergies()->attach([1 => ['severity' => 3], 4 => ['severity' => 1]]);

So, on Laravel 9, passing the ids in the array worked for me. Likeso,
$user->roles()->attach([$a->id,$b->id,$c->id]); and so on.
I guess instead of passing the string. We can pass just the id or else convert the string into array.

Easiest indeed is to attach with the extra data, like so:
$retailer->paymentmethods()->attach($paymentmethod, array('currency' => $paymentmethod->currency));
change out the values for food allergy severity, but you get the hint... :-)

Related

Laravel 8 - Calling multiple factory states at once

Pre Laravel 8 there seems to be an option to line up different states at once
$users = factory(App\User::class, 5)->states('premium', 'delinquent')->make();
(edit: I now realise that this was to apply 2 different states into the same factory record)
whereas I can't seem to find the equivalent of this in Laravel 8, does anybody know how?
Essentially I am trying to call one chain to create multiple records with different states for example:
User::factory()->verified()->unverified()->banned()->create()
Result: 3 different users one verified one unverified one banned
It can be achieved with below code, it just seems a little verbose
User::factory()->verified()->create();
User::factory()->unverified()->create();
User::factory()->banned()->create();
1. Get rid of states and just use sequences
User::factory()
->count(3)
->state(new Sequence(
['status' => 'verified'],
['status' => 'unverified'],
['status' => 'banned'],
))
->create()
2. Use variables as functions
Keep your current states, but dynamically access them from a predefined list.
$states = ['verified', 'unverified', 'banned'];
foreach ($states as $state) {
User::factory()->$state()->create();
}
3. Write each one out by hand.
Similar to number 2, but we don't use a loop.
User::factory()->verified()->create();
User::factory()->unverified()->create();
User::factory()->banned()->create();

Show reaction count on Laravel

I'm using this package as a voting system, I use Laravel 7, I avoid pasting the various codes because you can find all the documentation here: https://github.com/qirolab/laravel-reactions
I was testing the whole thing, but can't show a certain reaction count? I mean, $article->reactionSummary() shows an array of all reactions, but I'd like to show the "Like" reaction count.
https://github.com/qirolab/laravel-reactions#reaction-summery-on-model
If you want to retrieve a value from your reactionSummary you can do:
$reactionSummary = $article->reaction_summary->toArray();
$likesCount = $reactionSummary['like'];
as the docs says: Reaction summary on model
$article->reactionSummary();
$article->reaction_summary;
// example
$article->reaction_summary->toArray();
// output
/*
[
"like" => 5,
"dislike" => 2,
"clap" => 4,
"hooray" => 1
]
*/
The $article->reaction_summary->toArray(); will gives you an array containing a like key and so on.
EDIT 1:
If the key like does not exists you can do something like this:
$reactionSummary = $article->reaction_summary->toArray();
$likesCount = 0;
if (array_key_exists('like', $reactionSummary)) {
$likesCount = $reactionSummary['like'];
}

Comparing laravel collections

I have two collections: "Instructions" and "Known". Basically I am taking a new set of "Instructions" and checking whether anything is different to what is "Known".
So, the quantity is not massive. I retrieve the info:
$Instructions = Instruction::all();
$Knowns = Known::all();
Now, I'm looking for the differences, and I've tried each of these three methods:
$IssuesFound = $Instructions->diff($Knowns);
$IssuesFound = $Instructions->diffKeys($Knowns);
$IssuesFound = $Instructions->diffAssoc($Knowns);
The thing is, an "Instruction" or "Known" is an item with 17 attributes, and anyone of those attributes can be different. I want to compare the attributes of an "Instruction" with the matching attribute of a "Known". (Both items have the same keys, bot items have a Reference attribute to act as a unique identifier.
What I'm finding is that theese methods give me the item that is different, but doesn't tell me which individual attributes are the mismatch.
foreach ($IssuesFound as $issue)
{
dd($issue);
}
So a method like $IssuesFound = $Instructions->diffKeys($Knowns); will come up with item xxx being different, but I can't see how to find out which attribute of the item it is that is different. Not unless I start nesting loops and iterating through all the attributes - which I'm trying to avoid.
How do I do it?
Thanks in advance. (Laravel 5.6)
Straight from laravel docs, diffAssoc will return what you are asking:
$collection = collect([
'color' => 'orange',
'type' => 'fruit',
'remain' => 6
]);
$diff = $collection->diffAssoc([
'color' => 'yellow',
'type' => 'fruit',
'remain' => 3,
'used' => 6
]);
$diff->all();
// ['color' => 'orange', 'remain' => 6]
You get the attribute from the FIRST collection that is different on the SECOND collection, therefore if you get 3 attributes when calling $diff->all() you will know WHICH attributes ARE DIFFERENT, so you could access them or do whatever you want to, if you post more specific results of what you are getting and what you are trying we can help, but I think you are just not thinking how to use these methods

Difference between [attributes:protected] and [original:protected]

Please could anyone explain to me a difference between [attributes:protected] array and [original:protected] array in laravel when using print_r to an array?
When Model reads data from table, arrays 'original' and 'attribute' contains same data. When you change the attribute value (ex $user->name='John'), the change is reflected only on the 'attributes' array but 'original' remains same. (hence the name).
When update() on a model is called, method checks what has changed comparing two arrays and construct query only for changed fields. Thus, in the case of $users->name change Laravel will not create this code:
UPDATE users set name = 'John', password = 'pass', email = 'email' where id = 1
but this:
UPDATE users set name = 'John' where id = 1
This may not be the only way Eloquent uses 'original' array. I found clockwork helpful when you need to see what's going on under the hood of Eloquent.

Order complex object via LINQ

I have a complex object:
+ Client
+ Products
+ Types
| TypeId
| ManagerCode
| TypeName
....
A single client will be associated with a collection of products they have bought, and each product will have a collection of types associated with it.
I need to sort the 'Types' on two properties - ManagerCode and TypeName.
eg: client.Products.ForEach(o => o.Types.OrderBy(o1 => o1.BaseType.ManagerCode));
Well, when I do this, the list doesn't actually order once returned to the front end. It maintains the original sort order. What am I doing wrong?
OrderBy doesn't replace the original collection; it returns a newly-sorted collection.
Assuming Types is a list, you need
client.Products.ForEach(o =>
o.Types = o.Types.OrderBy(o1 => o1.BaseType.ManagerCode).ToList());
An alternative solution would be to use the Sort method.
client.Products.ForEach(o => o.Types.Sort(
(x, y) => string.Compare(x.BaseType.ManagerCode, y.BaseType.ManagerCode));

Resources