Laravel sync manytomany error - php

I am attaching user_id, product_id with an extra field. Every thing is working fine until the extra field should be updated. When the field will be filled for second time instead of updating it will add another one to database. and it's obvious because I used attach instead of sync. But when I use sync I get an error.
this is my code:
$price = $request->input('price');
$product = Product::find($id);
$product->users()->attach(Auth::id(), ['price' => $price]);
and this is the error I get when I use sync:
Argument 1 passed to
Illuminate\Database\Eloquent\Relations\BelongsToMany::formatRecordsList()
must be of the type array, integer given

The first parameter of the sync() method should be an array. So correct syntax is:
$product->users()->sync([Auth::id() => ['price' => $price]]);
https://laravel.com/docs/5.4/eloquent-relationships#updating-many-to-many-relationships

Sync method accepts an array of IDs to place on the pivot table
also the sync method will delete the models from table if model does not exist in array and insert new items to the pivot table.
So you need to do
$product->users()->sync([Auth::id() => ['price' => $price]]);

The sync method accepts an array of IDs to place on the intermediate table.Any IDs that are not in the given array will be removed from the intermediate table.So you should pass an array as a first parameter to sync() function as
$product->users()->sync([Auth::id() => ['price' => $price]]);

Related

Using Codeigniter's Model - issue with saving according to primary key

I have been looking for a solution that would replicate MySQL's INSERT ... ON DUPLICATE KEY UPDATE in my Codeigniter model, which led me to the save() function.
I get the logic of it, and my script works fine... but for UPDATING only.
What I've realised now is that my data array is formed from info pulled from an external datafeed, and includes both new AND existing products. There will ALWAYS be a sku (primary key) included in for every product, therefore the save function will ALWAYS look to update the corresponding row in my database - but never insert it - hence, no new objects are ever added to my database - only updated.
// Defined as a model property
$primaryKey = 'sku';
// Does an insert()
$data = [
'brand' => 'Heinz',
'name' => 'Baked Beans'
];
$userModel->save($data);
// Performs an update, since the primary key, 'sku', is found.
$data = [
'sku' => xrt567ycr,
'brand' => 'Heinz',
'title' => 'Baked Beans'
];
$userModel->save($data);
What is the easiest way to get around this and have the script either insert or update based on whether the sku is actually present in the database already or not? All help greatly appreciated!

How to sync multiple values of the same Attribute in Laravel?

