WesselsW
WesselsW

Reputation: 69

Insertion of many to many relationship in database gives me a Call to a member function sync() on null

I have a many to many relationship.

Quote Model

public function products(){
    return $this->belongsToMany(Product::class);
}

Product Model

public function quotes(){
    return $this->belongsToMany(Quote::class);
}

I receive details from an input form to my Controller. The output is as follows:

array:39 [▼ 
"_token" => "NgcHxCjpGUe1ot8nJr4Z8VFuA3DG9VprKWqRu5yk"
"quote_id" => "52" 
20 => "0"
10 => "0"
11 => "0"
12 => "0"
13 => "0"
14 => "0"
15 => "0"
16 => "0"
17 => "0"

The "quote_id" refers to the quote created in a step preceding the input form mentioned above. The quote_id is passed to the controller from a hidden input field in the form. In the array above the $key refers to the product_id and the $value refers to the quantity of the product.

I am trying to insert the data into a table product_quote.

Schema::create('product_quote', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('product_id');
        $table->unsignedBigInteger('quote_id');
        $table->integer('qty');
        $table->timestamps();
    });

Here is the code I have:

$data = $request->toArray();
$quote_id = $data['quote_id'];
    unset($data['quote_id']);
    $quote = Quote::query()->findOrFail($quote_id);
    foreach (array($data) as $key => $value){
        $quote->products->sync($data);
        $quote->save();
    }

There after I still need to add the product quantity but I am getting an error: Call to a member function sync() on null.

What am I doing wrong?

Upvotes: 1

Views: 183

Answers (2)

mrhn
mrhn

Reputation: 18926

This is a misunderstanding of Collections vs Relations.

In Laravel if you have the relation products in your case, to access the relation you will call.

$quote->products(); // returns belongsToMany relation query

To access the collection you access it as a property.

$quote->products; // returns a collection with the products

In your case, you are accessing it as a Collection. Thou you are getting a null error something else could be wrong, but start by changing it to the following.

Secondly to set pivot fields use key value array on the sync call, using the key and the value from your structure. You should probably also unset the token. Instead i filter it by using except. A better approach is to use validated on a form request.

You will need to adjust your data to fit the expected key value structure on the sync call, sync should also only be called one as it synchronizes the data between each call.

$data = $request->toArray();

$quote = Quote::findOrFail($data['quote_id']); // calling query() is unnecessary

$products = collect($request->except(['_token', 'quote_id']))->
    ->mapWithKeys(function($item, $key) {
        return [$key => ['qty' => $item]];
    })->all();

$quote->products()->sync($products);

This solution utilises mapWithKeys, which lets you control the key and the item in the collection, which is a collection method not used that often. The return of the closure should be in the format return ['key' => 'item']; instead of return 'item';

Upvotes: 1

Andy Song
Andy Song

Reputation: 4684

You need to use the products relationships rather than the collection.

$quote->products()->sync($data);

Upvotes: 0

Related Questions