cakephp - one find() query to rule them all - php

I have the following model relationships defined:
class Publication extends AppModel {
var $name = 'Publication';
var $hasAndBelongsToMany = array(
'Author'=>array(
'className'=>'Author'
)
);
}
class Author extends AppModel {
var $name = 'Author';
var $hasAndBelongsToMany = array(
'Publication'=>array(
'className'=>'Publication'
)
);
var $belongsTo = array(
'College' => array (
'className' => 'College'
)
);
}
class College extends AppModel {
var $name = 'College';
var $hasMany = array(
'Department'=>array(
'className'=>'Department'
)
);
}
class Department extends AppModel {
var $name = 'Department';
var $belongsTo = array(
'College'=>array(
'className'=>'College'
)
);
}
The database tables are set up correctly (join tables for the HABTM, etc.). I am trying to find the one DB query to rule them all. I want to create a query that will find all of the publications with the associated authors, colleges, departments, etc. After getting data from a form, I have tried to run queries like this:
$conditions = array(
"Author.id" => $this->data['authors'],
"Publication.year" => $this->data['year']
);
$publications = $this->Publication->find('all', array('conditions' => $conditions));
This throws SQL errors saying that Author.id is not a valid field. Now, this is because a join with the 'authors' db table has not been completed by the time Author.id is being searched for. BUT, if I do this:
$pubs = $this->Publication->find('all', array('conditions' => $conditions));
then I get an array that has all of the Publications with all of the associated Authors (though, not the associated Colleges for some reason).
My question is this: what do I need to do to make the tables join before Author.id is searched for? I've attempted to use bindModel, containable, subqueries, but cannot get those to work for some reason (probably a ID10T error).
Thanks for any advice!
Edit:
The result of the following call:
$this->Publication->recursive = 2;
$pubs = $this->Publication->find('all');
are as follows:
Array (
[0] => Array (
[Publication] => Array ( [id] => 1 [title] => TestArticle [year] => 2011 [type_id] => 3 )
[Type] => Array ( [id] => 3 [type_name] => Journal Articles )
[Author] => Array (
[0] => Array ( [id] => 2 [firstname] => Jeremy [lastname] => Gustine [middle] => A [faculty] => 0 [college_id] => 4 [AuthorsPublication] => Array ( [id] => 3 [author_id] => 2 [publication_id] => 1 )
[College] => Array ( [id] => 4 [college_name] => Letters, Arts, and Sciences ) )
[1] => Array ( [id] => 3 [firstname] => George [lastname] => Obama [middle] => A [faculty] => 0 [college_id] => 6 [AuthorsPublication] => Array ( [id] => 2 [author_id] => 3 [publication_id] => 1 )
[College] => Array ( [id] => 6 [college_name] => School of Public Affairs ) )
[2] => Array ( [id] => 2 [firstname] => Jeremy [lastname] => Gustine [middle] => A [faculty] => 0 [college_id] => 4 [AuthorsPublication] => Array ( [id] => 1 [author_id] => 2 [publication_id] => 1 )
[College] => Array ( [id] => 4 [college_name] => Letters, Arts, and Sciences ) ) ) )
[1] => Array (
[Publication] => Array ( [id] => 2 [title] => TestBook [year] => 2010 [type_id] => 1 )
[Type] => Array ( [id] => 1 [type_name] => Books )
[Author] => Array (
[0] => Array ( [id] => 7 [firstname] => Sony [lastname] => Stuff [middle] => L [faculty] => 0 [college_id] => 5 [AuthorsPublication] => Array ( [id] => 4 [author_id] => 7 [publication_id] => 2 )
[College] => Array ( [id] => 5 [college_name] => Nursing and Health Science ) ) ) ) )
Hopefully that is somewhat readable...

Your second find is correct
i.e. $pubs = $this->Publication->find('all', array('conditions' => $conditions));
just above that try using recursive 2 (below code) to get related College too.
$this->Publication->recursive = 2;

Related

Merge duplicate keys in multi-dimensional array

