How to access a related table field from a Controller? - cakephp

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

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 find by conditions in two joining tables in CakePHP

In my CakePHP app I have three tables:
Businesses, Towns and Categories.
A business can belong to multiple towns and multiple categories so I have created joining tables and hasMany and belongsTo relationships. Everything works fine when finding businesses by either Town or Category by using the Town or Category model to search, but I am completely stuck when I want to search for businesses in a certain town AND a certain category, eg. Plumbers in London.
The associations just don't seem to work when searching with the Business model and I get column not found errors when trying to use the associated tables. I would think that this would be along the lines of what needs to be done, but I can't get it to work:
$this->set('listings', $this->Business->find('all', array(
'conditions' => array(
'Business.approved' => 1,
'BusinessesCategory.category_id' => $id,
'BusinessesTown.town_id' => $town_id,
'Business.sasite' => 1
)
You need to join the tables to do that.
I will put above a example how has to work with category and you can do the town yourself.
$this->Business->find("all", array(
"joins" => array(
array(
"table" => "businness_categories",
"alias" => "BusinessesCategory",
"type" => "LEFT",
"conditions" => array(
"Businesses.id = BusinessesCategory.business_id"
)
),
array(
"table" => "categories",
"alias" => "Category",
"type" => "LEFT",
"conditions" => array(
"BusinessesCategory.category_id = Category.id"
)
)
),
'conditions' => array(
'Business.approved' => 1,
'Category.id' => $id,
)
));
You also could use a behavior to do that for you:
https://github.com/Scoup/SuperJoin
Hi I had a very similar setup and the same problem. This is how I would solve your problem:
As you dont give away to much of your code I make some assumptions:
- You implemented your search method in the BusinessController
- Your search argument for the town is stored in vaiable $where and the one for Category is stored in $what
Code if you only have conditions for one table
$this->Businesses->Town->recursive = -1;
....
$options['joins'] = array(
array('table' => 'towns',
'alias' => 'Town',
'type' => 'inner',
'conditions' => array(
'Business.town_id = Town.id',
)
)
);
$options['conditions'] = array(
'Town.townName' => $where
);
$result = $this->Business->find('all', $options);
Code if you have conditions for two table
$this->Businesses->Town->recursive = -1;
$this->Businesses->Category->recursive = -1;
....
$options['joins'] = array(
array('table' => 'towns',
'alias' => 'Town',
'type' => 'inner',
'conditions' => array(
'Business.town_id = Town.id',
)
),
array('table' => 'categories',
'alias' => 'Category',
'type' => 'inner',
'conditions' => array(
'Business.category_id = category.id',
)
)
);
$options['conditions'] = array(
'Town.townName' => $where,
'Category.categoryName' => $what
);
$result = $this->Business->find('all', $options);
You can use
$this->Business->find('all', array(
'conditions' => array(
'AND' => array(
'BusinessesTown.town_id' => $town_id,
'BusinessesCategory.category_id' => $id
)
),
'recursive' => 2
));

CakePHP 2.x containable behavior conditions on deep associations

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

cakephp model associations - find other child of parent model

I have associations:
Product -> hasMany -> ProductOption
Product -> hasMany -> ProductImage
I want to find the product data including ProductImage with the ProductOption.id
When I do this:
$this->Basket->Product->ProductOption->find('first',array(
'contain' => array(
'Product' => array(
'ProductOption' => array(
'conditions' => array('ProductOption.id = '.$id)
),
'ProductImage'
)
)
));
I get this:
array(
'ProductOption' => array(
'id' => '46',
'product_id' => '9',
),
'Product' => array(
'id' => '9',
'name' => 'Some product',
)
)
Which is most of what I want but without 'ProductImage' included. How do I get this in the array?
I think you're using too much recursion. If you're using Containable on Product, make the query on it, not ProductOption, like this:
$this->Basket->Product->find('first',array(
'contain' => array(
'ProductOption' => array(
'conditions' => array('ProductOption.id = '.$id)
),
'ProductImage'
)
));
And no need to use Product on 'contain' array, it's already guessed.
(I didn't try this, but almost sure)

Resources