Preventing Laravel adding multiple records to a pivot table - php

I have a many to many relationship set up and working, to add an item to the cart I use:
$cart->items()->attach($item);
Which adds an item to the pivot table (as it should), but if the user clicks on the link again to add an item they have already added it creates a duplicate entry in the pivot table.
Is there a built in way to add a record to a pivot table only if one does not already exist?
If not, how can I check the pivot table to find if a matching record already exists?

You can also use the $model->sync(array $ids, $detaching = true) method and disable detaching (the second param).
$cart->items()->sync([$item->id], false);
Update:
Since Laravel 5.3 or 5.2.44, you can also call syncWithoutDetaching:
$cart->items()->syncWithoutDetaching([$item->id]);
Which does exactly the same, but more readable :)

You can check the presence of an existing record by writing a very simple condition like this one :
if (! $cart->items->contains($newItem->id)) {
$cart->items()->save($newItem);
}
Or/and you can add unicity condition in your database, it would throw an exception during an attempt of saving a doublet.
You should also take a look at the more straightforward answer from Barryvdh.

#alexandre Butynsky method works very well but use two sql queries.
One to check if cart contains the item and one to save.
To use only one query use this:
try {
$cart->items()->save($newItem);
}
catch(\Exception $e) {}

As good as all this answers are because I had tried them all, one thing is still left unanswer or not taken care of: the issue of updating a previously checked value (unchecked the checked box[es]). I do have something similar to the above question expect i want to check and uncheck features of products in my product-feature table (the pivot table). I am a newbie and I have realised none of the above did that. The are both good when adding new features but not when i want to remove existing features (i.e. uncheck it)
I will appreciate any enlightenment in to this.
$features = $request->get('features');
if (isset($features) && Count($features)>0){
foreach ($features as $feature_id){
$feature = Feature::whereId($feature_id)->first();
$product->updateFeatures($feature);
}
}
//product.php (extract)
public function updateFeatures($feature) {
return $this->features()->sync($feature, false);
}
or
public function updateFeatures($feature) {
if (! $this->features->contains($features))
return $this->features()->attach($feature);
}
//where my attach() is:
public function addFeatures($feature) {
return $this->features()->attach($feature);
}
Sorry guys, not sure is I should delete the question because having figure out the answer myself, it sounds a bit stupid, well the answer to the above is as simple as working #Barryvdh sync() as follows; having read more and more about:
$features = $request->get('features');
if (isset($features) && Count($features)>0){
$product->features()->sync($features);
}

There are some great answers posted already. I wanted to throw this one out here as well though.
The answers from #AlexandreButynski and #Barryvdh are more readable than my suggestion, what this answer adds is some efficiency.
It retrieves only the entries for the current combination (actually only the id) and it than attaches it if it does not exist. The sync method (even without detaching) retrieves all currently attached ids. For smaller sets with little iterations this will hardly be a difference, ... you get my point.
Anyway, it is definitely not as readable, but it does the trick.
if (is_null($book->authors()->find($author->getKey(), [$author->getQualifiedKeyName()])))
$book->authors()->attach($author);

I just had this problem and was able to solve it.
For future readers: instead of using atach() you can use
syncWithoutDetaching()
This will make sure you do not get any duplicates!
There are more alternatives to attach()
That might be usefull in the laravel documentation
Note that this is not for Laravel 4

$branch->permissions()->syncWithoutDetaching([1,2,3]);

Related

Laravel grouping or taking from list in six

I'm not sure the title of this topic is correct, I apologize, you suppose I have this list which I get them from the database:
menu-index
menu-create
menu-store
menu-edit
menu-update
menu-destroy
commonCategory-index
commonCategory-create
commonCategory-store
commonCategory-edit
commonCategory-update
commonCategory-destroy
usersRole-index
usersRole-create
usersRole-store
usersRole-edit
usersRole-update
usersRole-destroy
as you can see we can grouping them with the first part of each item that we can have that them in three groups, after grouping them I want to use foreach for each one item that's there. I try to use the Laravel collection, but I can't. Could you help me please how can I do that?
You could do the following:
Model::all()
->groupBy(function($item) {
// Might want to add some checks here, but that depends on your setup
return explode('-', $item->getAttribute('columnName'))[0];
})
->each(function($item) {
// do sth.
});
But it might be more performant to add a group column to your model already.