I have a script that loops through a bunch of data collected from database tables. I've read other similar posts on StackOverflow about merging duplicate array keys, but none of them seem to work for me. Using the code below, I'm building all of the compiled data into an array:
$sql = 'SELECT * FROM '.$qstTable.' WHERE '.$type.'_qst_id = '.$answer['answer_qst'];
$result = $db->sql_query($sql);
$q = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
$sql = 'SELECT * FROM '.$catTable.' WHERE '.$type.'_cat_clean = "'.$q[$type.'_qst_cat'].'"';
$result = $db->sql_query($sql);
$cat = $db->sql_fetchrow($result);
$db->sql_freeresult($result);
$daField = $cat[$type.'_cat_name'];
if(count($allQsts)){
if(array_key_exists($daField, $allQsts)){
$daData = array(
'question' => array(
'id' => $q[$type.'_qst_id'],
'qst' => $q[$type.'_qst_qst'],
),
'answer' => array(
'id' => $answer['answer_id'],
'type' => $answer['answer_input'],
'content' => $answer['answer_content'],
'q_type' => $type,
)
);
array_push($allQsts[$daField], $daData);
}else{
$allQsts[$cat[$type.'_cat_name']][] = array(
'question' => array(
'id' => $q[$type.'_qst_id'],
'qst' => $q[$type.'_qst_qst'],
),
'answer' => array(
'id' => $answer['answer_id'],
'type' => $answer['answer_input'],
'content' => $answer['answer_content'],
'q_type' => $type,
)
);
}
}else{
$allQsts[$cat[$type.'_cat_name']][] = array(
'question' => array(
'id' => $q[$type.'_qst_id'],
'qst' => $q[$type.'_qst_qst'],
),
'answer' => array(
'id' => $answer['answer_id'],
'type' => $answer['answer_input'],
'content' => $answer['answer_content'],
'q_type' => $type,
)
);
}
And this is how my array turns out looking when it's all processed:
Array (
[Ancestry] => Array (
[0] => Array (
[question] => Array (
[id] => 1
[qst] => Has your family always lived in this country? Where did your family come from? How did they come here?
)
[answer] => Array (
[id] => 28
[type] => text
[content] => idk
[q_type] => life
)
)
)
)
Array (
[High School] => Array (
[0] => Array (
[question] => Array (
[id] => 158
[qst] => Who were your best friends in high school? Were they the same ones from grade school? Do you still keep in touch with them?
)
[answer] => Array (
[id] => 30
[type] => video
[content] => v-0bd3d270-2f24-0132-cd89-12313914f10b
[q_type] => life
)
)
)
)
Array (
[High School] => Array (
[0] => Array (
[question] => Array (
[id] => 124
[qst] => What year did you start high school? What high school did you go to? Did you like it? Did you ever wish you would've gone to a different high school?
)
[answer] => Array (
[id] => 36
[type] => text
[content] => Started HS in 1987
[q_type] => life
)
)
)
)
Array (
[Young Adult] => Array (
[0] => Array (
[question] => Array (
[id] => 213
[qst] => As a young adult did you stay in the same town as your friends or did you move to a new place and had to make new friends?
)
[answer] => Array (
[id] => 39
[type] => video
[content] => v-7d59df50-3bda-0132-cda7-12313914f10b
[q_type] => life
)
)
)
)
Array (
[Young Adult] => Array (
[0] => Array (
[question] => Array (
[id] => 207
[qst] => After high school - did you go to college, join the miltary, or did you get a job?
)
[answer] => Array (
[id] => 40
[type] => text
[content] => went to college at ASU
[q_type] => life
)
)
)
)
Array (
[Multiple Sclerosis] => Array (
[0] => Array (
[question] => Array (
[id] => 1278
[qst] => Do you know of any potential new drugs or treatments that are in development to treat multiple sclerosis? Are you optomistic?
)
[answer] => Array (
[id] => 33
[type] => text
[content] => vg hjc
[q_type] => pack
)
)
)
)
However, What I would like to do is combine nodes in the array that already exist, like so:
Array (
[Ancestry] => Array (
[0] => Array (
[question] => Array (
[id] => 1
[qst] => Has your family always lived in this country? Where did your family come from? How did they come here?
)
[answer] => Array (
[id] => 28
[type] => text
[content] => idk
[q_type] => life
)
)
)
)
Array (
[High School] => Array (
[0] => Array (
[question] => Array (
[id] => 158
[qst] => Who were your best friends in high school? Were they the same ones from grade school? Do you still keep in touch with them?
)
[answer] => Array (
[id] => 30
[type] => video
[content] => v-0bd3d270-2f24-0132-cd89-12313914f10b
[q_type] => life
)
)
[1] => Array (
[question] => Array (
[id] => 124
[qst] => What year did you start high school? What high school did you go to? Did you like it? Did you ever wish you would've gone to a different high school?
)
[answer] => Array (
[id] => 36
[type] => text
[content] => Started HS in 1987
[q_type] => life
)
)
)
)
Array (
[Young Adult] => Array (
[0] => Array (
[question] => Array (
[id] => 213
[qst] => As a young adult did you stay in the same town as your friends or did you move to a new place and had to make new friends?
)
[answer] => Array (
[id] => 39
[type] => video
[content] => v-7d59df50-3bda-0132-cda7-12313914f10b
[q_type] => life
)
)
[1] => Array (
[question] => Array (
[id] => 207
[qst] => After high school - did you go to college, join the miltary, or did you get a job?
)
[answer] => Array (
[id] => 40
[type] => text
[content] => went to college at ASU
[q_type] => life
)
)
)
)
Array (
[Multiple Sclerosis] => Array (
[0] => Array (
[question] => Array (
[id] => 1278
[qst] => Do you know of any potential new drugs or treatments that are in development to treat multiple sclerosis? Are you optomistic?
)
[answer] => Array (
[id] => 33
[type] => text
[content] => vg hjc
[q_type] => pack
)
)
)
)
How can I modify the code above to do this?
EDIT - Updating to include some of the code I've tried to compress the array:
After the full $allQsts array is created, I looped it through this and tested the output, but each entry was duplicated even more.
$sortedIt = array();
foreach($allQsts as $m => $n){
if(!isset($sortedIt[$m])){
$sortedIt[$m] = array();
}
$sortedIt[$m] = $n;
}
I've also been playing around with array_merge_recursive but have yet to get anywhere close.
Assuming this is run in a loop and $allQsts points to the same array each time, you can simplify the code drastically:
$daField = $cat[$type.'_cat_name'];
$daData = array(
'question' => array(
'id' => $q[$type.'_qst_id'],
'qst' => $q[$type.'_qst_qst'],
),
'answer' => array(
'id' => $answer['answer_id'],
'type' => $answer['answer_input'],
'content' => $answer['answer_content'],
'q_type' => $type,
)
);
if (array_key_exists($daField, $allQsts)) {
$allQsts[$daField][] = $daData;
} else {
$allQsts[$daField] = array($daData);
}
This by itself should be enough to get the desired data structure.

