I have a simple crud application,
and the edit page in my controller is like so:
function admin_edit($id = null) {
if (empty($this->data)) {
$this->data = $this->Product->read(); //product is the model
} else {
//its a post request, and $this->data is populated
debug($this->data);
//i force the id to another id
$this->data["Product"]["id"] = 115;
debug($this->data); //the data remains the same, doesnt change.. why?
//i will save this later
}
}
Both debugs result in this:
before
Array
(
[Product] => Array
(
[id] => 8
[alias] => ME
[order] => 80
[open_close_images] => 1
[gallery_id] => 8
[video_id] => 2
)
)
after:
Array
(
[Product] => Array
(
[id] => 8 //it must be 115 now!!
[alias] => ME
[order] => 80
[open_close_images] => 1
[gallery_id] => 8
[video_id] => 2
)
)
Why this?
In cakephp 1.3 that worked well, i don't understand how it can be possible to "lock" that array.
Try setting the id by (re-)setting the Model's id var, like this:
$this->Product->id = 115;
That should update the id properly.
EDIT
If you're trying to update other values, use $this->request->data instead (it's called that since CakePHP 2.0), for example:
$this->request->data['Product']['id'] = 115;
Related
I have model for
user(id,name)
section(id,name)
section_users(id,user_id,section_id)
The admin adds all the users and sections separately. Once theses are added I want the admin to selects the section and add all the users in it in section_users
I have a select input with multiple set to true. How do i save this data the cakephp way along with validation.
<?php echo $this->Form->input("section_id"); ?>
<?php echo $this->Form->input("user_id", array('multiple'=>'checkbox')); ?>
This generates
Array
(
[section_id] => 1
[user_id] => Array
(
[0] => 3
[1] => 4
)
)
I know i can loop and convert to this and use saveAll or saveMany but what is the cakephp way/right way to do it.
Array
(
[0] => Array
(
[section_id] => 1
[user_id] => 3
)
[1] => Array
(
[section_id] => 1
[user_id] => 4
)
)
As already mentioned, this is exaplained in the docs, please read them, and in case you don't understand them (which would be understandable as the HABTM section is a little confusing and requires some trial & error), tell us what exactly you are having problems with.
http://book.cakephp.org/2.0/en/models/saving-your-data.html#saving-related-model-data-habtm
Based on the examples shown, the format for saving multiple X to Y should be
Array
(
[Section] => Array
(
[id] => 1
)
[User] => Array
(
[User] => Array(3, 4)
)
)
The corresponding form could look like this:
<?php echo $this->Form->create('User'); ?>
<?php echo $this->Form->input('Section.id'); ?>
<?php echo $this->Form->input('User', array('multiple' => 'checkbox')); ?>
<?php echo $this->Form->end('Add Users'); ?>
And the data would be saved via the Section model, that way its modified column is being updated properly.
public function addUsersToSection($id) {
// ...
if($this->request->is('post')) {
if($this->Section->save($this->request->data)) {
// ...
} else {
// ...
}
} else {
$options = array(
'conditions' => array(
'Section.' . $this->Section->primaryKey => $id
)
);
$this->request->data = $this->Section->find('first', $options);
}
$users = $this->Section->User->find('list');
$this->set(compact('users'));
}
Another way would be to restructure the array as shown by Vinay Aggarwal, that works fine, the only difference is that it requires saving through the join model, and consequently it doesn't update the Section models modified column.
CakePHP has the saveMany function, as mentioned in the documentation:
Model::saveMany(array $data = null, array $options = array())¶
This is HABTM relation, first of all you need have relation setup in SectionUser model. Then you can use saveAll() method to save all records at once.
Array
(
[0] => Array
(
[user_id] => 33
[section_id] => 9
)
[1] => Array
(
[user_id] => 33
[section_id] => 10
)
)
Make sure your data array is in above given format.
$this->SectionUser->saveAll($data);
Let's say I need to update one of the [status] array values in the returned Array below.
In PHP I will get the array returned below into this variable $taskArray
The only thing I will have is the [taskid] of the Array item I need to modify and the new value for [status] that I would like to change it to.
I am looking for the most efficient way that I can take the array below, find the array that matches my [taskid] and change the [status] to my new status value and then return the complete updated array to a variable so I can pass it back to my database.
I'm really not sure how I can do that based on how the array is setup, I would appreciate any help in doing this please?
Based on this array below, I would like to basically pass in these 2 variable into a function and have the function make the updates mentioned above and return the whole updated array...
function updateTaskStatus($taskId, $newStatus){
// Contains the Array that is shown below this function
$tasksArray;
// Update $tasksArray [status] with the value of $newStatus
// WHERE $taskId is in the SAME array
// RETURN UPDATED $tasksArray
return $tasksArray;
}
// Calling function above would update the [status] to 'completed
// WHERE [taskid] = 4
updateTaskStatus(4, 'Completed');
Array
(
[0] => Array
(
[taskid] => 3
[name] => sdgsdfgdfg
[description] => dfgsdfgsdfg
[status] => In Progress
[priority] => Low
[type] => Magento
)
[1] => Array
(
[taskid] => 4
[name] => Dfgyrty
[description] => rtyrty
[status] => Open
[priority] => Urgent
[type] => Design
)
[2] => Array
(
[taskid] => 9
[name] => yrgrtyerty
[description] => rtyrt6yerty
[status] => Cancelled
[priority] => Urgent
[type] => Magento
)
[3] => Array
(
[taskid] => 9
[name] => ertgsdftg
[description] => dfgsdfg
[status] => Open
[priority] => Medium
[type] => SEO
)
[4] => Array
(
[taskid] => 30
[name] => fghdfgh
[description] => fghdfgh
[status] => In Progress
[priority] => Low
[type] => SEO
)
[5] => Array
(
[taskid] => 1410858495187
[name] => tyrty
[description] => tyrty
[status] => Open
[priority] => Low
[type] => Other
)
)
If I understood your question, the simple answer is to do a loop like this:
function updateTaskStatus($taskId, $newStatus){
global $tasksArray;
foreach($tasksArray as $k => $task)
{
if ($task['taskid'] == $taskId)
{
$tasksArray[$k]['status'] = $newStatus;
break;
}
}
return $tasksArray;
}
Note there is other solution (see php documentation with all array_* functions).
I added global to your code because otherwise this would not work, but using global is something to avoid everytime you can.
The easiest way to do this sort of thing is using a loop.
<?php
function updateTaskStatus($taskId, $newStatus){
// Loop through all the tasks to see if there's a match
foreach ($tasksArray as $id => $task) {
if ($task['taskid'] != $taskId) {
// Mismatch
continue;
}
$tasksArray[$id]['status'] = $newStatus;
}
// RETURN UPDATED $tasksArray
return $tasksArray;
}
You can do it like this:
foreach ($tasksArray as $task)
if ($task['taskid'] == $taskId)
$task['status'] = $newStatus;
Change key to task id and then use something like this
function updateTaskStatus($taskId, $newStatus){
$tasksArray[$taskId]['status'] = $newStatus;
}
updateTaskStatus(4, 'Completed');
I have a relatively simple entries model with just five fields:
id
type (what datatype this entry is)
amount (how many of whatever type it is)
unit (the unit of the type)
date (the datettime when this entry was entered)
user_id (the id of the user who enters
So, nothing fancy. Now a single form can have multiple entries (both already existing ones and new ones just created), the form is extended via ajax calls.
When I submit the form $this->data looks like this:
Array
(
[Entry] => Array
(
[date] => 2011-01-07
[0] => Array
(
[id] => 1
[type] => Eat
[amount] => 1 Steak, one baked potatoe
[unit] => lunch
[time] => Array
(
[hour] => 13
[min] => 31
)
)
[1] => Array
(
[type] => weight
[amount] => 78.5
[unit] => KG
[time] => Array
(
[hour] => 22
[min] => 22
)
)
)
)
The first entry in $this->data['Entry']['date'] is the date that shall be used by ALL the entries. And since also the user_id is missing I created a "beforeSave" function in the entry-model. It looks like this:
function beforeSave() {
App::import('Component','Session');
$this->Session = new SessionComponent();
if (isset($this->data) && isset($this->data['Entry'])) {
$date = $this->data['Entry']['date'];
unset($this->data['Entry']['date']);
foreach ($this->data['Entry'] as $n => $entry) {
if (is_array($entry)) {
$this->data['Entry'][$n]['date'] = $date . ' ' . $entry['time']['hour'] . ':' . $entry['time']['min'] . ':00';
$this->data['Entry'][$n]['user_id'] = $this->Session->read('Auth.User.id');
}
}
debug($this->data);
}
return true;
}
I remove the date, add it together with the time entry of the user, thus creating a mysql datetime entry and add the user_id of the logged in user. Straightforward, really. The resulting array (as output by that last debug()) looks like the following:
Array
(
[Entry] => Array
(
[0] => Array
(
[id] => 1
[type] => Eat
[amount] => 1 Steak, 1 baked potatoe
[unit] => lunch
[time] => Array
(
[hour] => 09
[min] => 31
)
[date] => 2011-01-07 09:31:00
[user_id] => 2
)
[1] => Array
(
[type] => Weight
[amount] => 78.5
[unit] => KG
[time] => Array
(
[hour] => 22
[min] => 22
)
[date] => 2011-01-07 22:22:00
[user_id] => 2
)
)
)
So it look exactly like I want it to look and it should be easily saved. But when I use $this->Entry->saveAll($this->data['Entry']) to save all the entries, not only does it not work, but when I debug $this->data directly after the saveAll, it looks exactly like before the saveAll function - the date is back in the array, the entries do not have a date or user_id entry.
I can see that beforeSave is called, I can see that it changes $this->data, but somewhere between the end of beforeSave and the usage of "saveAll" all my changes get lost and $this->data is reverted to it's original state. Therefore no saving takes place.
I had similar problem trying to implement some sanitization with hasMany through. As it turns out the problem lies in how saveAll() works. The $this->data in controller isn't the same as $this->data in Model::beforeSave() callback and the two are not synchronized. When You call Model::saveAll() passing $this->data from the controller You explicitly give the model "the old version" of data array. And when You look into saveAll definition in cake/libs/model/model.php it just does:
if (empty($data)) {
$data = $this->data
}
and nothing more to sync up the modified $data array in model and passed by parameter $data form the controller. The solution is to not pass the data to saveAll() but instead set the data on model first using Model::set() and then call Model::saveAll() (without the $data parameter).
When we call SaveAll, doesn't pass updated/submitted data to model's beforeSave. To come out from this issue,
create tmp_data array in model
i.e.
var $tmp_data = array();
before saveall call, set this variable,
i.e.
$this->model->tmp_data = $this->data;
$this->model->saveAll();
Then, amend code for model->beforeSaveBefore
Instead of $this->data use $this->tmp_data
You're not getting tripped up on validation are you?
Try
$this->Entry->saveAll($data,array('validate'=>false));
If it works then the problem lies in validation
However don't forget that you need to use create() before saving for single models if no primary key is specified. Maybe the mixing of new and existing data in the saveAll() is the issue.
as far as I know saveAll() updateAll() and deleteAll() dont trigger any callbacks by default
you need to enable them manually (last parameter of the method)
I'm wondering what the cleanest way is to implement a cakephp form where 1 control is a multi-select and the rest are text fields or single-selects, and then the data is inserted as multiple rows with a saveall(). So for example a form is selected with these values:
textfield A
value=Foo
mulit-select B
values=US,Mexico,Canada
single=select C
value=10
and so I want to insert these rows into the database with a saveall():
Foo,US,10
Foo,Mexico,10
Foo,Canada,10
Now I know in the add view I can use this format for the input statement:
input('Model.0.field1',...)
but I'm wondering if I can mix that in that same form with inputs formatted like
input('Model.field2',....).
Update:
When I mix and match the single-select and multiple-select controls, the form data gets submitted like this:
Array
(
[Alert] => Array
(
[schedule_id] => 75
[user_id] => 6
[0] => Array
(
[frequency] => Array
(
[0] => WEEKLY
[1] => MONTHLY
)
)
[limit_value] => .03
[limit_adjustment] => 0
[type] => LIMIT
[disabled] => 0
)
)
I tried passing that data into saveall() but it treats it like a single record.
Update2: I think saveAll() requires that the multiple rows of data be formatted like this:
Array
(
[Article] => Array(
[0] => Array
(
[title] => title 1
)
[1] => Array
(
[title] => title 2
)
)
)
So it looks like after the submit I'm going to need some javascript code that will restructure the array.
I have something that works... I'm not sure if it takes full advantage of all of cake's "automagic" capabilities, but I don't think it's too convoluted.
So I just added the following code to my controller's add function:
if (!empty($this->data)) {
//debug($this->data, true);
/* begin custom code */
$multiselect = $this->data['Alert']['entity_id'];
$tmp2 = array();
foreach ($multiselect as $item)
{
$tmp = $this->data['Alert'];
$tmp['entity_id'] = $item;
array_push($tmp2,$tmp);
}
$this->data['Alert'] = $tmp2;
debug($this->data,true);
/* end custom code */
$this->Alert->create();
//restructure data
if ($this->Alert->saveAll($this->data['Alert'])) {
$this->Session->setFlash(__('The alert has been saved', true));
//$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The alert could not be saved. Please, try again.', true));
}
and that converts my data to this:
Array
(
[Alert] => Array
(
[0] => Array
(
[schedule_id] => 74
[entity_id] => 1
[user_id] => 6
[frequency] => HOURLY
[limit_value] => .02
[limit_adjustment] => 0
[type] => LIMIT
[disabled] => 1
)
[1] => Array
(
[schedule_id] => 74
[entity_id] => 2
[user_id] => 6
[frequency] => HOURLY
[limit_value] => .02
[limit_adjustment] => 0
[type] => LIMIT
[disabled] => 1
)
)
)
I'm working on retrieving a stack of data and for some reason some of the data gets corrupted. For instance, I've got some Post models that each are related to Comment models (hasMany), and each of the Comment models belongsTo a User. When retrieving the data, here's what I get from the database for the comments:
[Post] => Array
(
)
[Comments] => Array
(
[0] => Array
(
[content] => "2010 has definitely been a busy year!"
[created] => 2010-02-10 13:47:15
[user_id] => 18
[post_id] => 1
[User] => Array
(
[id] => U8
[username] => Uace
[first_name] => Uace
)
[_explicitType] => Comment
)
[0] => Array
(
[content] => "I can't wait..."
[created] => 2009-12-10 13:57:36
[user_id] => 18
[post_id] => 1
[User] => Array
(
[id] => U8
[username] => Uace
[first_name] => Uace
)
[_explicitType] => Comment
)
)
The first character of each of the Comments[i][User] arrays has been replaced with a capital U, though in each case it should be different (such as ID of 18, username of Jace, etc).
I traced it back to an array manipulation I was working with to assign an _explicitType field for Flex interaction (Thanks, Paweł Mysior!) in the afterFind() function. Here's the loop where I stuck in the _explicitType:
if (is_array($results)) {
foreach ( $results as &$item )
{
$item['_explicitType'] = $this->name;
}
} else {
$item[$this->name]['_explicitType'] = $this->name;
}
I assume it has to do with the assignment by reference, but I can't think of why it is happening.
It is very strange.
Set debug to 2 in core.php and look in the sql log at the bottom of the page, maybe you'll find something there. Also look through all models (app, post, user, comment). There might be some beforeFind() that are causing this to happen. Does it also happen when you do a simple User->find()?
Btw. how do you retrieve data here?
I think found the issue. I moved the check for the array inside the foreach() and that seems to be working correctly now. I assume this is because on non-array items, it actually broke things. Here's my altered loop with logging on the test cases:
foreach ( $results as &$item )
{
if(is_array($item)) {
$item['_explicitType'] = $this->name;
} else {
$copy = $item;
$copy['_explicitType'] = $this->name;
$this->log($copy, LOG_DEBUG);
}
}
And sure enough, it logged the data with capital U's replacing the first letter.