Omit majority of columns from tables in CakePHP schema

I have a legacy database linked to a CakePHP (2.5.3) application. The database has a LOT of columns in all of its tables, many of which are completely unused (for example in one table I only need 2 columns out of 80 blank ones), so as a result I always have to specify 'fields' whenever I run a query. I read elsewhere that I can unset the fields by using code like this in the model:
function beforeFind($query) {
$this->schema();
unset($this->_schema['ColumnName']);
unset($this->_schema['ColumnName2']);
etc.
}
And this seems to work okay, the problem is that I am using 80+ lines of code to unset columns when really I only need to set two. Is there a way to force CakePHP to let me manually define the columns in the schema?
I have tried declaring the $_schema variable at the start of the model and that doesn't seem to help.
Override Model::schema
Model::schema is the method which tells CakePHP what fields exist, you need only override it to say explicitly what fields it should contain, or filter out the fields you don't want.
e.g.:
function schema($field = false) {
if (!is_array($this->_schema) || $field === true) {
parent::schema();
$keepTheseKeys = array_flip(['Column1', 'Column2']);
$this->_schema = array_intersect_key($this->_schema, $keepTheseKeys);
}
return parent::schema($field);
}

If exist Update else Insert Into. MySQL - PHP

I'm asking this question in order to find the best practice to do it.
DB::table('owners')
->where('property_id',$id)
->update(array('person_id'=>$owner));
The problem is that in the table owners might not have a row to update. In that occasion i need to make an INSERT INTO instead of UPDATE.
My problem is that i have to run 2 queries each time, one for checking if the row already exists, and one more to update or insert into. Is it right to run 2 queries each time? Is there a better way to achieve that? I need to keep the queering processes fast for the user.
UPDATE: The table owners is a middle table of a many to many relationship. Unfortunately i cannot use ON DUPLICATE KEY.
well you could try to use firstOrCreate method of Laravel to check if user exists. After that retrieve the user object and pass it to an update function else if the user is not found firstOrCreate method will take care of you as it will create a new user with the data you will provide and will auto increment last user + 1 id.
There is also the option to use firstOrNew which will check if an instance exists based on the array values you passed and if no match is found it will auto create a new instance of the model you are handling for further manipulation.
Here is example with firstOrNew
Example Controller file.
public function getFirstUserOrNew($email)
{
$user = User::firstOrNew(['email' => $email]);
if($user)
{
$this->UpdateUser($user);
}
else
{
$this->CreateUser($user);
}
}
public function UpdateUser(User $user)
{
//Do update stuff
}
public function CreateUser(User $user)
{
//Do create stuff
}
P.S - I'm from Greece, if you want to discuss anything in native language send me a PM :)
EDIT:
Thanks to #Pyton contribution It seems you can also use an updateOrCreate method as it is explained here.
If you want to Update or Insert row You can use updateOrCreate
$owner = Owner::updateOrCreate(['property_id' => $id], ['person_id'=>$owner]);

Propel: how to remove link made via many-to-many relation