Formatting CakePHP join statement

I'm using CakePHP 2.3.8 and I'm trying to more efficiently join two tables. buildings and building_rental_rates
buildings
id | name | description | property_owner | building_type
1 Big Big Building 1 1
building_rental_rates
id | building_type | rate_name | rate
1 1 daily 150.00
2 1 hourly 15.00
I want to look up a building and select the different rental rates. The tables are joined on building_type. Here's the find statement I have
$buildings = $this->Building ->find('all',array(
'Building.property_owner' => '1',
'fields' => array('Building.*','BuildingRentalRate.*'),
'joins' => array(
array(
'table' => 'building_rental_rates',
'alias' => 'BuildingRentalRate',
'type' => 'inner',
'conditions' => array(
'Building.building_type = BuildingRentalRate.building_type'
)
)
)
));
Here is the result
Array
(
[0] => Array
(
[Building] => Array
(
[id] => 1
[name] => Big
[description] => Big Building
[property_owner] => 1
[building_type] => 1
)
[BuildingRentalRate] => Array
(
[id] => 1
[building_type] => 1
[rate_name] => daily
[rate] => 150.00
)
)
[1] => Array
(
[Building] => Array
(
[id] => 1
[name] => Big
[description] => Big Building
[property_owner] => 1
[building_type] => 1
)
[BuildingRentalRate] => Array
(
[id] => 2
[building_type] => 1
[rate_name] => hourly
[rate] => 15.00
)
)
)
While the data is found properly, it's a pain to traverse through it. Can I use a join statement to result in this output? Notice how the BuildingRentalRate is an array containing all entries of that table that share the same building_type
Array
(
[0] => Array
(
[Building] => Array
(
[id] => 1
[name] => Big
[description] => Big Building
[property_owner] => 1
[building_type] => 1
)
[BuildingRentalRate] => Array
(
[0] => Array
(
[id] => 1
[building_type] => 1
[rate_name] => daily
[rate] => 150.00
)
[1] => Array
(
[id] => 2
[building_type] => 1
[rate_name] => hourly
[rate] => 15.00
)
)
)
)
I know Cake can output results like this when using model associations, but I wasn't able to get my associations correct apparently because it kept on joining Building.id on BuildingRentalRate.building_type (it should be Building.building_type = BuildingRentalRate.building_type)
Even with my association like this...
//Building.php
public $hasMany = array('BuildingRentalRate' => array('foreignKey' => 'building_type'));
//BuildingRentalRate.php
public $belongsTo = array('Building' => array('foreignKey' => 'building_type'));
It would join Building.id on BuildingRentalRate.building_type despite specificying building_type in both models as the foreign key.
Can I do some kind of nested SQL query in the conditions or is there an easier way of doing this?
you should be able to avoid using joins if in BuildingRentalRate.php you set your relationship this way
$public belongsTo = array(
'Building' => array(
'foreignKey' => false,
'conditions' => array
(
'Building.building_type' => 'BuildingRentalRate.building_type'
)
)
);

