CakePHP get model structure in controller action - cakephp

I want to get the current model's structure from the controller, similar to the return of $this->modelName->read(null, id), but without the actual data in the record, just the structure.
Is this something Cake has built in?

I do not know of any such thing, although you can call, $this->ModelName->schema(); which will give output like:
array(
'id' => array(
'type' => 'integer',
'null' => false,
'default' => null,
'length' => (int) 11,
'key' => 'primary'
)
);
So you could use that to write something on your own like:
$schema = $this->Model->schema();
$values = array_fill ( 0 , count($schema), '' );
$model = array('Model' => array_combine(array_keys($schema), $values));

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)

Paginate related data in hasMany relationship

I'm having some trouble trying to paginate related data in a hasMany relationship
I have two models - Collection and Item
Collection hasMany Item
Item belongsTo Collection
In the Collection view.ctp it shows the collection details for the ID being passed along with related data of the Items. It receives this data from a find "first" call.
$this->set('collection', $this->Collection->find('first', $options));
With the resulting array looking like:
array(
'Collection' => array(
'id' => '4fc923f5-0a58-453f-9507-0c0c86106d80',
'name' => '<collection_name>'
),
'Item' => array(
(int) 0 => array(
'id' => '4fc92403-000c-4ed2-9dae-0c0c86106d80',
'name' => 'Item1'
),
(int) 1 => array(
'id' => '4fc9241b-35ec-4810-b511-0c0c86106d80',
'name' => 'Item2'
),
(int) 2 => array(
'id' => '4fc96e5d-ad74-4c05-a9da-0c0c86106d80',
'name' => 'Item3'
),
(int) 3 => array(
'id' => '4fc96e64-a110-4036-bea2-0c0c86106d80',
'name' => 'Item4'
)
)
Now I can get the same results from this paginate call except there is the added 0 index
$this->set('items', $this->paginate('Collection', array('Collection.id'=>$id)));
array(
(int) 0 => array(
'Collection' => array(
'id' => '4fc923f5-0a58-453f-9507-0c0c86106d80',
'name' => '<collection_name>'
),
'Item' => array(
(int) 0 => array(
'id' => '4fc92403-000c-4ed2-9dae-0c0c86106d80',
'name' => 'Item1'
),
(int) 1 => array(
'id' => '4fc9241b-35ec-4810-b511-0c0c86106d80',
'name' => 'Item2'
),
(int) 2 => array(
'id' => '4fc96e5d-ad74-4c05-a9da-0c0c86106d80',
'name' => 'Item3'
),
(int) 3 => array(
'id' => '4fc96e64-a110-4036-bea2-0c0c86106d80',
'name' => 'Item4'
)
)
)
);
I can perform an array_shift on the array returned but then the pagination doesn't work. I can try creating a custom paginate function for this but wanted to know if there's a simplier way to do this with the standard paginate I'm using since it has the data I want, just extra first array parent element [0].
Thanks in advance for any suggestions.
<?php
// Don't pull in child data, we'll fetch that manually.
$this->Collection->contain(); // Assuming you have this behavior.
// Get the collection.
$collection = $this->Collection->find('first', $options);
// Conditions
$this->paginate['Item'] = array(
'conditions' => array(
'Item.collection_id' => $collection['Collection']['id'];
)
);
// Get the items
$items = $this->paginate('Item');
// Return to original child model structure.
array_walk($items, function(&$v) { // Anonymous functions requires PHP 5.3
$v = $v['Item'];
});
// Add it to the collection array.
$collection['Item'] = $items;
?>
That's the way I implement pagination on child models. Give it a shot, might work for you too.
-A

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')
)
)
);

Drupal allowed_values_function does not get called when creating a field

For some reason my allowed_values_function never gets called when showing a field on a user bundle. Code:
function get_business_units()
{
$options = entity_load('business_unit', FALSE, NULL, FALSE);
$opt = bu_to_list_values($options);
return $opt;
}
function MYMODULE_enable()
{
if (!field_info_field('field_user_business_unit')) {
$field = array(
'field_name' => 'field_user_business_unit',
'type' => 'text',
'settings' => array(
'allowed_values' => array(),
'allowed_values_function' => 'get_business_units',
)
);
field_create_field($field);
// Create the instance on the bundle.
$instance = array(
'field_name' => 'field_user_business_unit',
'entity_type' => 'user',
'label' => 'Business Unit',
'bundle' => 'user',
'required' => FALSE,
'settings' => array(
'user_register_form' => 1,
),
'widget' => array(
'type' => 'options_select',
),
);
field_create_instance($instance);
}
}
The field is created, and even displayed on the users "edit" page when editing their info. But the only value is "Select" or "None". My method is never called (I even placed a debug point). This is all in MYMODULE.install file.
The problem is: 'type' => 'text'.
You have to use: 'type' => 'list_text'.
Allowed values is meaningless for a text type.
Your get_business_units() function needs to be in the MYMODULE.module file; the .install files aren't included in a normal Drupal bootstrap.
Have you tried
drush features-revert MYMODULE ?

Using postConditions with CakePHPs Security Component

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']);

Resources