(link to previous question just in case: Struggling with one-to-many relation in an admin form)
I have this many-to-many relation in my Symfony-1.3 / Propel-1.4 project between User and Partner. When the User is being saved, if it has certain boolean flag being true, I want to clear all the links to the partners. Here is what I do at the moment and it doesn't work:
// inside the User model class
public function save(PropelPDO $con = null) {
if ($this->getIsBlaBla()) {
$this->setStringProperty(NULL);
$this->clearUserPartners();
}
parent::save($con);
}
Setting the string property to NULL works; looking at the DB clearly shows it. Thing is however, the USER_PARTNER table still holds the relations between the users and the partners. So I figured I have to clear the links one by one, like this:
foreach($this->getUserPartners() as $user_partner) {
$user_partner->delete();
//UserPartnerPeer::doDelete($user_partner); // tried that too
}
Both don't do the trick.
As I mentioned in my previous question, I am just monkey-learning Symfony via trial and error, so I evidently miss something very obvious. Please point me in the right direction!
EDIT: Here is how I made it work:
Moved the code to the Form class, like so:
public function doSave(PropelPDO $con = null) {
parent::doSave($con);
if ($this->getObject()->getIsSiteOwner()) {
$this->getObject()->setType(NULL);
$this->getObject()->save();
foreach($this->getObject()->getUserPartners() as $user_partner) {
$user_partner->delete();
}
}
return $this->getObject();
}
public function updateObject($values = null) {
$obj = parent::updateObject($values);
if ($obj->getIsSiteOwner()) {
$obj->clearUserPartners();
}
return $this->object;
}
What this does is:
When the boolean flag `is_site_owner` is up, it clear the `type` field and **saves** the object (ashamed I have not figured that out for so long).
Removes all existing UserPartner many-to-many link objects.
Clears newly associated (via the DoubleList) UserPartner relations.
Which is what I need. Thanks to all who participated.
Okey so now you have a many-to-many relation where in database terms is implemented into three tables (User , Parter and UserPartner). Same thing happens on Symfony and Propel, so you need to do something like this on the doSave method that should declare in UserForm:
public function doSave($con = null)
{
parent::doSave($con); //First all that's good and nice from propel
if ($this->getValue('please_errase_my_partners_field'))
{
foreach($this->getObject()->getUserPartners() as $user_partner_relation)
{
$user_partner_relation->delete();
}
}
return $this->getObject();
}
Check the method name "getUserPartners" that should be declared on the BaseUser.class.php (lib/model/om/BaseUser.class.php)
If you are learning Symfony, I suggest you use Doctrine instead of Propel because, I think Doctrine is simplier and more "beautiful" than Propel.
For your problem, I think you are on the good way. If I were you, I will keep my function save() I will write an other function in my model User
public function clearUserPartners(){
// You have to convert this query to Propel query (I'm sorry, but I don't know the right syntax)
"DELETE FROM `USER_PARTNER` WHERE user_id = '$this->id'"
}
With this function, you don't must use a PHP foreach.
But I don't understand what is the attribute StringProperty...
UserPartnerQuery::create()->filterByUser( $userObject )->delete();
or
UserPartnerQuery::create()->filterByUser( $partnerObject )->delete();
Had the same problem. This is a working solution.
The thing is that your second solution, ie. looping over the related objects and calling delete() on them should work. It's the documented way of doing things (see : http://www.symfony-project.org/book/1_0/08-Inside-the-Model-Layer#chapter_08_sub_saving_and_deleting_data).
But instead of bombing the DB with delete queries, you could just as well delete them in one go, by adding a method to your Peer class that performs the deletion using a simple DB query.

Doctrine : do not load related records

In order to improve performance of app, I would like to separate queries instead of using leftJoins. Then I have to create my own related Doctrine_Collection :
$user->Friends->add($current_friend);
But I don't want doctrine does a query when I try to access related (not loaded) Collection.
How I can do that.
Thanks in advance.
I think the answer is in this ยง about relation handling. Build a new friendship relation and save it instead of adding a friend to a user object.
then I found this way (I should optimize this) :
$my_relation_collFriend = FriendTable::getInstance()->findByIdUser($user->id_user);
foreach($my_relation_collFriend as $friend)
{
$collFriend = $user->get('Friends', false); //get the related collection without db query
if(!$collFriend ) //unfornatly, It can be null
{
$collFriend = new Doctrine_Collection::create('friend'); //create the collection
$user->set('Friends', $collFriend, false); // define the related collection without db query
}
$collFriend->add($friend); //add the record to related collection
}
With this example I know this is useless but with lot of joins and datas it becomes necessary

Categories