cakephp formatting an array

My goal is to display 1 Projects and many Keywords to it. My DB is simple:
Project has many Keywords
Keyword belongs to Project
So far so good. Now I try to get the Information in my ProjectsController:
public function view($id = null)
{
$this->Project->bindModel(array('hasMany' => array('Keyword' => array('className' => 'Keyword',
'foreignKey' => 'project_id')
)), false);
$this->paginate['Project']['conditions'] = array('Project.id' => $id);
$this->paginate['recursive'] = '2';
$this->set('projects', $this->paginate('Project'));
$Projects = $this->paginate('Project');
$this->set('projects', $this->paginate());
}
by printing out the array it looks a bit unexpected:
Array
(
[0] => Array
(
[Project] => Array
(
[id] => 3
[title] => Foo
[created] => 2013-08-05 17:39:07
[modified] => 2013-08-05 17:39:07
)
[Keyword] => Array
(
[0] => Array
(
[id] => 1
[project_id] => 3
[title] => Num1
[demand] => 50000000000
[competition] => 37889.56700
[cpc] => 676.50
[created] => 2013-06-26 17:54:48
[modified] => 2013-09-19 13:37:25
)
)
)
[1] => Array
(
[Project] => Array
(
[id] => 4
[title] => Bar
[created] => 2013-08-05 17:39:07
[modified] => 2013-08-05 17:39:07
)
[Keyword] => Array
(
[0] => Array
(
[id] => 3
[project_id] => 4
[title] => Num1
[demand] => 76534000000
[competition] => 5555.55560
[cpc] => 99.34
[created] => 2013-06-26 17:54:48
[modified] => 2013-09-19 13:37:36
)
)
)
)
Now I have the problem, how do i display it with the right Project.id? because the new created array contains a different id than Project.id. My question is how do I filter the right Project.id for display it only in my /View/[id]
EDIT
I think the best way to work with, is an array structure like this:
Array
(
[Project] => Array
(
[id] => 3
[title] => Lerncoachies
[created] => 0000-00-00 00:00:00
[modified] => 2013-08-05 17:39:07
)
[Keyword] => Array
(
[0] => Array
(
[id] => 1
[project_id] => 3
[title] => Num1
[demand] => 50000000000
[competition] => 37889.56700
[cpc] => 676.50
[created] => 2013-06-26 17:54:48
[modified] => 2013-09-19 13:37:25
)
)
)
)
From descriptions it looks like you'd like to paginate Keywords not Projects - so you have 'unexpected result'.
This is an expected result for paginating Projects.
If you'd like to paginate Keywords then:
$this->Project->recursive = 1;
$project = $this->Project->findById($id);
$this->loadModel('Keywords'); // I don't remember if this is needed
$this->paginate['Keywords'] = array(
'project_id' => $project['Project']['id']
);
$this->set('keywords', $this->paginate('Keyword'));
$this->set('project', $project);
You'll have a view with 1 Project and you'll be able to paginate Keywords related to given project with sorting.
public function view($id = null) {
$this->Project->bindModel(array(
'hasMany' => array('Keyword' => array(
'className' => 'Keyword',
'foreignKey' => 'project_id')
)), false
);
$this->Project->recursive = 2;
$project = $this->Project->findById($id);
}
Now your project array should suffice your requirement.

Filter Pagination results on Contained column

