CakePHP: COUNT function on a hasmany association - php

I'm trying to join a model through a hasmany association and then count the number of records. Basically I have users and projects. I want to display the number of associated projects on the user index action.
var $hasMany = array('Project' =>
array('className' => 'Project',
'conditions' => '',
'order' => '',
'limit' => '',
'foreignKey' => 'user_id',
'dependent' => true,
'exclusive' => false,
'finderQuery' => '',
'fields' => '',
'offset' => '',
'counterQuery' => '',
'counterCache' => true
)
);
So I have joined to Project model on user_id and have set the counterCache to true. The question is how do I access this in the user index view?
I've tried
<?php echo $user['Project']['project_count']; ?>
and
<?php echo $user['User']['project_count']; ?>
Can someone help me?
Jonesy

counterCache need to be in the belongsTo association. In your example you need to have project_count in the users table while in the project model's association belongsTo you need to activate the counterCache to true. Check the manual

If you've done all that and the project_count is still not updating, try turning off caching in app/config/core.php
I ran into that issue when I updated the database schema but Cake was caching the old schema and thus no counter field was found.

Related

Cakephp Model ignores condition in belongsTo

I have 2 Models that are joined but not thrue the id's but with another field.
class Post extends AppModel{
public $useDbConfig = 'test';
public $actsAs = array('Containable');
public $belongsTo= array(
'User' => array(
'className' => 'User',
'foreignKey' => false,
'type' => 'inner',
'conditions' => array('User.field = Post.field')
));
}
But the cakephp is not connecting the 2 models correctly. To every Post it just gives the the first user (User with id 1). When I check the SQL-Statements the User statement is following:
SELECT `User`.`id`, `User`.`group_id`... FROM `users` AS `User` WHERE 1 = 1
Any ideas why this is not working?
I also tried joining the models manually:
$this->paginate = array(
'limit' => '15',
'joins' => array(
'INNER JOIN users User ON (User.field = Post.field)'
)
);
$posts= $this->paginate('Post');
Any ideas?
EDIT
I totally forgot to mention that these 2 tables are in different databases. Maybe there is the problem. But I had no problem joining tables over different databases so far.
EDIT
I was wrong about the join so I changed it from "belongsTo" to "hasOne" since a post can only be written by one user. But still no changes.
You can use the foreignKey parameter to any field, not necessarily the id:
Post model:
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'some_user_field'
)
);
User model:
public $hasMany = array (
'Post' => array (
'className' => 'Post',
'foreignKey' => 'some_user_field',
'dependent' => true
)
);
According to the book:
It is not mandatory to follow CakePHP conventions. You can easily
override the use of any foreignKey in your associations definitions.
Nevertheless, sticking to conventions will make your code less
repetitive and easier to read and maintain.
Otherwise, make joins manually:
$this->paginate = array(
'limit' => '15',
'joins' => array(
array(
'table' => 'users',
'alias' => 'User',
'type' => 'inner',
'conditions' => array('User.field = Post.field')
)
)
);
I found a solution although I don't understand why the others won't work. I put
$this->Post->primaryKey = 'field';
right before
$posts= $this->paginate('Post');
and now it works. Can anyone tell me why the other ways won't work?

Foreign Key in 2 tables (CakePHP)

