CakePHP - Trying to Build Multiple SQL Inserts Using Model SaveMany( ) - cakephp

I'm using cakephp 2.3.0. I have a scenario where I need to insert data in a table, but I need to do multiple inserts, based on the number of users that need the inserted data. If I have one value in the $userIds array, or more than 1 value in the $userIds array, then only one row is inserted in the table. i.e. if I had two values in the array, I would expect two rows to be inserted in my table.
I can't figure out how to correctly build my $message array with the different values from the $userIds array. In the example output below, the value of 5 gets overwritten by 3, and 3 gets overwritten by the value of 4.
When I have 2 or more values in the $userIds array, I confirm that the for loop is correctly looping through each time in the array, by the debug statement.
Below is the output of debug statements:
\app\Controller\ActivitiesController.php (line 134)
'5'
\app\Controller\ActivitiesController.php (line 134)
'3'
\app\Controller\ActivitiesController.php (line 134)
'4'
\app\Controller\ActivitiesController.php (line 136)
array(
'Message' => array(
'message' => 'The activity has been deleted.',
'user_id' => '4'
)
)
Below is my code snippet from my controller:
$this->Activity->Message->create();
foreach ($userIds as $ids) {
$message = array(
'Message' => array(
'message' => 'The activity has been deleted.',
'user_id' => $ids
)
);
debug($ids);
}
debug($message);
$this->Activity->Message->saveMany($message);

You reset your array each time instead of adding one element to it. So try changing your foreach like this
$message = array();
foreach ($userIds as $ids) {
$message[] = array(
'Message' => array(
'message' => 'The activity has been deleted.',
'user_id' => $ids
)
);
debug($ids);
}

Related

CakePHP find() group and order by date complex conditions

I am trying to get messages with their corresponding users, in order to display a chat list of profiles with their last message in chronological order.
array(
0 => array(
'Recepient' => array(
'id' => ...
'name' => ...
...
),
'Message' => array(
'content' => ...
'created' => ...
...
)
),
1 => ...
)
and in order to retrieve the results, I've written this find() method:
$msgs = $this->Message->find('all', array(
'group' => array('Recepient.id'),
'order'=>'Message.created DESC',
'conditions'=>
array(
'OR'=> array(
array('recepient_id'=>$pid),
array('sender_id' => $pid)
)
)
));
What I have:
message with corresponding "Recepient",
in chronological order
The problem:
the query DOES NOT retrieve the most recent message from $recepient_id/$sender_id combination.
So instead of list of users with the last message, I have a list of users with a message. What's wrong with my query? Thanks for help!
METHOD 2 results
I've created "chat_id" field in the database which is basically recepient_id+sender_id sorted alphabetically (because if user1 send user2 a message user1 is sender, later when user2 responds, he becomes the sender, so sorting will ensure two users will always have the same chat_id).
Than I added DISTINCT to the query:
$this->Message->recursive = 0;
$msgs = $this->Message->find('all', array(
'fields' => array('DISTINCT Message.chat_id','Message.*','Recepient.*'),
'order'=>'Message.created DESC',
'conditions'=>
array(
'OR'=> array(
array('recepient_id'=>$pid),
array('sender_id' => $pid)
)
)
));
and it does NOT work! I am now getting multiple messages for the same conversation.
If I remove Message fields and Recipient fields from the query, I get correct amount of "chats".
'fields' => array('DISTINCT Message.chat_id'),
but that's not the solution.
CakePHP version 2.7.0
MySQL DB
METHOD 3 results
$msgs = $this->Message->find('all', array(
'order'=>'Message.created DESC',
'fields' => 'recepient_id, content, max(Message.created) as max_created',
'group'=>'recepient_id',
// 'contain' => array('Recepient'),
'conditions'=>array( 'chat_id' => $chats )
));
I gave up on single-find method to resolve this, so now 1.I am getting list of chats, 2.I want to find the last message from each chat.
Acording to http://www.xaprb.com/blog/2006/12/07/how-to-select-the-firstleastmax-row-per-group-in-sql/ my find query should work. What happens is
(int) 0 => array(
'Message' => array(
'recepient_id' => '55e6d764-1444-4aad-a909-9042f07f76da',
'content' => '1st msg',
'created' => '2015-09-20 18:24:17',
'created_nice' => '2 hours ago'
),
(int) 0 => array(
'max_created' => '2015-09-20 18:24:28'
)
),
the field max(Message.created) is indeed showing the latest message from conversation but the 0=>Message array is for different message! As you can see $results[0]['Message']['created'] time and $results[0][0]['max_created'] are different!
Finally! Working solution:
$db = $this->Message->getDataSource();
$chats = $db->fetchAll(
'select id from (select * from messages order by created desc) Message
where recepient_id = "'.$pid.'" or sender_id = "'.$pid.'"
group by `chat_id`'
);
Above code will retrieve all messages, following the requirements. You can
a) SINGLE QUERY add mysql JOIN to include associated model (we keep the code single-query neat)
b) TWO QUERIES BUT SMARTER easier method, to select just "ids" of the messages and create another query that will have cake's build in containable behaviour. This might be better also because Behaviours will be applied.
You will have array tree as a result, to dig out the actual ids for next query use following:
$chats = Set::extract("/Message/id",$chats);
c) translate the solution to CakePHP query builder... :) up for a challenge?