I have two tables in my database Members and Memberitems. I created two models for it. one is, Member and another is Memberitem
Member model has hasMany relation to Memberitem, and Memberitem model has belongsTo relations with my Member model.
Memberitem entries has specific categorization based on color, like, Red, Pink, Green, etc.
Now I want to select all the members who has atleast one Pink color memberitem using pagination.
Currently I am using:
$this->paginate = array(
'limit' => 5,
'contain' => array(
'Memberitem' => array(
'conditions' => array('Memberitem.color' => 'Pink')
)
)
);
But its showing all the Members.
output is this:
Array
(
[0] => Array
(
[Member] => Array
(
[id] => 1
[first_name] => fh
[last_name] => g
)
[Memberitem] => Array
(
[0] => Array
(
[id] => 1
[name] => item2
[color]=> Pink
)
)
)
[1] => Array
(
[Member] => Array
(
[id] => 2
[first_name] => ad
[last_name] => vd
)
[Memberitem] => Array
(
)
)
[2] => Array
(
[Member] => Array
(
[id] => 3
[first_name] => ae
[last_name] => sdi
)
[Memberitem] => Array
(
[0] => Array
(
[id] => 3
[name] => item1
[color]=> Pink
)
)
)
)
Its showing this result. Member of empty memberitem is still there. I want only 1st and 3 record in result.
This is a classic :-) It takes a while to get it though.
The Cake manual has an excellent peace on this. You should read: http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html#containing-deeper-associations
So if you want only Members with certain MemberItems, you should query on the MemberItems and contain the Member Model. You can actually do this from Member Model:
$this->Member->Memberitem->find('all', array(
'conditions' => 'Memberitem.color = "Pink"',
'contain' => 'Member',
));
or maybe a bit more catered to you app:
$this->paginate['Memberitem'] = array(
'contain' => array('Member'),
'conditions' => array(
'Memberitem.color' => 'Pink',
),
);
$items = $this->paginate('Memberitem');
Hope that helps!

How to group elements of array using Primary and secondary key?

I've got this array:
Array
(
[0] => Array
(
[id]=>1
[account_id] => 1
[object_id] => 43
[object_type] => PHOTO
[action_type] => UPLOAD_PHOTO
)
[1] => Array
(
[id] => 1
[account_id] => 1
[object_id] => 42
[object_type] => PHOTO
[action_type] => UPLOAD_PHOTO
)
[2] => Array
(
[id] => 1
[account_id] => 1
[object_id] => 41
[object_type] => PHOTO
[action_type] => UPLOAD_PHOTO
)
[3] => Array
(
[id] => 2
[account_id] => 2
[object_id] => 1
[object_type] => USER
[action_type] => FOLLOW_USER
)
[4] => Array
(
[id] => 1
[account_id] => 1
[object_id] => 2
[object_type] => USER
[action_type] => FOLLOW_USER
)
[5] => Array
(
[id] => 1
[account_id] => 1
[object_id] => 1
[object_type] => PHOTO
[action_type] => UPLOAD_PHOTO
)
)
Now I want to group elements have same value (for example UPLOAD_PHOTO) by id as Primary Key and action_type as Secondary Key, like:
Array
(
[0] => Array(
[id] => 1
[account_id] => 1
[actions] => array(
[1] => array(
[object_id] => 42
[object_type] => PHOTO
[action_type] => UPLOAD_PHOTO
)
[2] => array(
[object_id] => 43
[object_type] => PHOTO
[action_type] => UPLOAD_PHOTO
)
)
)
[2] => Array
(
[id] => 1
[account_id] => 1
[actions] = array(
[0] => Array
(
[object_id] => 3
[object_type] => USER
[action_type] => FOLLOW_USER
)
[1] => Array
(
[object_id] => 4
[object_type] => USER
[action_type] => FOLLOW_USER
)
)
)
)
I tried some solutions but didn't succeed.
When constructing your output array, you'll want to use meaningful array keys. That way you can find the out element that the in element needs to be added to:
$in = (...your array...);
$out = array();
foreach($in as $element) {
$id = $element['id'];
//If that id hasn't been seen yet, create the out element.
if (!isset($out[$id])) {
$out[$id] = array(
'id'=>$element['id'],
'account_id' => $element['account_id'],
'actions' => array()
);
}
//Add that action to the out element
$out[$id]['actions'][] = array(
'object_id' => $element['object_id'],
'object_type' => $element['object_type'],
'action_type' => $element['action_type']
);
}
I'm not sure why your input elements have different fields... If this is a desired feature, where one set of possible fields belongs to the id group and another set belongs to the action, you can do this instead (slightly less readable, but more flexible):
$in = (...your array...);
$out = array();
//These define which fields from an
//input element belong to the id,
//and which to the action.
$id_fields = array_flip(array('id','account_id','object_user_id');
$action_fields = array_flip(array('object_id','object_type','action_type');
foreach($in as $element) {
$id = $element['id'];
//If that id hasn't been seen yet, create the out element.
if (!isset($out[$id])) {
$out[$id] = array_intersect_key($element, $id_fields);
$out[$id]['actions'] = array();
}
//Add that action to the out element
$out[$id]['actions'][] = array_intersect_key($element, $action_fields);
}
This is a better solution if the input elements can be different, because if an expected field (other than 'id') is missing, the script will cope with it easily.

Categories