I have 3 tables: staffs, users and threads.
Both the staffs and users can create threads and a thread belongs to a user.
In thread, there is a user_id (FK - BelongsTo). The question is, How can I identify who created the thread. If I create a field called creator_id I still don't know If it's a staff or a user who created the thread.
I also tried a different approach by creating a field called creator (enum('staff', 'user)). The limitation with this method is, when I am using Thread->find(), I couldn't figure out a way to retrieve the information of the creator.
What options do I have to achieve what I want?
Do the Model Association as the following in Model for Thread. Create a field called creator_id and store the id of the user/staff who created the thread,
public $belongsTo = array(
'Client' => array(
'className' => 'Staff',
'foreignKey' => 'creator_id',
'conditions' => array('Thread.creator' => 'staff'),
'fields' => '',
'order' => ''
),
'User' => array(
'className' => 'User',
'foreignKey' => 'creator_id',
'conditions' => array('Thread.creator' => 'user'),
'fields' => '',
'order' => ''
)
);

Cakephp hasManyThrough relationship with user_id being used twice

I have created hasManyThrough table like below:
id user_id friend_id
I also have users table like below:
id username password
I want to have both user_id and friend_id belongsTo relation with user's id column. So I have written below two models:
Friend model
class Friend extends AppModel {
var $name = 'Friend';
//The Associations below have been created with all possible keys, those that are not needed can be removed
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Friend' => array(
'className' => 'User',
'foreignKey' => 'friend_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
User model
class User extends AppModel {
var $name = 'User';
var $displayField = 'username';
var $hasMany = array(
'User' => array(
'className' => 'Friend',
'foreignKey' => 'user_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
),
'Friend' => array(
'className' => 'Friend',
'foreignKey' => 'friend_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
}
I'm not sure why but when I try to view this via controller in scaffolding, it comes up with 500 server error?? Is there something wrong with the model config that is creating wrong SQL?
OK it seems like error is due to Zend Optimizer according to some forums. This is creating Segmentation Fault when baking!
I cannot turn off this in the third party hosted server so I might have to move to my local server and test so I can see Cake errors.
OK below is the error (it's very long so I'm not going to put all but below gives a bit of an idea):
Fatal error: Maximum function nesting level of '100' reached, aborting! in /usr/share/php/cake/libs/debugger.php on line 248 Call Stack: 0.0011 352048 1. {main}()
Most probably problem is occurring due to nesting of Friend Model construct and which is occurring due to use of same alias of Friend for User and Friend table has well. Here is what happening i guess:
Friend Model being initialized, which in its constructs during model binding initialize User Model.
Now when User Model is initialized, during its binding Process, It Tries to bind Friend Model with alias as User, but due to use of Singleton pattern in Models, Instead of Friend Model instance ClassRegistry returns instance of User, resulting User model binding to itself.
Similarly is the case when User is Initialized first.Change Alias of relations like Friend_2,
or something else, ie Dont repeat Alias in Model relations.
Look at my answer in CakePHP Twitter-clone: Can't get follow-system to work. It`s the same but with friends rather then followers.
You just make a user_users table with a user_id (User ID) field and child_user_id (Friend ID), you can use the user_users table again to make similar relations eg user HABTM followers, user HABTM followers
Sorry for the late response, I thought I posted the answer a day ago but clearly it didn't post.

CakePHP: Retrieving records based on field in related model

I'm trying to search based on a field in a related model. I can do so with the belongsTo model, but not the hasMany model. I'm sure this is something simple I'm overlooking but I can't see it. Could someone point me in the right direction (or even the cakebook page I need - searched, but didn't find anything that looked like this). Thanks
In groups controller: (this doesn't work)
$group = $this->Group->find('all', array('conditions' => array('Voucher.id' => '55')));
This does:
$group = $this->Group->find('all', array('conditions' => array('TourOperator.id' => '3')));
Background file snippets:
Group model:
var $belongsTo = array(
'TourOperator' => array(
'className' => 'TourOperator',
'foreignKey' => 'tour_operator_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
var $hasMany = array(
'Voucher' => array(
'className' => 'Voucher',
'foreignKey' => 'group_id',
'dependent' => true,
'conditions' => '',
'fields' => '',
'order' => 'Voucher.date, Voucher.meal_type_id'
)
);
Voucher model:
var $belongsTo = array(
'Group' => array(
'className' => 'Group',
'foreignKey' => 'group_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
Tour Operator model:
var $hasMany = array(
'Group' => array(
'className' => 'Group',
'foreignKey' => 'tour_operator_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
Update (as posted in comment below, but clearer to read here)
I've ended up using this.
$groups = $this->Group->Voucher->find('all', array('fields' => 'DISTINCT group_id', 'conditions' => array('Voucher.status' => 'pending')));
$group_ids = Set::extract($groups, '/Voucher/group_id');
$data = $this->Group->find('all', array('conditions' => array('Group.id' => $group_ids)));
I get a distinct list of group IDs matching my criteria, create an array of just the IDs and then use that to pull the groups so that the arrays are ordered as I expect them in my view.
Can you recast your query to $this->Voucher->find, and then use the associated Group data returned from that query? As you say, there's no problem finding groups by their TourOperator id, and the Groups/Voucher relationship looks like it's the inverse of that.
With CakePHP's Containable behaviour you can't use conditions on related data in hasMany and hasAndBelongsToMany associations. This is because it uses seperate queries to get those associations.
With the Linkable behaviour, however, you can use data from hasMany associations to filter records. The behaviour can be downloaded here: https://github.com/Terr/linkable
Linkable uses standard JOINs to, well, join related data. This means it doesn't pull in all the related data (ie, all the Vouchers related to your Group(s)), just like a normal SELECT. However, it can work together with Containable to do both.
You can find examples of how Linkable works in the README.
(disclaimer: I currently maintain Linkable, but that is not the reason I point to it)

How do I query data in CakePHP using HABTM relationships?

I'm working on a CakePHP 1.2 application. I have a model "User" defined with a few HABTM relationships with other tables through a join table.
I'm now tasked with finding User information based on the data stored in one of these HABTM tables. Unfortunately, when the query executes, my condition is rejected with an error about a missing table. Upon inspection it seems that CakePHP is not including any of the HABTM tables in the select statement.
My User HABTM relationship is as follows:
var $hasAndBelongsToMany = array(
'Course' => array(
'className' => 'Course',
'joinTable' => 'course_members',
'foreignKey' => 'user_id',
'associationForeignKey' => 'course_id',
'conditions' => '',
'order' => '',
'limit' => '',
'uniq' => false,
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
),
'School' => array(
'className' => 'School',
'joinTable' => 'school_members',
'foreignKey' => 'user_id',
'associationForeignKey' => 'school_id',
'conditions' => '',
'order' => '',
'limit' => '',
'uniq' => false,
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
),
'Team' => array(
'className' => 'Team',
'joinTable' => 'team_members',
'foreignKey' => 'user_id',
'associationForeignKey' => 'team_id',
'conditions' => '',
'order' => '',
'limit' => '',
'uniq' => false,
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
The error is:
SQL Error: 1054: Unknown column
'School.name' in 'where clause'
And finally, the query it is trying to execute
SELECT
`User`.`id`, `User`.`username`, `User`.`password`, `User`.`firstName`, `User`.`lastName`, `User`.`email
`, `User`.`phone`, `User`.`streetAddress`, `User`.`city`, `User`.`province`, `User`.`country`, `User
`.`postal`, `User`.`userlevel`, `User`.`modified`, `User`.`created`, `User`.`deleted`, `User`.`deleted_date
` FROM `users` AS `User` WHERE `User`.`id` = 6 AND `School`.`name` LIKE '%Test%' LIMIT 1
Turn your debug level up to 2 and look at the SQL output. Find the query that your code is generating and you'll notice there are several. The ORM layer in CakePHP doesn't join HABTM related tables in the first query. It gets the results from the first select, then separately fetches the HABTM data for each item. Because the join table is not in the first query, your condition, which is intended for use on a joined table, results in the error you are seeing.
The cook book has a section on HABTM associations and fetching data based on conditions in the HABTM table that will fit your requirements.
From the Cookbook: http://book.cakephp.org/view/83/hasAndBelongsToMany-HABTM
One option is to search the Tag model (instead of Recipe), which will also give us all of the associated Recipes.
$this->Recipe->Tag->find('all', array(
'conditions' => array('Tag.name' => 'Dessert')));
We could also use the join table model (which CakePHP provides for us), to search for a given ID.
$this->Recipe->bindModel(array('hasOne' => array('RecipesTag')));
$this->Recipe->find('all', array(
'fields' => array('Recipe.*'),
'conditions' => array('RecipesTag.tag_id' => 124) // id of Dessert
));
It's also possible to create an exotic association for the purpose of creating as many joins as necessary to allow filtering, for example:
$this->Recipe->bindModel(array('hasOne' => array('RecipesTag',
'FilterTag' => array(
'className' => 'Tag',
'foreignKey' => false,
'conditions' => array('FilterTag.id = RecipesTag.tag_id')
))));
$this->Recipe->find('all', array(
'fields' => array('Recipe.*'),
'conditions' => array('FilterTag.name' => 'Dessert')
));
Your table should be called "schools_users" and not "school_members" because it's many-to-many, thus using the plural form in the table name on both sides is appropiate.
You also set the Model ClassName "School" as Alias for the HABTM. You should change that to something more generic like "Schools" as in "User is in and has many SchoolS" to avoid conflicts.
And another hint: Try to find the user "via" the School Model rather than the User Model.
$this->User->Schools->find()
Hope this helps.
FWIW, your join tables do appear to be "oddly named" insofar as they don't follow the convention described here:
http://book.cakephp.org/view/83/hasAndBelongsToMany-HABTM
In any case, good luck, I remember HABTM being a butt-pain in CakePHP.

Categories