CakePHP Unable to access array in nested model - arrays

I have a nested associated model based on
Project=>ProjectDocument=>ProjectDocumentSection=>ProjectDocumentSectionVariable=>Codeset=>Code
I am using containable behaviour to get a specific array with these elements (for a single project).
I have set up associations for these models in their respective models.
Using foreach I am able to interrogate all the data up to an including the ProjectDocumentSectionVariable level.
However, once I try to go to the Codeset level, I get errors.
The pertinent code is:
foreach ($project['ProjectDocument'] as $project_document):
if ($project_document['document_type'] == 1)
{
foreach ($project_document['ProjectDocumentSection'] as $project_document_section):
echo "<h4>" . $project_document_section['title']. "</h4><div><table><th>Variable</th><th>Full text</th><th>Type</th><th>Format</th><th>Codeset</th>" ;
foreach ($project_document_section['ProjectDocumentSectionVariable'] as $project_document_section_var):
echo "<tr><td>" . $project_document_section_var['variable_name']
. "</td><td>".$project_document_section_var['variable_description']
. "</td><td>".$project_document_section_var['variable_type']
."</td><td>".$project_document_section_var['variable_format']
."</td><td>".$project_document_section_var['codeset_id'] ;
foreach($project_document_section_var['Codeset'] as $codeset):
foreach($codeset['Code'] as $pcode):
echo $pcode['codevalue'].", ";
endforeach;
endforeach;
echo "</td></tr>";
endforeach;
echo "</table></div>";
endforeach;
}
endforeach;
But I get Illegal string offset 'Code', and Invalid argument supplied for foreach() at the line echo $pcode['codevalue'].", ";
I have looked at the array for $project_document_section_variable and I think there is a problem with the codeset array, but I cannot work out why it is doing this.
Here is an example of some output for the array $project_document_section_variable:
array(
'id' => '32',
'project_id' => '05a',
'project_document_id' => '3',
'project_document_section_id' => '2',
'variable_id' => '2',
'variable_name' => 'Respondent Education',
'variable_description' => 'B1. What is your highest level of education?',
'variable_type' => 'Categorical',
'variable_format' => 'Quantitive',
'codeset_id' => '43',
'Codeset' => array(
'id' => '43',
'title' => 'Educational level',
'Code' => array(
(int) 0 => array(
'id' => '96',
'codeset_id' => '43',
'codevalue' => '1',
'title' => 'No education'
),
(int) 1 => array(
'id' => '97',
'codeset_id' => '43',
'codevalue' => '2',
'title' => 'Primary'
),
(int) 2 => array(
'id' => '98',
'codeset_id' => '43',
'codevalue' => '3',
'title' => 'Secondary'
),
(int) 3 => array(
'id' => '99',
'codeset_id' => '43',
'codevalue' => '4',
'title' => 'Tertiary'
),
(int) 4 => array(
'id' => '100',
'codeset_id' => '43',
'codevalue' => '7',
'title' => 'Other (describe)'
),
(int) 5 => array(
'id' => '101',
'codeset_id' => '43',
'codevalue' => '8',
'title' => 'Refuses to answer'
),
(int) 6 => array(
'id' => '102',
'codeset_id' => '43',
'codevalue' => '9',
'title' => 'Don’t know'
)
)
)
)
You may notice that the codeset array does not have an (int) 0. perhaps this is because only one array is returned for codeset (which is correct, each project_document_section_variable has only one associated codeset.
Is there something blindingly obvious I am missing here?

This line -> foreach($project_document_section_var['Codeset'] as $codeset): is making php assign ['id'], ['title'], and ['Code'] to $codeset.
This line -> foreach($codeset['Code'] as $pcode): is making php looks for ['id']['Code'], ['title']['Code'], and ['Code']['Code'] which don't exists and raise the error.
Try replacing this:
foreach($project_document_section_var['Codeset'] as $codeset):
foreach($codeset['Code'] as $pcode):
echo $pcode['codevalue'].", ";
endforeach;
endforeach;
For this:
foreach($project_document_section_var['Codeset']['Code'] as $pcode):
echo $pcode['codevalue'].", ";
endforeach;

Related

Cakephp 2 use the id of the record as index when retrieving data

Is it possible to retrieve records where the index is the record id?
I have a model with a hasMany Relation. So my study can have multiple texts and questions etc.
The custom retrieve function looks like this:
protected function _findStudyWithSequence($state, $query, $results=array()){
if ($state === 'before') {
$query['contain'] = array(
'SubjectFilter'=>array('fields'=>'SubjectFilter.studyCode'),
'Text'=>array('fields'=>'Text.position,Text.id,Text.text','order'=>array('position')),
'Image'=>array('fields'=>'Image.position, Image.id','order'=>array('position')),
'Video'=>array('fields'=>'Video.position, Video.id','order'=>array('position')),
'Web'=>array('fields'=>'Web.position, Web.id','order'=>array('position')),
'Likert'=>array('fields'=>'Likert.position, Likert.id','order'=>array('position')),
'Question'=>array('fields'=>'Question.position, Question.id','order'=>array('position')),
'Star'=>array('fields'=>'Star.position, Star.id','order'=>array('position')),
'Multiple_choice'=>array('fields'=>'Multiple_choice.position, Multiple_choice.id','order'=>array('position'))
);
$query['recursive']=-1;
$query['order']=array('Study.started DESC');
$query['fields']=array(
'Study.id', 'Study.user_id','Study.title','Study.description','Study.numberParticipants','Study.state','Study.created','Study.modified','Study.started','Study.completed','Study.numberParticipants');
return $query;
}
}
return $results;
}
As you can see I am using containable
The results look like this:
array(
'Study' => array(
'id' => '1845346986',
'user_id' => '1402402538',
'title' => 'bla bla',
'description' => '',
'numberParticipants' => '10',
'state' => 'active',
'created' => '2017-08-21 14:06:11',
'modified' => '2017-08-21 14:29:56',
'started' => '2017-08-21 14:30:06',
'completed' => '0000-00-00 00:00:00',
),
'SubjectFilter' => array(
'studyCode' => null
),
'Text' => array(
(int) 0 => array(
'position' => '0',
'id' => '1423909242',
'text' => '<p>Bla <b>bla </b></p><p>blub</p><p><br /></p>',
'study_id' => '1845346986'
)
),
'Question' => array(
(int) 0 => array(
'position' => '4',
'id' => '729521908',
'study_id' => '1845346986'
)
), etc
But I want the Text and Question index to be the ids. Like this:
'Text' => array(
(int) 1423909242 => array(
'position' => '0',
'id' => '1423909242',
'text' => '<p>Bla <b>bla </b></p><p>blub</p><p><br /></p>',
'study_id' => '1845346986'
)
),
How can i manage this?
You can use CakePHP's amazing Hash::combine() method.
Something like:
$results['Text'] = Hash::combine($results['Text'], '{n}.id', '{n}');
$results['Question'] = Hash::combine($results['Question'], '{n}.id', '{n}');
return $results;
It's not tested code, but I believe it's correct (or at least close), and you should get the idea from here.
Notes on Hash::combine:
Creates an associative array using a $keyPath as the path to build its
keys, and optionally $valuePath as path to get the values. If
$valuePath is not specified, or doesn’t match anything, values will be
initialized to null. You can optionally group the values by what is
obtained when following the path specified in $groupPath.
Hash::combine(array $data, $keyPath, $valuePath = null, $groupPath =
null)

Adding associated data in model::beforeSave

I'm trying to add associated data to during cakePHPs beforeSave() but it looks like cakePHP is ignoring it.
<?php
App::uses("AppModel", "Model");
class Recipe extends AppModel {
public $hasMany = array('Ingredient');
public function beforeSave($options=array()) {
if(!isset($this->data["Ingredient"])) {
Debugger::log("data[Ingredient] didn't exist! adding some ingredients...");
$this->data["Ingredient"] = array(
array(
'Ingredient' => array(
'name' => 'Gluten Free Flour',
'amount' => '12 cups'
)
),
array(
'Ingredient' => array(
'name' => 'Sugar',
'amount' => '50 cups'
)
),
array(
'Ingredient' => array(
'name' => 'A shoe',
'amount' => 'Just one'
)
),
);
}
Debugger::log("Saving Data: ");
Debugger::log($this->data, 7, 10);
return true;
}
}
In my RecipesController I have this:
public function test_save() {
//Test saving With the ingredients in the recipe
Debugger::log("Saving w/ ingredients");
Debugger::log($this->Recipe->saveAssociated(array(
'Recipe' => array(
'name' => 'Test Recipe 1'
),
'Ingredient' => array(
array(
'name' => 'Test Ingredient 1 for Test Recipe 1',
'amount' => 'a few'
),
array(
'name' => 'Test Ingredient 2 for Test Recipe 1',
'amount' => 'a lot'
)
)
)));
Debugger::log("Saving w/o ingredients");
Debugger::log($this->Recipe->saveAssociated(array(
'Recipe' => array(
'name' => 'Test Recipe 2'
)
)));
}
The save with ingredients works, but the save without ingredients doesn't, the data in Recipe->data before beforeSave returns is this:
array(
'Recipe' => array(
'name' => 'Test Recipe 1'
),
'Ingredient' => array(
(int) 0 => array(
'Ingredient' => array(
'name' => 'Test Ingredient 1 for Test Recipe 1',
'amount' => 'a few'
)
),
(int) 1 => array(
'Ingredient' => array(
'name' => 'Test Ingredient 2 for Test Recipe 1',
'amount' => 'a lot'
)
)
)
)
And when Ingredient is added in the before save:
array(
'Recipe' => array(
'name' => 'Test Recipe 1'
),
'Ingredient' => array(
(int) 0 => array(
'Ingredient' => array(
'name' => 'Gluten Free Flour',
'amount' => '12 cups'
)
),
(int) 1 => array(
'Ingredient' => array(
'name' => 'Sugar',
'amount' => '50 cups'
)
),
(int) 2 => array(
'Ingredient' => array(
'name' => 'A shoe',
'amount' => 'Just one'
)
)
)
)
When the data is in the array before I call a save method, it'll work just fine, only when I try and add the associated data in the beforeSave.
From the documentation:
Be sure that beforeSave() returns true, or your save is going to fail.
http://book.cakephp.org/2.0/en/models/callback-methods.html#beforesave
Edited: I tested your code and you are right, related data added in beforeSave() is being ignored. This is happening because you cannot add or modify related data in a model's beforeSave(). It means that, in your Recipe's beforeSave(), you cannot neither add Ingredients nor modify the Ingredients you previously attached to Recipe in your controller.
In your Recipe's beforeSave() you can only modify Recipe data.
Is this a bug? It is not, according to Mark Story (creator of CakePHP):
Its a feature, as currently saveAll only lets you use beforeSave to
modify the record that's about to be saved, not the full data set. I
personally don't feel that should change, but its something that could
be discussed.
This statement is about saveAll() but the important part is what he said about beforeSave(). Check the full thread: https://github.com/cakephp/cakephp/issues/1765

Can't get formhelper to pre-populate multiple records

I have a model called Category which hasMany Expense. I am trying to generate an accordion type interface where a user can expand a category and edit the expenses. I can generate the input fields but they don't seem to be in "edit" mode because they are not pre-populated.
I've searched online and found a few related articles such as this one CakePHP: How to update multiple records at the same time with the Form helper and also this one CakePHP: Form helper with saveMany() to edit multiple rows at once. I have tried to emulate their code but with no success.
My CategoriesController Index function looks like this ...
public function index($project_id) {
$data = $this->Category->find('all', array(
'conditions'=>array('Category.project_id' => $project_id),
'order'=>array('Category.category_name')
));
$this->set('categories', $data);
$this->request->data = $data;
}
I have read that cakephp2 requires $this->request->data to be set for FormHelper to work. But in the examples I have found online everyone seems to use the plural of the model name so I tried that as well.
My Categories\index.ctp looks like this. I'm not at the "accordion" stage yet. I'm just trying to get input boxes on the screen that are pre-populated.
$i=0;
foreach ($categories as $category)
{
echo $this->Form->input("Category.$i.category_id");
echo $this->Form->input("Category.$i.category_name");
$j=0;
foreach($category['Expense'] as $expense)
{
echo $this->Form->input('Expense.' . $j . '.expense_id');
echo $this->Form->input('Expense.' . $j . '.expense_name');
echo $this->Form->input('Expense.' . $j . '.dollar_amount');
echo $this->Form->input('Expense.' . $j . '.sqft_amount');
$j++;
}
$i++;
}
This code seems to be iterating properly because it spits out the correct input fields. The big problem right now is just getting the fields to pre-populate. It doesn't seem to be in "edit" mode and I'm worried that that will be a problem down the road when I try to save the data.
Also, I have tried it with and without $this->form->create('Category') at the top. It doesn't seem to make a difference.
The $categories array looks like this ...
array(
(int) 0 => array(
'Category' => array(
'category_id' => '1',
'category_name' => 'Category 1',
'category_index' => '1',
'project_id' => '1'
),
'Project' => array(
'project_id' => '1',
'project_name' => '131 Anndale Dr',
'project_sqft' => '1700',
'project_cost' => '318',
'project_cost_per_sqft' => '0'
),
'Expense' => array(
(int) 0 => array(
'expense_id' => '2',
'expense_name' => 'Nails',
'category_id' => '1',
'dollar_amount' => '50',
'sqft_amount' => '1',
'expense_index' => '2'
),
(int) 1 => array(
'expense_id' => '1',
'expense_name' => 'Wood',
'category_id' => '1',
'dollar_amount' => '99',
'sqft_amount' => '1',
'expense_index' => '1'
)
)
),
(int) 1 => array(
'Category' => array(
'category_id' => '3',
'category_name' => 'Category 2',
'category_index' => '2',
'project_id' => '1'
),
'Project' => array(
'project_id' => '1',
'project_name' => '131 Anndale Dr',
'project_sqft' => '1700',
'project_cost' => '318',
'project_cost_per_sqft' => '0'
),
'Expense' => array(
(int) 0 => array(
'expense_id' => '3',
'expense_name' => 'Bed',
'category_id' => '3',
'dollar_amount' => '99',
'sqft_amount' => '2',
'expense_index' => '1'
),
(int) 1 => array(
'expense_id' => '4',
'expense_name' => 'Chair',
'category_id' => '3',
'dollar_amount' => '70',
'sqft_amount' => '1',
'expense_index' => '2'
)
)
)
)
Any help would be greatly appreciated. Thanks!!
Coincidentally, I've been doing almost exactly the same thing today.
In order to automatically populate the form, you need to have your data in $this->data in the view. You can do this by assigning to $this->request->data in your controller.
Then you need to have your field names reflect the structure of that array exactly. So using your example, you might have:
echo $this->Form->input("$i.Category.category_name");
for the category fields, and
echo $this->Form->input("$i.Expense.$j.expense_id");
for your expense fields.

How to access a related table field from a Controller?

I am trying to get an array structure of a database and a few of its fields from my controller.
The fields I want are: Document.id, Document.name, Document.submission_date, and the Requester.name which is joined with: Requester.id = Document.requester_id
In my Controller:
Method A:
$documents = $this->Document->find('list', array('fields' => array('Document.name', 'Requester.name', 'Document.submission_date')));
Can't seem to find 'Requester.name'
Method B:
$documents = $this->Document->find('list', array('fields' => array('Document.name', 'Requester.name', 'Document.submission_date'), 'recursive' => 0));
Gives me:
array(
'2012-08-17' => array(
'Document_A' => 'Requester_Z'
),
'2012-08-05' => array(
'Document_B' => 'Requester_Y'
),
'2012-07-09' => array(
'Document_C' => 'Requester_X'
)
)
But I need it to be in format:
array(
(int) 0 => array(
'id' => '16'
'submission_date' => '2012-08-17'
'name' => 'Document_A',
'requester_name' => 'Requester_Z'
),
(int) 1 => array(
'id' => '41'
'submission_date' => '2012-08-05'
'name' => 'Document_B',
'requester_name' => 'Requester_Y'
),
(int) 2 => array(
'id' => '213'
'submission_date' => '2012-07-09'
'name' => 'Document_C',
'requester_name' => 'Requester_X'
),
)
I can't seem to figure it out after going through the 2.0 CakeBook and on StackOverflow...
Any help would be appreciated? Sorry - I'm still a n00b with CakePHP (but REALLY loving it so far!) Thanks!
You need to do a find('all') instead of a find('list') and you can use the fields key like you already are to limit the amount of info that's returned.
It won't come back exactly as you need the data, each field will be in a key of the model it belongs to - if you really need it in that format you can get away with using a virtual field, a custom find, or an afterFind callback to modify the data.
Find list is generally for an id => label formatted array.
You can try this:
$documents = $this
->Document-
>find('all',
array(
'fields' => array('Document.name', 'Requester.name', 'Document.submission_date'),
'joins' => array(
'table' => 'requesters',
'alias' => 'Requester',
'type' => 'left',
'conditions' => array('Requester.id = Document.requester_id')
)
)
);

Cakephp edit.ctp not populating input boxes despite existing in $this->data

I'm struggling to find the answer to this. I have only been using CakePHP for a month and I've hit a problem. It's something I can fix by manually inserting the values but I expected my data to pre-populate. Here is what is happening:
The Model is Product which hasMany 'Dynamicprice'
I'm testing a product with the id of 7 (/products/edit/7).
the first part of my edit function is:
public function edit($id = null) {
$this->Product->id = $id;
if (!$this->Product->exists()) {
throw new NotFoundException('Invalid Product');
}
if ($this->request->is('get')){
$this->request->data = $this->Product->read();
}
debug($this->request->data);
//other stuff setting vars for drop-down lists
}
the
debug($this->request->data);
gives me the following:
array(
'Product' => array(
'id' => '7',
'category_id' => '70',
'name' => 'Full Test',
'description' => 'This is to test all features',
'price' => '0.00',
'aesthetic' => true,
'image' => '',
'price_structure' => '2',
'suggest_for' => '',
'created' => '2012-06-28 12:49:06',
'modified' => '2012-06-28 12:49:06'
),
'Dynamicprice' => array(
(int) 0 => array(
'id' => '15',
'product_id' => '7',
'drop' => '600',
'prices' => '6000:9.99, 12000:18.99 '
),
(int) 1 => array(
'id' => '16',
'product_id' => '7',
'drop' => '1200',
'prices' => '6000:19.99, 12000:28.99 '
),
(int) 2 => array(
'id' => '17',
'product_id' => '7',
'drop' => '2400',
'prices' => '6000:29.99, 12000:38.99 '
)
)
)
However, whilst everything in ['Product'] pre-populates the ['Dynamicprice'] array does not pre-populate the following:
<?php
echo $this->Form->input('Dynamicprices.0.id');
echo $this->Form->input('Dynamicprices.0.drop', array('label' => 'Drop 1 (mm). Enter "0" for "Any Drop"'));
echo $this->Form->input('Dynamicprices.0.prices', array('label' => 'Prices 1', 'type' => 'textarea', 'rel' => 'dynamic'));
?><hr><?php
echo $this->Form->input('Dynamicprices.1.id');
echo $this->Form->input('Dynamicprices.1.drop', array('label' => 'Drop 2 (mm).'));
echo $this->Form->input('Dynamicprices.1.prices', array('label' => 'Prices 2', 'type' => 'textarea', 'rel' => 'dynamic'));
?><hr><?php
echo $this->Form->input('Dynamicprices.1.id');
echo $this->Form->input('Dynamicprices.2.drop', array('label' => 'Drop 3 (mm).'));
echo $this->Form->input('Dynamicprices.2.prices', array('label' => 'Prices 3', 'type' => 'textarea', 'rel' => 'dynamic'));
?>
Am I right to expect them to populate automatically and if so what have I done wrong?
I have created /Model/Dynamicprice.php with the following just to make sure:
class Dynamicprice extends AppModel {
public $name = 'Dynamicprice';
public $belongsTo = 'Product';
But as I expected it didn't change anything.
This is the second time I've done this this week; ask a stupid question. Yes I was right to expect them to pre-populate. The problem is I got my naming conventions mixed up. "Dynamicprices.1.drop" shouldn't have the 's' at the end. Silly me!

Resources