Laravel, sync() - how to sync an array and also pass additional pivot fields?
Asked Answered
C

11

118

Official Laravel documentation has this on sync() function:

$user->roles()->sync( array( 1, 2, 3 ) );

You may also associate other pivot table values with the given IDs:

$user->roles()->sync( array( 1 => array( 'expires' => true ) ) );

In the latter example only a single pivot row is being added. What I don't understand is how to associate other pivot table records if there are more than one rows to be synced?

Cristicristian answered 1/12, 2014 at 14:35 Comment(2)
The answer below did not quiet got me through.. could you please post your solutions to that? Thanks!Aucoin
Good question... all tuturials are full of basics.Computerize
U
26

There is now a ->syncWithPivotValues($ids, $pivotValues) method available if you want to set the same pivot value for all synced items.

Example from the doc:

$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);
Undulation answered 22/11, 2021 at 14:46 Comment(1)
You can also pass a third parameter. This parameter is true by default. If you set it to false it will not detach values.Redmond
N
193

In order to sync multiple models along with custom pivot data, you need this:

$user->roles()->sync([ 
    1 => ['expires' => true],
    2 => ['expires' => false],
    ...
]);

Ie.

sync([
    related_id => ['pivot_field' => value],
    ...
]);

edit

Answering the comment:

$speakers  = (array) Input::get('speakers'); // related ids
$pivotData = array_fill(0, count($speakers), ['is_speaker' => true]);
$syncData  = array_combine($speakers, $pivotData);

$user->roles()->sync($syncData);
Nucleus answered 1/12, 2014 at 14:42 Comment(8)
Jarek but what if I have for example $speakers = \Input::get( 'speakers' ) (where $speakers becomes an array), and then want to pass $speakers along with =>array( 'is_speaker' => true)?Diskin
Not what if. You create sync array like shown above. Build your array with ids as keys, or make it this way in your HTML form.Nucleus
Jarek, my \Input::get( 'speakers' ) is returning an array of ids. Would something like this work: $training->users()->sync( array( $speakers => array( 'is_speaker' => true ) ) )Diskin
First off, try and you will learn it won't work. Like I said you need to build the array, so check the edit for an example.Nucleus
OK, I got it in the end. Sorry, I didn't quite understand what you wrote. But I got it on my own. Thanks.Diskin
@JarekTkaczyk looks nice, but each item is updated\inserted with a separate query, which is a bit disappointing, as it will effect performance.Casares
@Casares 1 that's how AR will work pretty much always, there's cost to using it. 2 you're ofc right about performance, but throwing such statement without any context is pointless - operation like this alone won't be a bottleneckNucleus
@JarekTkaczyk $model->detach($ids); $model->attach($items); worked for me. I had 10+ items to sync, so i thought this would be a better approach.Casares
C
59

This works for me

foreach ($photos_array as $photo) {

    //collect all inserted record IDs
    $photo_id_array[$photo->id] = ['type' => 'Offence'];  

}

//Insert into offence_photo table
$offence->photos()->sync($photo_id_array, false);//dont delete old entries = false
Cule answered 2/6, 2016 at 6:0 Comment(2)
Upvoting this one because it's nice and clean, and I suspect it'd be very easy for any developer coming after to understand and modify.Buy
You can replace ->sync($photo_id_array, false) to ->syncWithoutDetaching($photo_id_array) as well. Upvoting too since this is the most elegant solution.Seafood
U
26

There is now a ->syncWithPivotValues($ids, $pivotValues) method available if you want to set the same pivot value for all synced items.

Example from the doc:

$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);
Undulation answered 22/11, 2021 at 14:46 Comment(1)
You can also pass a third parameter. This parameter is true by default. If you set it to false it will not detach values.Redmond
Z
5

Attaching / Detaching

Eloquent also provides a few additional helper methods to make working with related models more convenient. For example, let's imagine a user can have many roles and a role can have many users. To attach a role to a user by inserting a record in the intermediate table that joins the models, use the attach method:

$user = App\User::find(1);

$user->roles()->attach($roleId);

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]);

You can also use Sync if you want to remove old roles and only keep the new ones you are attaching now