CakePHP nested associations, empty records errors

I have few tables which are connected (hasMany) like a tree: 1 -> 2 -> 3.
There are few records in table 1, table 2 and 3.
I'm using CakePHP to fetch all data from table 1 with connected table 2, which is connected with table 3.
However few records in table 1 don't have any connected records in table 2. The same is in table 2, some records don't have connected records in table 3.
For the second situation scripts work fine. I get something like this:
1 -> 2 -> empty. But in the first situation, when data looks similar to: 1-> empty -> empty I get errors that table 3 doesn't exist.
Is there any solution to skip this errors and get pretty formatted association table as return to my query?
$options = array(
'conditions' => array(
'Table1.id' => $table1_ids
),
'contain' => array(
'Table2' => array(
'conditions' => array(
'id' => $table2_ids
),
'Table3' => array(
'conditions' => array(
'date_end >' => date('Y-m-d H:i:s')
),
'fields' => array('id'),
),
'fields' => array()
),
),
'fields' => array('id', 'name')
);
$this->Table1->recursive = -1;
$table1 = $this->Table1->find('all', $options);
It's not really cakephp to blame here but your lack of understanding what contain does.
What you're looking for are left joins, which is actually hinted in the documentation of contain.

cakephp correct array to use updateAll

I am using cakephp 2.5 and I have an array that I wish to update many records at once in the Page model. I can't seem to get the format of the array correct. I get the error:
Notice (8): Undefined index: newOrder [APP/Controller/PagesController.php, line 133]
$newOrder = array(
'Page' => array(
0 => array(
'id' => 3,
'order' => 0),
1 => array(
'id' => 4,
'order' => 0),
2 => array(
'id' => 7,
'order' => 0
)
));
$this->Page->updateAll($newOrder);
One of the parts I think I am missing is using 'data' as part of the array. But I am unsure where to place it.
I have also tried:
$this->Page->updateAll($newOrder['Page']);
You should use saveMany for your requirement. Find the explanation below -
updateAll
updateAll(array $fields, array $conditions) - is used to update one or more records with the same value based on a condition or multiple conditions. eg: If you want to update all your pages & set all of them to order = 0, you can use updateAll without passing the primary keys -
$this->Page->updateAll(
array('Page.order' => 0)
);
If you want to update some pages based on a condition you will do something like -
$this->Page->updateAll(
array('Page.order' => 0),
array('Page.type' => 'PROMOTED')
);
Assuming you have a type field in your page model, the above query will set order 0 for all pages with type PROMOTED
Ref - http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-updateall-array-fields-array-conditions
saveMany
Now if you want to update some specific records with some specific values which you want to create in an array, you should use a saveMany(array $data = null, array $options = array())
To save/update multiple records using a data array, you should first create the data array in this format -
$data = array(
array('Page' => array('id' => 1, 'order' => 0)),
array('Page' => array('id' => 2, 'order' => 0)),
array('Page' => array('id' => 3, 'order' => 0))
);
$this->Page->saveMany($data);
Now you can use saveMany to update the three records with the given id(primary key) with order 0. Note if you don't pass primary keys i.e id in the arrays, saveMany will just create new records for the given array.
Ref - http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-savemany-array-data-null-array-options-array

