In CakePHP if I run the following find command
$this->Instructor->find('all');
the following is returned:
Array
(
[0] => Array
(
[Instructor] => Array
(
[id] => 1
[user_id] => 3
[bio] => A billionaire playboy, industrialist and ingenious engineer, Tony Stark suffers a severe chest injury during a kidnapping in which his captors attempt to force him to build a weapon of mass destruction. He instead creates a powered suit of armor to save his life and escape captivity. He later uses the suit to protect the world as Iron Man.
)
[User] => Array
(
[id] => 3
[first_name] => Tony
[last_name] => Stark
[asurite_user] => tstark
[asurite_id] =>
[password] =>
[email] => tstark#example.com
[created] => 2012-10-30 09:57:36
[modified] => 2012-10-30 09:57:36
)
[Course] => Array
(
[0] => Array
(
[id] => 1
[slug] => beatles
[sln] => AAA001
[course_number] =>
[title] => The Beatles
[description] => This is a class about the Beatles. If this were a real description, more information would be listed here.
[state] =>
)
[1] => Array
(
[id] => 2
[slug] => elvis
[sln] => AAA002
[course_number] =>
[title] => Elvis: The King of Rock
[description] => All about the king of rock and roll, Elvis Presley.
[state] =>
)
)
)
[1] => Array
(
...
There is a many-to-many relationship between "Instructor" and "Course". How can I filter the results based on the "Course"s each "Instructor" belongs to? I tried the following without success:
$instructors = $this->Instructor->find('all', array(
'conditions' => array('Course.id' = 2)
));
If you're trying to restrict your parent item based on conditions against your child item, you can either do your main query through the child model instead of the main model, and contain() the rest, or use MySQL Joins - available in CakePHP via joins().
You need to (re)bind the associations for your Instructor model with hasOne and use these settings:
'CoursesInstructor' => array(
'className' => 'CoursesInstructor',
'foreignKey' => false,
'conditions' => 'Instructor.id = CoursesInstructor.id'),
'Course' => array(
'className' => 'Course',
'foreignKey' => false,
'conditions' => 'CoursesInstructor.course_id = Course.id');
This will generate the correct SQL with the required joins and your conditions will work.
Related
To explain the issue I'm having, I'll use an example. Let's say that I'm building a system where students can sign up for one or more afterschool courses, but a school official has to approve the sign-up in order for it to be valid. So I have these models:
Course (belongs to "Teacher", hasAndBelongsToMany "Student")
Student (hasAndBelongsToMany "Course")
Teacher (hasMany "Course")
Now let's say I want to see a list of all of the unapproved sign-ups that are for ninth-graders. That's easy:
$this->request->data = $this->Student->CoursesStudent->find('all', array(
'conditions' => array(
'CoursesStudent.is_approved' => null,
'Student.grade' => 9
)
));
The thing that I'm struggling with is pulling the teacher's data as well (more than just their ID). I've tried this:
$this->request->data = $this->Student->CoursesStudent->find('all', array(
'conditions' => array(
'CoursesStudent.is_approved' => null,
'Student.grade' => 9
),
'contain' => array(
'CoursesStudent',
'Course' => array(
'Teacher' => array(
'fields' => array(
'Teacher.id',
'Teacher.name'
)
)
),
'Student'
)
));
But it doesn't have any effect whatsoever on the returned array. To make it easy for you to throw this into an existing CakePHP project to play with, here is the database schema and data and here is what you can paste into a random action of an existing CakePHP project. This is what I'm trying to get (notice that the teacher's data is there):
Array
(
[0] => Array
(
[CoursesStudent] => Array
(
[id] => 1
[course_id] => 1
[student_id] => 1
[is_approved] =>
[created] => 2012-12-11 00:00:00
[modified] => 2012-12-11 00:00:00
)
[Course] => Array
(
[id] => 1
[teacher_id] => 1
[title] => Introduction to Improvisation
[start_date] => 2012-12-17
[end_date] => 2012-12-21
[created] => 2012-12-11 00:00:00
[modified] => 2012-12-11 00:00:00
[Teacher] => Array
(
[id] => 1
[first_name] => John
[last_name] => Doe
[created] => 2012-12-11 00:00:00
[modified] => 2012-12-11 00:00:00
)
)
[Student] => Array
(
[id] => 1
[first_name] => Bill
[last_name] => Brown
[grade] => 9
[created] => 2012-12-11 00:00:00
[modified] => 2012-12-11 00:00:00
)
)
)
Thanks!
You must set $recursive to -1 before using contain() will work.
Also, make sure your model is set to use the Containable Behavior. In this case, since 'Course' is also containing something, it needs to use the Containable behavior too.
(You could think about setting the below $actsAs in your AppModel to make Containable available to every model.)
//in your CoursesStudent model (and in your Course model)
public $actsAs = array('Containable');
And here's your find, but with setting recursive first:
$this->Student->CoursesStudent->recursive = -1;
$this->request->data = $this->Student->CoursesStudent->find('all', array(
'conditions' => array(
'CoursesStudent.is_approved' => null,
'Student.grade' => 9
),
'contain' => array(
'CoursesStudent',
'Course' => array(
'Teacher' => array(
'fields' => array(
'Teacher.id',
'Teacher.name'
)
)
),
'Student'
)
));
I have the folowing associations
post->primary->secondary
$results = $this->Post->find('all', array(
'conditions' => array(
'Post.post_id =' => 2,
'Primary.secondary_id !=' => null
),
'contain' => array(
'Primary' => array(
'Secondary' => array(
'conditions' => array('Secondary.short_code =' => 'code')
)
)
)
));
Returns this.
Array
(
[0] => Array
(
[Post] => Array
(
[id] => 2
[created] => 2012-10-29 09:48:29
[modified] => 2012-10-29 09:48:29
)
[Primary] => Array
(
[id] => 3
[secondary_id] => 6
[Secondary] => Array
(
[id] => 6
[short_code] => code
[created] => 2012-10-31 11:19:56
[modified] => 2012-10-31 11:20:03
)
)
)
However when I change
'conditions' => array('Secondary.short_code =' => 'code')
to
'conditions' => array('Secondary.short_code !=' => 'code')
it still returns the primary record, when I dont want it to.
Array
(
[0] => Array
(
[Post] => Array
(
[id] => 2
[created] => 2012-10-29 09:48:29
[modified] => 2012-10-29 09:48:29
)
[Primary] => Array
(
[id] => 3
[secondary_id] => 6
[Secondary] => Array
(
)
)
)
It's hard to know exactly what you're hoping to achieve, but I THINK it sounds like you're trying to limit the 'Primary' results based on conditions against the 'Secondary' model.
If that's the case, you're going to need to use joins() instead of contain().
The reason: When you use CakePHP's Containable Behavior, it actually does separate queries, then combines the results before returning the data to you. Doing it this way is great in many ways, but it does not allow you to limit parent results based on conditions against it's children.
To do that, just use joins(). (CakePHP syntax which creates MySQL JOIN)
Ive a HABTM relationship where the output is like below, what i would like to do is have cakephp return a "list" with just the "friend.id" and "friend.company_name" and exclude the "User".
Ive spent quite a while researching how to do this and cant get it to work.
My "search method" in users controller; i think i need to use the containable behaviour but im not sure what to do. ive managed to get the "friend" results only show 2 fields but i need to get rid of the "User" results. How do i do this?
The relationship is defined as in the user model file;
var $hasAndBelongsToMany = array(
'Friend' => array(
'joinTable' => 'retailerrelationships',
'className' => 'User',
'foreignKey' => 'retailer_id',
'associationForeignKey' => 'supplier_id'
)
);
user action in user controller:
$this->User->Behaviors->attach('Containable');
$users=$this->User->find('all',array('conditions'=>array('User.id'=>$this->Auth->user('id')),'contain'=>array('Friend.id','Friend.company_name')));
$this->set('users' ,$users);
debug Output
Array
(
[0] => Array
(
[User] => Array
(
[id] => 104
[username] => admin
)
[Friend] => Array
(
[0] => Array
(
[id] => 107
[company_name] => carskitchens
[Retailerrelationship] => Array
(
[id] => 12
[retailer_id] => 104
[supplier_id] => 107
[created] => 2012-03-28 10:14:23
[modified] => 2012-03-28 10:14:23
)
)
[1] => Array
(
[id] => 112
[company_name] => mr manufacturer
[Retailerrelationship] => Array
(
[id] => 13
[retailer_id] => 104
[supplier_id] => 112
[created] => 2012-03-28 11:26:52
[modified] => 2012-03-28 11:26:52
)
)
)
)
)
You should look at the Set class.
Try this
Set::combine($results, '{n}.Friend.id', '{n}.Friend.company_name');
I would like to be able to list the 10 most popular tags (subjects) of my site on the right menu as an element.
I however have been thinking 30 minutes about this now and unlike ending up with 1 statement to do this, I seem to be ending up with 3 statements that would drastically decrease the performance of the site.
I have a site with short stories and tags. This is a HABTM (Has-and-belongs-to-many) relationship between them using a table 'stories_tags'. A story can have multiple tags and a tag can be used by multiple stories.
The goal is listing $tagname ($storycountwiththattag), from highest number of stories per tag, to 10th highest. I have this so far, but it doesn't seem too close.
$tags = $this->Tag->find('all',array('fields'=>array('Tag.name')));
$tags_count = $this->Tag->Story->find('count',array('conditions'=>array('Story.tag'=>$tags)));
debug($tags_count);
I have tried a lot of possible queries. I could do it with restricted find('all')'s, or with:
Getting all ID's of all tags
Running 1 count-query per tag...
Use those results.
But I have picked Cakephp to make better applications so I wondered how you guys would do this! The site gets only a few hundred visitors daily, so performance is not extremely vital, but avoiding extremely dumb queries seems something I should try to do even if some performance penalty wouldn't matter.
use counterCache on your tag (although you can't use with HABTM though, you'll have to define another "Tag hasMany StoriesTag" relationship. You can also Cache the query result.
I make some test in the same scenario but differents ModelNames. I hope you can use it.
function top(){
$options = array(
'fields' => array('*','COUNT(CategoriesEnterprise.enterprise_id) AS num')
, 'group' => array( 'CategoriesEnterprise.category_id' )
, 'order' => 'num DESC'
, 'limit' => '3'
, 'joins' => array(array(
'table' => 'categories',
'alias' => 'Category',
'type' => 'LEFT',
'conditions' => array('CategoriesEnterprise.category_id = Category.id')
))
);
$categories = $this->Category->CategoriesEnterprise->find('all', $options);
debug($categories);
$this->autoRender = false;
}
The result
Array
(
[0] => Array
(
[CategoriesEnterprise] => Array
(
[category_id] => 3
[enterprise_id] => 7
)
[Category] => Array
(
[id] => 3
[name] => Turismo y Viajes
[modified] => 2011-05-16
[created] => 2011-04-14
)
[0] => Array
(
[num] => 4
)
)
[1] => Array
(
[CategoriesEnterprise] => Array
(
[category_id] => 24
[enterprise_id] => 5
)
[Category] => Array
(
[id] => 24
[name] => Compras
[modified] => 2011-05-05
[created] => 2011-05-05
)
[0] => Array
(
[num] => 3
)
)
[2] => Array
(
[CategoriesEnterprise] => Array
(
[category_id] => 32
[enterprise_id] => 8
)
[Category] => Array
(
[id] => 32
[name] => Salud y Belleza
[modified] => 2011-05-16
[created] => 2011-05-16
)
[0] => Array
(
[num] => 3
)
)
)
I've been banging my head against the wall with a really annoying issue. I have two model classes:
class User extends AppModel {
var $name = 'User';
var $hasMany = array(
'Application' => array(
'className' => 'Application',
'foreignKey' => 'user_id',
'dependent' => false,
)
);
}
class Application extends AppModel {
var $name = 'Application';
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
)
);
}
I want to pull Applications and the fields of User associated with it.
$this->Applications->find();
No matter what I set $recursive to, it's still giving me only one output:
Array
(
[Applications] => Array
(
[id] => 1
[user_id] => 3
[datecreated] =>
[status] =>
[source] => 1
)
)
On the other hand, when I pull the data from Users table with recursive set to 2, I get all the users, with their applications, WITH the user data associated with the application WITH the applications associated with the user. To put it plain and simple, here's what I get:
Array
(
[0] => Array
(
[User] => Array
(
[id] => 3
[email] => email#email.email
[password] => hashstring
[socialsecurityno] => 21232134123
[role_id] => 3
[firstname] => Firstname
[lastname] => Lastname
[status] => 1
)
[Application] => Array
(
[0] => Array
(
[id] => 1
[user_id] => 3
[datecreated] =>
[status] =>
[source] => 1
[User] => Array
(
[id] => 3
[email] => email#email.email
[password] => hashstring
[socialsecurityno] => 21232134123
[role_id] => 3
[firstname] => Firstname
[lastname] => Lastname
[status] => 1
[Application] => Array
(
[0] => Array
(
[id] => 1
[user_id] => 3
[datecreated] =>
[status] =>
[source] => 1
)
)
)
)
)
)
)
What I want, is to get from the Applications, its associated user info, and that's it. I'm pretty much out of ideas short of creating a habtm relationship between applications and users, which would be technically incorrect, as one user can have many applications but one application only has one user.
Right now, Applications is connected with Users via user_id in Applications table, which... should be kind of obvious, but probably should be noted anyway.
Any kind of help with this could be appreciated, I'm literally out of ideas.
If Model::find is called with no parameters, Cake treats it as though you made this request:
Model::find('first',array('limit'=>1));
This explains why it's only retrieving the first Application. To retrieve all Applications, modify your call to:
$this->Application->find('all');
This doesn't explain why Model::find is ignoring the model's recursive setting, and, indeed, I can't find a way to reproduce that issue, unless it's something silly like you have typoed the model name when setting the recursion level (e.g. $this->Applications->recursive = 1;; note the pluralization of "Application").
Hope this helps.