$user->roles()->sync([1 => ['expires' => $expires], 2 => ['expires' => $expires]);

The default behaviour can be changed by passing a 'false' as a second argument. This will attach the roles with ids 1,2,3 without affecting the existing roles.

In this mode sync behaves similar to the attach method.

$user->roles()->sync([1 => ['expires' => $expires], 2 => ['expires' => $expires], false);

Reference: https://laravel.com/docs/5.4/eloquent-relationships

Zonda answered 24/8, 2017 at 18:7 Comment(0)
L
2

Add following trait to your project and append it to your model class as a trait. This is helpful, because this adds functionality to use multiple pivots. Probably someone can clean this up a little and improve on it ;)

namespace App\Traits;

trait AppTraits
{
    /**
     * Create pivot array from given values
     *
     * @param array $entities
     * @param array $pivots
     * @return array combined $pivots
     */
    public function combinePivot($entities, $pivots = [])
    {
        // Set array
        $pivotArray = [];
        // Loop through all pivot attributes
        foreach ($pivots as $pivot => $value) {
            // Combine them to pivot array
            $pivotArray += [$pivot => $value];
        }
        // Get the total of arrays we need to fill
        $total = count($entities);
        // Make filler array
        $filler = array_fill(0, $total, $pivotArray);
        // Combine and return filler pivot array with data
        return array_combine($entities, $filler);
    }
}

Model:

namespace App;

use Illuminate\Database\Eloquent\Model;

class Example extends Model
{
    use Traits\AppTraits;
    // ...
}

Usage:

// Get id's
$entities = [1, 2, 3];
// Create pivots
$pivots = [
    'price' => 634,
    'name'  => 'Example name',
];
// Combine the ids and pivots
$combination = $model->combinePivot($entities, $pivots);
// Sync the combination with the related model / pivot
$model->relation()->sync($combination);
Lei answered 13/3, 2019 at 9:23 Comment(1)
I like your thinking-- better to turn it into something reusable, rather than copied the next time this issue comes up-- though I'd recommend making it a helper function so it could be used anywhere and with any model.Buy
B
1

Simply assign your key-value fields to its id, and update the pivot like below:

$user->roles()->sync([
   1 => ['F1' => 'F1 Updated']
]);
Bygone answered 13/3, 2019 at 9:54 Comment(1)
Thanks a lot. I had a problem with id's and differente values, I could resolve my problem with your solution.Metagalaxy
C
1

To use syncWithoutDetaching with syncWithPivotValues, u need to set third argument false:

$model->rele()->syncWithPivotValues($rele->id, ['pivot_field' => $pivot_value ], false);
Commonweal answered 13/12, 2023 at 13:41 Comment(1)
Welcome! Thank you for contributing an answer. This is just a note to help you improve your answers. To help future users better understand your answer please provide an explanation with your code blocks. For example, you could explain why your solution works, or how your approach is different from other answers.Agouti
D
0
$data = array();
foreach ($request->planes as $plan) {
 $data_plan = array($plan => array('dia' => $request->dia[$plan] ) );                
 array_push($data,$data_plan);    
}
$user->planes()->sync($data);
Depressomotor answered 24/11, 2019 at 13:1 Comment(2)
When answering an old post, it would be helpful if you could provide some context to your answer rather than just code, as it might make it more useful to others.Vivl
See David Bucks comment. Also, include software and/or system and/or library versions; only way to prevent downvoting or deletion of answer.Indian
S
0

Putting this here in case I forget it later and Google it again.

In my case I wanted the extra column to have the same data for each row

Where $syncData is an array of IDs:

$syncData = array_map(fn($locationSysid) => ['other_column' => 'foo'], array_flip($syncData));

or without arrow

$syncData = array_map(function($locationSysid) {
      return ['ENTITY' => 'dbo.Cli_Core'];
   }, array_flip($syncData));

(array_flip means we're using the IDs as the index for the array)

Shrunken answered 3/2, 2021 at 6:4 Comment(0)
S
0
    foreach ($request->exercise_id as $key => $exercise_id) {
        $data_plan[$exercise_id] = [
            'serie' => $request->serie[$key],
            'observation' => $request->observation[$key],
        ];
    }
Sampling answered 17/2, 2022 at 1:15 Comment(0)
A
0

In case someone else finds this post and is trying to sync data with multiple pivot fields with detach you don't need to create an array with id's as keys you can just pass the id as a parameter of the array for example

    $syncData = [];
    foreach($validated['items'] as $item) {
        array_push($syncData, [
                'item_id' => $item['item_id'],
                'quantity' => $item['quantity'],
                'unit_price' => $item['unit_price'],
                'line_total' => $item['line_total'],
                'unit' => $item['unit']
        ]);
    }
    $order->items()->sync($syncData);

In my case I didn't need to actually make the array and could just sync the data straight from the validated data

$order->items()->sync($validated['items']);
Allopath answered 26/1 at 12:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.