I developing an eCommerce ( with Multiple Product Attributes feature ) website using Laravel 5.4. Everything is working fine. But When I try to sync multiple values of the same Attribute in Pivot table. Laravel ignores the duplicate pares. For example, I've an Attribute called "Network" which has 3 values: 2G, 3G, 4G. A mobile supports 3G and 4G network. I want to sync 3G and 4G value in database. Laravel ignores one of them.
Products Table:
ID - Product Name
1 - Test Mobile
Attributes Table
ID - AttributeName
1 - Network
AttributeValues Table
ID - AttributeID - AttributeValue
1 - 1 - 2G
2 - 1 - 3G
3 - 1 - 4G
ProductAttributes Table
ID - AttributeID - ProductID - AttributeValue
1 - 1 - 1 - 3G
1 - 1 - 1 - 4G
I want to store the Product Attributes in "ProductAttributes" table something like that. But Laravel Ignore one of them.
I am saving the data like that:
$product = Product::create([
'name' => 'Test Mobile'
]);
$product->attributes()->sync([
1 => ['AttributeValue' => '3G'],
1 => ['AttributeValue' => '4G']
]);
Any suggestions, Ideas?
I know this is two years late, but I was dealing with the same issue today, and figured I may leave the solution here, in case anyone looks for it in the future. If you use your original code:
$product->attributes()->sync([
1 => ['AttributeValue' => '3G'],
1 => ['AttributeValue' => '4G']
]);
the second item in the array will overwrite the first one, so in the end, there will only be a "4G" entry in the database. This is not really a laravel issue, it is how PHP associative arrays are implemented - you basically cannot have two items in the array on the same index.
There are actually two ways to solve this issue
1) first one is very inefficient, but it is functional. I am leaving it here only for the record, because that was the original way I solved the issue. Instead of your code, you would need something like this
$product->attributes()->sync([]); // empty relation completely
// add first item
$product->attributes()->syncWithoutDetaching([
1 => ['AttributeValue' => '3G'],
]);
// add second item without detaching the first one
$product->attributes()->syncWithoutDetaching([
1 => ['AttributeValue' => '4G'],
]);
this is EXTREMELY inefficient, because it needs one query to delete all existing data from the relation, and then add new items one by one. You could also run the syncWithoutDetaching inside a loop, and overall inefficiency would greatly depend on how many items you need to sync.
2) the first solution was not good enough, so I kept digging and experimenting, and figured out a way how to make this happen. Instead of putting your items on specific index in the array, you can send array without specific indexes given, and put the ID in the array itself. Something like this
$product->attributes()->sync([
['AttributeID' => 1, 'AttributeValue' => '3G'],
['AttributeID' => 1, 'AttributeValue' => '4G']
]);
by doing it this way, you can actually send two items with the same AttributeID to the sync() method, without one overwriting the other one
For now,
$product->attributes()->sync([]);
$product->attributes()->sync([
['AttributeID' => 1, 'AttributeValue' => '3G'],
['AttributeID' => 1, 'AttributeValue' => '4G']
]);
Looking at Becquerel's response of April 5th, response #2, and in reading the source code of the sync() method in /laravel/framework/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php (this is, I think, Laravel 2.4), I do not see (or, cannot identify) code that would support this "array of array" functionality. In fact, here's the source-code of the entire method:
public function sync($ids, $detaching = true)
{
$changes = [
'attached' => [], 'detached' => [], 'updated' => [],
];
if ($ids instanceof Collection) {
$ids = $ids->modelKeys();
}
// First we need to attach any of the associated models that are not currently
// in this joining table. We'll spin through the given IDs, checking to see
// if they exist in the array of current ones, and if not we will insert.
$current = $this->newPivotQuery()->pluck($this->otherKey);
$records = $this->formatSyncList($ids);
$detach = array_diff($current, array_keys($records));
// Next, we will take the differences of the currents and given IDs and detach
// all of the entities that exist in the "current" array but are not in the
// the array of the IDs given to the method which will complete the sync.
if ($detaching && count($detach) > 0) {
$this->detach($detach);
$changes['detached'] = (array) array_map(function ($v) {
return is_numeric($v) ? (int) $v : (string) $v;
}, $detach);
}
// Now we are finally ready to attach the new records. Note that we'll disable
// touching until after the entire operation is complete so we don't fire a
// ton of touch operations until we are totally done syncing the records.
$changes = array_merge(
$changes, $this->attachNew($records, $current, false)
);
if (count($changes['attached']) || count($changes['updated'])) {
$this->touchIfTouching();
}
return $changes;
}
Now, Laravel is full of dependency-injection and other Magick (apparently similar to Perl's notion of "map?"), but I don't see anything here that will do what Becquerel says it will. And, generally speaking, Laravel's documentation really doesn't come out and say what it does do with repeated values in many-to-many relationships, or if it is cognizant of them at all.
I also notice that the implementation of the method as shown above is actually very similar to the "alternative #1" that he cites as "extremely inefficient." It seems to classify the keys into three buckets ... never seeming to allow for repetition, by my reading ... and then to perform insert, update and delete operations as needed. (No SQL "transactions" anywhere, that I can see, which also surprises me very much ... are they "magickally" there somehow?)
I simply can't determine if Laravel, when presented with more than one occurrence of a value in the (set of related records in the) foreign table, does anything sensible like return them as an array.
I've made this very long-winded response in hopes of eliciting further comments. Thanks.
Use the sync method to store/update the data in the Controller using the relationship :
$product-> attribute()->sync($request->input('product_ids', []));
sync() function in Laravel automatically get reads of duplicates. You can force it with
$product->attribute()->syncWithoutDetaching([
1 => ['AttributeValue' => '3G'],
1 => ['AttributeValue' => '4G']
]);
Good luck mate!

Why sync parameter expect two array to be pass using multiple model?

Can somebody explained me why I need to pass two closed array [[]] in my sync parameter? I tried to use one array with multiple model inside and it's not syncing. I tried two array and it works. Any tips would appreciated!
$document = new Document();
foreach($request->recipient as $recipientId)
{
$document->notifications()->sync([['user_id' => $recipientId, 'sender_id' => $user->id, 'notification_id' => 4]],false);
}
Simply the sync method expects an array of id for the models to attach, and any other pivot changes in the second array.
Please check below the example from the documentation:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
You can find out more on what the sync method expects in the documentation, https://laravel.com/docs/5.2/eloquent-relationships#inserting-many-to-many-relationships.
At its most basic level, the sync method accepts an array of ids.
You can also sync an array of arrays where each child array contains intermediate table values, which is what you are doing. You are only syncing one item though, thus the double brackets.

Phalcon find with columns parameter returns rows instead models

When I use Model::find() response is Resultset of Models, but when I add columns parameter to restrict returned columns the response is Resultset of Rows.
Example:
// Resultset of Models
$users = \Models\Users\Users::find();
// Resultset of Rows
$users = \Models\Users\Users::find([
'columns' => 'id, email'
]);
That makes me unable to call model methods. Is there a way to have Resultset of Models with columns restriction in ::find() method? I'm not sure, but this seems like bug, since Phalcon docs says:
While findFirst() returns directly an instance of the called class (when there is data to be returned), the find() method returns a Phalcon\Mvc\Model\Resultset\Simple.
And there is nothing about the exception of this rule when using columns parameter.
I would also note that other parameters of ::find() like condition, order, bind etc. works fine (Models returned).
Phalcon 1.3.4
This is not a bug, it is the expected behaviour.
Info in the docs scroll a bit down to the Parameters table and read description of columns.
If you need to use model methods or relations you should not specify columns. But if you are after better performance and do not need model relations you should use the Query Builder.
Rest of the find() parameters like condition, order e.t.c. will, not affect your ability to use model methods.
The findFirst() method is also working like the find() method. Example here:
No columns specified:
News::findFirst(3);
// Output
Models\News Object
(
...
When specifying columns
News::findFirst([
'columns' => 'id, created_at'
]);
// Output
Phalcon\Mvc\Model\Row Object
(
[id] => 1
[created_at] => 2016-02-02
)

Laravel Eloquent: Return Array key as the fields ID

When I execute a query via Laravel's Eloquent ORM, I want to get the row ID as the Array result key, this will make things easier when I want to check something based on an ID (using array_key_exists) or do some look ups (if I need a specific entry from the result array)
Is there any way I can tell Eloquent to set the key to the fields ID?
You can simply do
Model::all()->keyBy('category_id');
Since you have an Eloquent Collection (a child class of the generic Collection class) you can use the getDictionary method. $collection->getDictionary() will give you an array of your Category objects keyed by their primary keys.
If you wanted another Collection rather than a native PHP array, you could instead use $collection->keyBy($property). It looks like your primary key is category_id, so $collection->keyBy('category_id'). You can use that method to key by any arbitrary property, including any get mutators you may have written.
While getDictionary is unique to the Eloquent Collection extension, keyBy is available to all Laravel Collection objects. See the Laravel 4.2 API docs or Laravel 5.0 API docs.
You have a Support\Collection/Database\Eloquent\Collection you can use the method lists('id') to return an array of the id of each of the models within the collection.
Then use array_combine to map the keys to the models. The result of which will be an array with the id mapped to their corresponding model.
If you need id as the key, then rely on the Collection:
$collection = Model::all();
$collection->getDictionary();
// returns:
array(
1 => object(Model) ( ... ),
2 => object(Model) ( ... ),
...
idN => object(Model) ( ... )
);
Otherwise, nice or not, you can do this:
$keys = $collection->lists('columnName');
$arrayOfModels = array_combine($keys, $collection->getDictionary);
pluck('label','id') on the get() output is what you're looking for in Laravel 5.8+, which gives you a collection, ready to be toArray()ed
eg:
$options = \App\DataChoice::where([ 'field_id' => $dataField->id , 'active' => true ])->get()->pluck('label','id')->toArray();
I had a similar challenge - I wanted to a PHP array to allow me to create a with an for each choice, with the 's value being the id auto increment column, and the displayed text being the value.

Categories