For a basic search on a Model in Cake, I have been using the postConditions method. It was working well until I enabled CSRF protection via the Security Component.
Enabling this component produces SQL Error: 1054: Unknown column '_Token.key' in 'where clause [...] and I can see that this is happening because $this->data has changed from:
Array (
[CrmPerson] => Array
(
[firstname] => john
[surname] =>
[email] =>
)
)
....to.....
Array
(
[_Token] => Array
(
[key] => 03aef38c3c2f631d6dc50baa98c7327a3fe6d0cd
[fields] => 71d0fa03bc4e10f6f4d0de8f91674100836ea498%3A
)
[CrmPerson] => Array
(
[firstname] => john
[surname] =>
[email] =>
)
)
In other words, the presence of [_Token] up upsetting the $this->postConditions call.
My entire find() function in the controller looks like this:
function find() {
if(!empty($this->data)) {
$this->CrmPerson->recursive = 0;
$conditions = $this->postConditions(
$this->data,
array(
'firstname' => 'LIKE',
'surname' => 'LIKE',
'email' => 'LIKE'
)
);
$this->paginate = array(
'order' => 'CrmPerson.created DESC',
'limit' => '40',
);
$this->set('crmPeople', $this->paginate($conditions));
}
}
I tried changing $this->data to $this->data['CrmPerson'] but the postConditions method needs the data to start one level higher up in the array.
Do I need to create a whole new array to give to postConditions or is there a simpler option I'm not seeing?
Sorry, I was having a slow Sunday... just re-read the postConditions() method #params in controller.php and I can see where I've gone wrong.
This:
$conditions = $this->postConditions(
$this->data,
array(
'firstname' => 'LIKE',
'surname' => 'LIKE',
'email' => 'LIKE'
);
... needs to be....
$conditions = $this->postConditions(
$this->data,
array(
'firstname' => 'LIKE',
'surname' => 'LIKE',
'email' => 'LIKE'
),'AND',true
);
unset($this->data['_Token']);
Related
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)
I have this selector:
$this->Table->find('list',array('contain'=>false,'conditions'=>array('status'=>1,'unit_id'=>null,'country_id'=>$countryId,'eday'=>$eday),'fields'=>'id'));
And this works perfect. But now i need to have another one i cant find how to do this ;)
I need to select all records from Table but with condition:
'eday'>=$eday AND 'eday'<$eday+7
Its this posibble in easy way? Maybe this is stupid question but i dont have exp in PHP ;)
In cakephp the equivalent for and condition is
Example: column1 = 'value' AND column2 = 'value'
'AND' => array(
'column1' => 'value',
'column2' => 'value'
)
So your query builder would be like this
$this->Table->find('list',array(
'contain' => false,
'conditions' => array(
'status' => 1,
'unit_id' => null,
'country_id' => $country,
'AND' => array(
'eday >=' => $eday,
'eday <' => ($eday+7)
)
),
'fields'=>'id'
));
Just pass an AND array with in your current conditions array :
$this->Table->find('list',array('contain'=>false, 'conditions'=>array('status'=>1,'unit_id'=>null,'country_id'=>$countryId,'eday'=>$eday, AND => array( array('eday >=' => $eday) , array( 'eday <' => $eday+7) )), 'fields'=>'id' ));
Have you tried something like this :
$conditions['status'] = 1;
$conditions['unit_id'] = null;
$conditions['country_id'] = $countryId;
$conditions['eday >='] = $eday;
$conditions['eday <'] = $eday + 7;
$this->Table
->find('list') //either use this
->find('all') //or use this
->find() //or this
->where($conditions)
->select(['addFiledsToSelectHere'])
This looks more cleaner.
Inside an afterSave, what is the best way to get information about $this.
For example. If I debug($this->read()), I get everything I need to know about the current record I am working with (associated models, etc..).
array(
'Comment' => array(
'id' => '12',
'user_id' => '38'
'body' => 'test',
'created' => '2013-04-11 18:56:26',
'modified' => '2013-04-11 18:56:26'
),
'User' => array(
'password' => '*****',
'id' => '38',
'username' => 'example',
'created' => '2013-01-26 18:25:39',
'modified' => '2013-01-26 18:25:39',
'first_name' => '',
'last_name' => ''
)
)
But doesn't that mean I am querying the db again? Shouldn't $this already have all this information in it?
What is the proper way to get the results of $this->read(), or this is the correct way?
It is the proper way ($this->read), depending of what info you want about the record you just saved.
For example, if you're doing an insert, and your $data (used like Comment->save($data)) is:
array(
'Comment' => array(
'id' => '12',
'user_id' => '38'
'body' => 'test',
'created' => '2013-04-11 18:56:26',
'modified' => '2013-04-11 18:56:26'
),
'User' => array(
'password' => '*****',
'id' => '38',
'username' => 'example',
'created' => '2013-01-26 18:25:39',
'modified' => '2013-01-26 18:25:39',
'first_name' => '',
'last_name' => ''
)
)
and I mean exactly like that, then $this->data will still have that same info you just saved. $this->data is set to false only after afterSave.
However, if you do something like
$this->Comment->saveField('body', 'othertest');
the $this->data array in the afterSave will only contain something like
Array
(
[id] => 6
[body] => 'othertest'
[modified] => 2013-04-11 15:17:45
)
In other words, if you want to get all the information related to a model regardless of the data passed as parameter in save(), you'll have to do a $this->read() (or find()).
You should be able to access the data like so:
public function afterSave($var = null){
parent::afterSave($var);
echo '<pre>';
print_r($this->data);
echo '</pre>';
die();
}
Try that code and see look through the output. This will give you access to the data that was posted, which should match the record if the save was a success. Also what version of CakePHP are you using and what are you attempting to do?
Read is fine though.
I have model relations like this:
Project hasMany SubProject hasMany Item
I want to set up a containable array so that I can find all of the Items which belong to a particular Project, and paginate the results. So, in my ItemsController I have:
public $paginate = array(
'Item' => array(
'limit' => 10,
'order' => array('
'Item.create_time' => 'desc'
),
'contain' => array(
'SubProject' => array(
'Project'
)
)
)
);
Somewhere, obviously, I need to place a condition like "SubProject.project_id = $pid", but nothing I've tried yields the correct results. The best I can manage is results that look like this:
Array
(
[0] => Array
(
[Item] => Array
(
[id] => 13
[file_name] => foo.tar.gz
.... other keys ...
[create_time] => 2013-01-23 14:59:49
[subProject_id] => 4
)
[SubProject] => Array
(
[id] => 4
[name] => foo
[project_id] => 2
..... other keys ....
[Project] => Array
(
)
)
)
[1] => Array
.....
Edit: It is quite correctly omitting the Project record that doesn't match; I want to skip any Item records with out a matching Project record.
It has crossed my mind to manually specify my joins, but I feel like that shouldn't be necessary.
It seems like this should be obvious, but alas, the solution escapes me.
I did eventually solve this problem, so I thought I'd explain what I did in the hope it might help someone else.
After reading this blog post by Mark Story (which is from the days of 1.2 but still relevant) I decided that the thing to do was create a custom find type in my Item model that binds the Project model directly. This gives a first-level association that Containable can filter correctly.
So, in the Items model, I have something like the following (see the documentation on custom find types).
public $findMethods = array('byProject' => true);
public function _findByProject($state, $query, $results=array()) {
if ($state == 'before') {
$this->bindModel(array(
'hasOne' => array(
'Project' => array(
'foreignKey' => false,
'conditions' => array('Project.id = SubProject.project_id')
)
)
));
return $query;
}
return $results;
}
Note that setting foreignKey to false is necessary to prevent CakePHP from trying to automatically use a non-existent database key. In the ItemsController, the pagination options now look like this:
public $paginate = array(
'Item' => array(
'findType' => 'byProject',
'limit' => 10,
'order' => array(
'Item.create_time' => 'desc'
),
'contain' => array(
'SubProject',
'Project'
),
'conditions' => array('Project.id' = $pid)
),
);
...where $pid is the id of the project to display. Some minor tweaks in the view code to accomodate the slightly different results array structure, and I was all set.
EDIT ===============
public $paginate = array(
'limit' => 10,
'order' => 'Item.create_time DESC', //'order' => array(''Item.create_time' => 'desc'),
'contain' => array(
'SubProject' => array(
'Project' => array(
'conditions' => array(
'id' => $project_id // Passed parameter
)
)
)
)
);
=================================================
Have you tried using conditions as in the following? Also, I did not write the 'order' section of the code the way you have it.
public $paginate = array(
'Item' => array(
'limit' => 10,
'order' => 'Item.create_time DESC', //'order' => array(''Item.create_time' => 'desc'),
'contain' => array(
'SubProject' => array(
'Project' => array(
'conditions' => array(
'id' => $project_id // Passed parameter
)
)
)
)
)
);
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')
)
)
);