Cake HABTM not deleting rows in join table correctly - cakephp

This has been causing me considerable grief for a couple of days now, so I turn to the community for help.
Assume two tables - Album HABTM Customer
The join table is customer_albums PK(album_id, customer_id)
I have two controller functions:
public function like($album_id) {
if ($this->request->is('post')) {
$this->Album->CustomerAlbum->save(array('album_id' => $album_id,'customer_id' => $this->Auth->user('id')));
$this->redirect(Controller::referer());
}
}
and this one...
public function unlike($album_id) {
$this->log($album_id);
if ($this->request->is('post')) {
$this->Album->CustomerAlbum->deleteAll(array('album_id' => $album_id,'customer_id' => $this->Auth->user('id'),false));
$this->redirect(Controller::referer());
}
}
The "Like" function results in the SQL:
INSERT INTO gre49302_digital.customer_albums (album_id, customer_id) VALUES (1, 62)
which is what I would expect.
However, the "Unlike" function results in:
SELECT CustomerAlbum.album_id FROM gre49302_digital.customer_albums AS CustomerAlbum WHERE album_id = 1 AND customer_id = 62
DELETE CustomerAlbum FROM gre49302_digital.customer_albums AS CustomerAlbum WHERE CustomerAlbum.album_id = (1)
Which indicates to me that CakePHP doesn't understand the concept of a compound primary key.
Consequently when attempting to delete one "like" between customer and album, I end up deleting everything for the selected album.
Ideally the "unlike" function should simply delete a single record from customer_albums using a compound primary key as selector.

This could possibly be the cause. Your current code is:
deleteAll(array('album_id' => $album_id, 'customer_id' => $this->Auth->user('id'), false));
I'm going to assume you meant to set cascade to false. If that is the case, you have the wrong order of close parentheses.
Change your code to:
deleteAll(array('album_id' => $album_id, 'customer_id' => $this->Auth->user('id')), false);
Note the ('id')), false) vs ('id'), false)).

Related

Laravel 5.5 - Updating a pivot table with a custom field given two input arrays

I have 2 input arrays, one for ingredients and one for the amount of the ingredient that is required for an associated recipe. My pivot table has four columns - id, recipe_id, ingredient_id and amount. I want to use the sync method to update the pivot table, however I can't work out how I would go about passing the second 'amounts' array values and ensuring they are synced with the correct record?
$ingredients = $request->ingredients;
$ingredientAmounts = $request->ingredients_amount;
$project->ingredients()->sync( $ingredients => ['amount' => $ingredientAmounts] );
The ingredient and its amount will both have the same key so I guess I could loop through them manually and update the pivot table, but I feel like there will be a simpler way which will make better use of eloquent.
The two input arrays need to be merged to be in the format required:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
From https://laravel.com/docs/5.5/eloquent-relationships#updating-many-to-many-relationships
$array = [];
foreach ($ingredients as $key => $ingredient) {
$array[$ingredient->id] = ['amount' => $ingredientAmounts[$key]];
}
$project->ingredients()->sync($array);

DQL query for getting specific columns from the table

I had created the following table method in order to extract some specific table columns to allow later comparison to values stored on arrays:
public function findAllComposedExcelColumns()
{
$q = Doctrine_Query::create()
->select('p.branch_code, p.state_id, p.state_description, p.account, p.client_name')
->from('Process p');
return ($q->fetchArray());
}
But when I print an element of the retrieved array, it has also the property id which a don't need.
Array ( [0] => Array ( [id] => 1 [branch_code] => ... [state_id] => ... [state_description] => ... [account] => ... [client_name] => ... ) )
Why the id is also appearing on the results? There is any way to remove it?
Try hydrating with HYDRATE_SCALAR - it might give you what you want.
Eg.
public function findAllComposedExcelColumns()
{
$q = Doctrine_Query::create()
->select('p.branch_code, p.state_id, p.state_description, p.account, p.client_name')
->from('Process p');
$output = $q->execute(array(), Doctrine_Core::HYDRATE_SCALAR);
return $output;
}
edit:
note that this will also change your array keys,
eg from ['branch_code'] to ['p_branch_code']
using this type of hydration method is also not ideal when there are related records.
All in all, the above achieves your goal for this scenario. However, I would agree with DrColossos that it is better to simply loop over and ignore the data you don't want.

CakePHP is updating when it should be inserting a HasAndBelongsToMany model

