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

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.

Related

Laravel sync manytomany error

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

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!

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.

Manually packaging data for CakePHP's save()

I'm trying to package up some data for the save() function in cakephp. I'm new to PHP, so I'm confused about how to actually write the below in code:
Array
(
[ModelName] => Array
(
[fieldname1] => 'value'
[fieldname2] => 'value'
)
)
Thank you!
To answer your question, you can create the array structure you need, and save it, by doing this:
<?php
$data = array(
'ModelName' => array(
'fieldname1' => 'value',
'fieldname2' => 'value'
)
);
$this->ModelName->save($data);
?>
Please note:
Based on what you've written above in your comments it looks like you're not keeping to the CakePHP conventions. It's possible to do things this way but you'll save yourself a lot of time and trouble if you decided to stick with the CakePHP defaults as much as possible, and only do it your own way when you have a good reason to.
A couple things to remember are:
Model names should be singular. This means that your model should be called Follower instead of Followers.
The model's primary key in the database should be named just id, not followers_id, and should be set as PRIMARY KEY and AUTO_INCREMENT in your database.
If you decide not to follow the conventions you'll probably find yourself scratching your head, wondering why things aren't working, every step of the way. Try having a look at the CakePHP documentation for more details.
I think you need to do like below:
$this->Followers->create();
$this->data['Followers']['user_id'] = $user_id;
$this->data['Followers']['follower_id'] = $follower_id; // If it is primary and auto increment than you don't need this line.
$this->Followers->save($this->data)

How to merge two doctrine entities in the same object

In my database, the user management is divided into two tables:
- One created by the symfony sfDoctrineGuard plugin (sfGuardUser), with the username, the password and other information used by the plugin
- another one that I created to extend this table with more properties such as the firstname, surname etc...
What I want to do is to gather all the properties of the two tables in a same object to display all the information related to any member on a specific page.
In that purpose I did a join of the two tables like this:
$q = $this->createQuery()
->from('sfGuardUser u')
->leftJoin('u.Mishmember m WITH u.id = ?', $rel['member_id']);
$member = $q->fetchOne();
My problem is that the generated query seems correct since it selects all the attributes of both tables, but in the $member variable, I can only access the properties of the sfGuardUser object.
I want the $member object to encapsulate all the properties of both tables/doctrine objects.
How would you do that?
EDIT1: if I do a
print_r($member->toArray())
after the previous code I get a 2 dimensional array containing all the properties of sfGuardUser in a first dimension and the properties of my second table in a second dimension. To be clear the result of print_r is like:
Array (
[table1-prop1] => value
[table1-prop2] => value
[table1-prop3] => value
[Table2] => Array (
[table2-prop1] => value
[table2-prop2] => value
[table2-prop3] => value
[table2-prop4] => value
)
)
So to access for example the table2-prop3 property I have to do:
$var[Table2][table2-prop3];
Which is not what I want because I want to consider all the properties as part as the sameobjet or array (as if there were only one table.)
If you're still with me the above array should look like:
Array (
[table1-prop1] => value
[table1-prop2] => value
[table1-prop3] => value
[table2-prop1] => value
[table2-prop2] => value
[table2-prop3] => value
[table2-prop4] => value
)
)
Hope that helps to understand my problem.
EDIT2 (answer to DuoSRX and DrColossos)
Thank you both for your interesting answers.
Well, the reason why I'd rather not to have a Mishmember property in my sfGuardUser is that both tables/classes stand for the same entity(the user). Though this fragmentation is inevitable in my database (it wouldn't be wise to edit directly the sfGuardPlugin to add my properties), I'd like that the code of the application would be as if I had one table, because that would be much more sensible and logical to use (imagine that another developer who doesn't know the model would have to work on the controller or the templates...)
What would you think of adding a Doctrine class User that inherits from sfGuardUser and Mishmember (is there multiple inheritance in PHP5?) so that my controller would have only one user class to deal with?
It would make much more sense to me to ask all the attributes of any user without bothering to find out in which table they are stored.
I'm not sure about how doctrine inheritance works but it seem the neatest solution to me (please tell me if I'm wrong!)
I'm not sure I've been clear enough so please don't hesitate to ask anything.
If you don't need objects but just arrays you can do :
$result = $query->fetchArray();
$mishmember = $result['Mishmember'];
unset($result['Mishmember']);
$user = array_merge($result, $mishmember);
That should do the trick, but I think this is too complex. What is the problem with having an multi-dimensional array ?
Edit:
Well then you could use simple or column_aggregation inheritance :
Mishmember:
inheritance:
type: simple (or column_aggregation)
extends: sfGuardUser
columns:
myfield:
type:integer
And then :
$mishmember = Doctrine::getTable('Mishmember')->find(1);
echo $mishmember->myfield;
See the doctrine documentation for more about inheritance.
This is not really how ORMs work: As you noted, you get a sfGuardUser Object/Array back. Now the Mishmember is nothing more than a property to sfGuardUser (it's an coincindent, that is is an object iteself). So instead of having just a e.g. string property, you have an object/array property.
edit: Of yourse you can merge/combine/re-create the array/object (as the other answer suggests), if you don't like the multi-dimensional aspect. But keep in mind, that these operations can get pretty complex when you have larger amounts of returned data.

Categories