CakePHP UpdateAll Only Updating Last Entered Data

I have built an invoice system in my new CakePHP site and I am currently trying to build a function that will let me save the re-edited information back into the same records.
Below is the section of code I am working on,
$InvoiceDataVar = $this->data['Invoicedata'];
foreach ($InvoiceDataVar as $DataKey => $DataValue) {
$UpdateWork = $InvoiceDataVar[$DataKey]['workdes'];
debug($UpdateWork);
$this->Invoicedata->updateAll(
array('Invoicedata.workdes' => "'$UpdateWork'"),
array('Invoicedata.invoicejoblists_id' => $InvoiceID)
);
}
This is work $this->data holds,
'Invoicedata' => array(
(int) 0 => array(
'workdes' => 'test new update 1',
'price' => '500.00'
),
(int) 1 => array(
'workdes' => 'test new update 2',
'price' => '501.00'
),
(int) 2 => array(
'workdes' => 'test new update 3',
'price' => '503.00'
),
(int) 3 => array(
'workdes' => 'test new update 4',
'price' => '504.00'
),
(int) 4 => array(
'workdes' => 'test new update 5',
'price' => '505.00'
),
(int) 5 => array(
'workdes' => 'test new update 6',
'price' => '999.99'
)
)
These are of course just test data, but unless I am not understanding the UpdateAll function documentation which I have gone over many times, all this will do is update all the six fields with the last test input. Right now all the fields for the workdes contain, 'test new update 6'.
I should also say that there might not be all ways be six fields, there will always be at lest one, with a max (at this time) of 6 inputs to update.
I was thinking about loading the 'ID of the invoicedata table, which is an auto count number, where as the joblist_id is the same id in all the the fields being updated? This is my current line of thinking....
Any Help is most welcome.
Glenn.
It seems you are working with CakePHP 1.3
To udpate many records at once there is a shortcut way shown in documentation
First you need to include ids of Invoicedata model in the post data
'Invoicedata' => array(
(int) 0 => array(
'id' => 23, // include id of the record
'workdes' => 'test new update 1',
'price' => '500.00'
),
// other records
)
// then call save all function
$this->Invoicedata->saveAll($this->data['Invoicedata']);
Please, check: updateAll documentation you can see, that first parameter is $fields, which fields you wanto to update and their appropriate values, and second parameter is $conditions some restrictions which define scope of rows which will be changed. You have empty $conditions, so, all rows will be updated. Last values ( here you need to check source code of DataSource update method), but think that it produce some think like
UPDATE invoicedatas SET workdes = value1, price = value2, workdes = value3, price = value4 and etc
so last values will be used.
Your data arrays should contain id attribute, to update right records and for this you should use save or saveAll methods of Invoicedata model.

CGridView show data from dataProvider

I have a data provider which generates content like:
array(
[0] => ParentObjects array(
Child1Object array(...),
Child2Object array(...),
)
)
The first level of this array will always be 1.
I want to show in a CGridView the content of both childs.
$dataProvider=new CActiveDataProvider('Campanii', array(
'criteria'=>array(
'with'=>array('stocs','produse'),
),
'pagination'=>array(
'pageSize'=>20,
),
));
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>$dataProvider,
'columns'=>array(
array('name'=>'Cod','value'=>'$data->stocs[0]->cod_produs'),
),
));
As you see, the column cod has the value array('name'=>'Cod','value'=>'$data->stocs[1]->cod_produs'). Notice that [0]. Instead of that zero, I want something like an iterator. In this case, it shows me the content of that attributes in a column.
Is this possible?
Yes, you can write any complex expression you want. However, for readability it is better to use an actual function. This example assumes PHP 5.3+, if your environment doesn't support that, just create a normal function instead of an anonymous one (or find a better environment).
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider' => $dataProvider,
'columns' => array(
array(
'name' => 'Cod',
// needed when returning HTML instead of plain text
'type' => 'raw',
// this function is called each time a cell needs to be rendered
'value' => function(Companii $data) {
$codes = array();
foreach($data->stocs as $stoc) {
$code = CHtml::encode($stoc->cod_produs);
$codes []= CHtml::tag('li', array(), $code);
}
$codes = implode("\n", $codes);
return CHtml::tag('ul', array(), $codes);
},
),
),
));

Resources