I have a small problem. I am making a site that has Tags and Questions. I have a Question model, Tag model, QuestionsTag model, everything fits together nicely. The user upon asking something puts the tags in the field seperated by a space (foo bar baz) much like on stackoverflow.com.
Now, here is the code to check if a tag already exists or not and entering the tag into the database and the required associations:
function create () {
if (!empty($this->data)) {
$this->data['Question']['user_id'] = 1;
$question = $this->Question->save ($this->data);
/**
* Preverimo če se je vprašanje shranilo, če se je,
* vprašanje označimo.
*/
if ($question) {
$tags = explode (' ', $this->data['Question']['tags']);
foreach ($tags as $tag){
if (($tagId = $this->Tag->existsByName($tag)) != false) {
/**
* Značka že obstaja, torej samo povezemo trenuten
* id z vprašanjem
*/
$this->QuestionsTag->save (array(
'question_id' => $this->Question->id,
'tag_id' => $tagId
));
}
else {
/**
* Značka še ne obstaja, jo ustvarimo!
*/
$this->Tag->save (array(
'name' => $tag
));
// Sedaj pa shranimo
$this->QuestionsTag->save(array(
'question_id' => $this->Question->id,
'tag_id' => $this->Tag->id
));
$this->Tag->id = false;
}
; }
}
}
}
The problem is this, a Question has an id of 1 and I want it to have the tags with id of 1, 2, 3.
When the 2nd and 3rd save get called, Cake sees that in the questions_tags table is already a question with id 1, so it just updates the tag.
But this is not correct, as there should be many questions in that table with the same id, as they refer to different tags belonging to them.
So, is there a way to prevent this? Prevent the save method from UPDATEing?
Thank you!
This behavior isn't specific to HABTM relationships. You are calling the save() method inside of a loop. After the first save, an id value is set and each subsequent save call sees the id and assumes it's an update. Within a loop, you first need to call model->create() to reset an id value that may exist.
From the CakePHP Docs at http://book.cakephp.org/view/75/Saving-Your-Data:
When calling save in a loop, don't forget to call create().
In your case, it would look like this:
$this->QuestionsTag->create();
$this->QuestionsTag->save (array(
'question_id' => $this->Question->id,
'tag_id' => $tagId
));
Check out saveAll. You can make a single call to $this->Question->saveAll(), and it will save any associated data you supply as well. Note that with HABTM data, it will perform a DELETE for any questions_tags associated with that question_id, then perform an INSERT for all the tag_id's included with your data.
if you want to make sure, that a new entry (INSERT) is made rather then an update, you can set $this->create(); right in front of the save call. See http://book.cakephp.org/view/75/Saving-Your-Data (in the upper part of the page): When calling save in a loop, don't forget to call create().

How do I get the last inserted 'id' from the forms table and add it to the 'form_id' in the attributes table?

I have two tables,Forms and Attributes. I'm tring to retrieve the last inserted id from the Forms table and insert it to the form_id column of the Attributes table, along with the other field columns.
Earlier I retrieved the form Id from the Forms table and used it to update the value of the Form name column. It worked fine.The code for that is given below:
function saveFormName($data)
{
$this->data['Form']['formname']=$data['Form']['formname'];
$this->data['Form']['id']=$this->find('all', array(
'fields' => array('Form.id'),
'order' => 'Form.id DESC'
));
$this->id=$this->data['Form']['id'][0];
$this->saveField('name',$this->data['Form']['formname']);
}
But When I tried to do it in a similar way for updating the attributes table,the row is not saved in the database,since the value of $this->data['Form']['id'][0] is an 'Array'.
Even in the saveFormName function, the value of $this->data['Form']['id'][0] is an 'Array',but the form name gets updated correctly. Someone explain me the concept.
function saveFieldEntries($data)
{
$this->data['Form']['id']=$this->find('all', array(
'fields' => array('Form.id'),
'order' => 'Form.id DESC'
));
$this->data['Attribute']['form_id'] = $this->data['Form']['id'][0];
$this->data['Attribute']['label']= 'Label';
$this->data['Attribute']['size']='20';
$this->data['Attribute']['type']=$data['Attribute']['type'];
$this->data['Attribute']['sequence_no'] = $data['Attribute']['sequence_no'];
$this->Attribute->save($this->data);
}
EDIT:
Ok, here is the corresponding code in the controller.
function insertFormName()
{
$this->data['Form']['formname']=$this->params['form']['formname'];
$this->Form->saveFormName($this->data);
}
function insertFieldEntry()
{
$this->data['Attribute']['type']=$this->params['form']['type'];
$this->data['Attribute']['sequence_no'] = $this->params['form']['sequence_no'];
$this->Form->saveFieldEntries($this->data);
}
The parameters form name,type and sequence no are passed to the controller from the corresponding view file.
$this->data['Form']['id'][0] holds an array because find('all') returns an array.
So if you need first ID from this array, you need to pick it properly in function saveFieldEntries:
...
$this->data['Attribute']['form_id'] = $this->data['Form']['id'][0]['Form']['id'];
...

CakePHP: Is it possible to insert record with pre-defined primary key value?

I have a CakePHP model - User - that has ties to an external corporate system. I store some data on those systems and other data locally. In my User::beforeSave() method, I'm trying to set an ID, send the data (with that custom ID) to my corporate systems and then, if it inserts successfully there, return true so that Cake will insert the new user record with that same ID so that I can link them later.
I can't find a way to make this happen. Is there a way to insert a CakePHP record with a user-specified primary key value? I'm using UUIDs so there's (effectively) no opportunity for overlap.
$this->data['User']['id'] = String::uuid()
try {
$user_proxy = new CoreServicesUserProxy();
$corp_user = $user_proxy->CreateUser (
array (
'user' => array (
'UserName' => 'myusername',
'EmailAddress' => $this->data['User']['email'],
'SecurityId' => $this->data['User']['id']
)
)
);
}
catch ( Exception $e ) {
// error handling stuff
return false;
}
I realise you have already been given some hints, but here is some code which might help.
Why not add an external_user_id field to your users table?
<?php
class User extends AppModel {
function beforeSave() {
$ds = ConnectionManager::getDataSource('core_services');
$externalUser = $ds->createUser($this->data);
if (!$externalUser) {
return false;
}
$this->data['User']['external_id'] = $externalUser['id'];
return true;
}
function afterFind($results, $primary) {
// handle different types of find here ('all' vs 'first' vs through relation)
foreach ($results as &$result) {
$result = $this->_mergeExternalUser($result);
}
}
function _mergeExternalUser($user) {
$ds = ConnectionManager::getDataSource('core_services');
$externalUser = $ds->retrieveUser($result['external_id']);
return am($externalUser, $user);
}
}
?>
There is a way - but typically you would add another column to the Users table instead and let CakePHP do it's thing with the primary key. See this Bakery article to know how it's done. Since it is more than a year later, this is for reference mostly. As far as I understand it, this should function well with CakePHP 1.2 as well.

Resources