I'm fairly new to cakephp and I'm having a problem with a complicated find function on my models.
I have a number of Groups each containing a number of Users, and each group can make a number of Orders. Each order consists of a number of OrderAmounts which contain an amount and a user_id (relating to a User).
I have a find which finds an Order, and returns all users in the Group relating to that Order and any OrderAmounts corresponding to that user:
$currentOrder = $this->Order->find('first', array(
'conditions' => array(
'Order.group_id' => $this->Session->read("Auth.User.group_id")
),
'contain' => array(
'Group' => array(
'User' => array(
'OrderAmount' => array(
'OrderAmountType'
)
)
)
)
));
What I now want to do is to return a list of all the Users in the Group relating to the Order above who do not have a corresponding OrderAmount.
So far I have this, but I'm not sure where to put the condition to exclude users with OrderAmounts - if I put the conditions in the contains part it simply removes the OrderAmounts from the model, and if I put them in the top level conditions in the find I get an error.
$currentOrderOutstanding = $this->Order->Group->User->find('all', array(
'conditions' => array(
'Group.id' => $this->Session->read("Auth.User.group_id")
),
'fields' => array('User.id'),
'contain' => array(
'OrderAmount',
'Group'
)
));
Take a look at this example from the tutorial:
$this->User->find('all', array(
'contain'=>array(
'Profile',
'Account' => array(
'AccountSummary'
),
'Post' => array(
'PostAttachment' => array(
'fields' => array('id', 'name'),
'PostAttachmentHistory' => array(
'HistoryNotes' => array(
'fields' => array('id', 'note')
)
)
),
'Tag' => array(
'conditions' => array('Tag.name LIKE' => '%happy%')
)
)
)
));
Notice that all the conditions are inside the 'contain' array. Does it help?
Related
I have a hasmany relationship between Guardian ans Student. A Guardian hasmany Students. I cant get the required fields from containable object students, instead I get everything from Students but I do get the required fields from Guardian.
http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
$this->Guardian->Behaviors->load('Containable');
$guardians =$this->Guardian->find('all',array(
'contain'=>array('Student',
array( 'fields'=> array('Student.guardian_id,Student.id,Student.first_name' ))),
'order' => array('guardian_first_name ASC'),
'fields'=> array('Guardian.guardian_first_name,Guardian.guardian_last_name,Guardian.id' ),
'recursive'=> -1
));
array(
(int) 0 => array(
'Guardian' => array(
'guardian_first_name' => '',
'guardian_last_name' => '',
'id' => '166'
),
'Student' => array(
(int) 0 => array(
'id' => '166',
'student_inactive' => true,
'student_enq' => false,
'student_unallocated' => false,
'first_name' => 'Kala',
'last_name' => 'narayanan',
The fields option is not nested correctly, which you maybe would have noticed if you'd format your code properly, something along the lines of this:
$this->Guardian->Behaviors->load('Containable');
$guardians = $this->Guardian->find('all', array(
'contain' => array(
'Student',
array(
'fields'=> array(
'Student.guardian_id,Student.id,Student.first_name'
)
)
),
'order' => array(
'guardian_first_name ASC'
),
'fields' => array(
'Guardian.guardian_first_name,Guardian.guardian_last_name,Guardian.id'
),
'recursive'=> -1
));
The array holding the fields option must be passed as the value for Student key.
// ...
'contain' => array(
'Student' => array(
'fields'=> /* ...*/
)
),
// ...
On a side note, when passing the fields as a comma separated string (which might not be the best idea), it's not necessary to pass them in an array.
array(
'fields'=> array(
guardian_id, id, first_name'
)
)
use fields without model name.
I have a model Ranking which holds a contact_id and belongsTo Model Contact.
Model Contact has a costumer_id and belongsTo Model Costumer.
And hasMany Rankings.
There is also a Model Product which hasMany Ranking.
On a statistics page I select
$this->Product->recursive = 1;
$this->set('products', $this->Paginator->paginate())
;
and I get the array of
array(
'Product' => array(
'id' => '69',
),
'Ranking' => array(
(int) 0 => array(
'id' => '29',
'contact_id' => '9',
'product_id' => '69',
'ranking' => '9',
),
I would like to bind now the Contact and Costumer to the ranking based on the contact_id.
Is this manually possible via bindModel?
If yes, how can I do that?
I tried to set $this->Product->recursive = 1; to 2 and 3, but that select so many other things which I would need to clear with unbindModel... So I hope there is a smarter way of bind those model to get to the data...?
What you basically want to use is containable behavior. With this behavior you are able to filter and limit model find operations. You have the possibility to add this behavior on model level or at the controller to avoid side effects if the application has already grown to a complicated level.
Example from Cake-Book:
// Activate Containable Behavior on the fly in a controller
$this->User->Behaviors->load('Containable');
$this->User->contain();
$this->User->find('all', array(
'contain' => array(
'Profile',
'Account' => array(
'AccountSummary'
),
'Post' => array(
'PostAttachment' => array(
'fields' => array('id', 'name'),
'PostAttachmentHistory' => array(
'HistoryNotes' => array(
'fields' => array('id', 'note')
)
)
),
'Tag' => array(
'conditions' => array('Tag.name LIKE' => '%happy%')
)
)
)
));
Hope this gives you a push into the right direction.
using find it will get me the right data with this:
$this->set('products', $this->Product->find('all', array(
'contain' => array(
'Ranking' => array(
'Contact' => array(
'foreignKey' => 'contact_id',
'Customer' => array(
'foreignKey' => 'customer_id',
)
)
)
)
)));
When using the Paginator it looks like
$this->Paginator->settings['contain'] = array(
'Ranking' => array(
'Contact' => array(
'foreignKey' => 'contact_id',
'Customer' => array(
'foreignKey' => 'customer_id',
)
)
)
);
$this->Product->Behaviors->load('Containable');
$this->set('products', $this->Paginator->paginate());
Thanks so much!!
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
));
I have 3 tables: users, specialities, specialities_users.
User HABTM Speciality
Speciality HABTM User
SpecialitiesUser belongsTo Speciality,User
I have also model SpecialitiesUser.
When I want to get specialities for given user I can do it through
SpecialitiesUser->find('all' array('conditions' => array('user_id' => $given_user_id));
Now I want to get all specialities that user NOT belongs to. How I can do this in Cake?
The $other_specialities variable in the following code should be what you want:
$the_users_specialities = $this->SpecialitiesUser->find('list', array(
'conditions' => array(
'SpecialitiesUser.user_id' => $given_user_id
),
'fields' => 'SpecialitiesUser.speciality_id'
));
$other_specialities = $this->Speciality->find('all', array(
'conditions' => array(
'NOT' => array(
'Speciality.id' => $the_users_specialities
)
)
));
UPDATE: This is how to do it using a single query:
$db = $this->Speciality->getDataSource();
$sub_query = $db->buildStatement(
array(
'fields' => array('`SpecialitiesUser`.`speciality_id`'),
'table' => $db->fullTableName($this->SpecialitiesUser),
'alias' => 'SpecialitiesUser',
'conditions' => array('`SpecialitiesUser`.`user_id`' => $given_user_id),
),
$this->Speciality
);
$other_specialities = $this->Speciality->find('all', array(
'conditions' => array(
$db->expression('`Speciality`.`id` NOT IN (' . $sub_query . ')')
)
));
I have this NPO table which has one template. A template in turn has a theme. I want to retrieve which theme was selected by Npo.
I have following relation setup:
NPO - template on npo.id = template.npoid Template - theme on theme.id
= template.template_theme_id
And I am using:
$this->Npo->bindmodel(array(
'hasOne' => array(
'NpoTemplate' => array(
'className' => 'NpoTemplate'
)
)
), false);
$this->NpoTemplate->bindmodel(array(
'hasOne' => array(
'TemplateTheme' => array(
'className' => 'TemplateTheme',
'fields' => 'TemplateTheme.html'
)
)
), false);
$arrUserSite = $this->Npo->find('first', array(
'conditions'=> array(
'Npo.address' => $user
)
));
But it does not fetch anything from TemplateTheme. Instead it writes a seperate query for this table and does not consider it in the join.
I have set recursive level to 3
Please help. I don't really understand how the cake association works.
Regards
Himanshu Sharma
Try setting up your relationships in a single bindModel() call.
$this->Npo->bindmodel(array(
'hasOne' => array(
'NpoTemplate' => array(
'foreignKey' => false,
'conditions' => array(
'Npo.id = NpoTemplate.npoid'
)
),
'TemplateTheme' => array(
'foreignKey' => false,
'conditions' => array(
'NpoTemplate.template_theme_id = TemplateTheme.id'
)
)
)
));
$arrUserSite = $this->Npo->find('first', array(
'conditions' => array(
'Npo.address' => $user
)
));
You'll probably need to tweak the bindModel setup because I'm not 100% sure of your database structure. Good luck.