cakephp find query conditions on associated model - php

I have a model User and a model Role in a CakePHP application. The association between the two models is the following:
User $belongsTo Role
Role $hasMany User
I want to make a query on the User model to find all users with a specific role (let's say the role Supervisor). I did my query like this:
$supervisors = $this->User->find('all', array(
'contain' => array(
'Role' => array(
'conditions' => array(
'Role.name' => 'Supervisor'
)
)
)
));
But the above query returns me all the users in my users table. It does not return only the users with role Supervisor. I know that if I do two queries, one on the Role model to find the id of the role type 'Supervisor' and then do another query on the User model and pass the id of the supervisor role record in the conditions on my User model like this:
$supervisor_role_id = $this->Role->field('id', array('Role.name' => 'Supervisor'));
$supervisors = $this->User->find('all', array(
'conditions' => array(
'User.role_id' => $supervisor_role_id
)
));
The above queries will give me the desired result. But I don't wanna do 2 queries to do this. Why doesn't the first approach work. Any idea please?
Thank you

The reason your attempt didn't work
CakePHP's Containble Behavior creates separate queries for each model. So - what you did was basically described like this: "Find all Users. Also find any Roles with the name of 'Supervisor". As you can see, there is no condition that crosses between the two.
So, you can do one of the following:
1) [easy way] Query the other way around
Query from the Role model and contain it's user(s). This pulls the role you want (based on your provided conditions) then contains any/all of it's users.
Note - if you've already loaded the 'User' model (or it's been loaded by default because you're in the UsersController), you can run your find like this: $this->User->Role->find(..... - so you don't have to load the Role model separately.
2) Use JOINs (see CakePHP Book on Joining Tables)
This allows you to limit the result of a parent model based on it's associated data.

Related

Optimize query in Laravel Backpack n-n relationships

I am building a backend panel for a website with Laravel Backpack. It is really nice, but I have noticed that relationship queries are very expensive.
I have two models: Product and Center with a many to many relationship between them. In my CenterCrudController I have defined a field this way:
$this->crud->addColumns([
// More fields...
[
'label' => 'Products',
'type' => 'select2_multiple',
'name' => 'products', // the method that defines the relationship in your Model
'entity' => 'products', // the method that defines the relationship in your Model
'attribute' => 'name', // foreign key attribute that is shown to user
'model' => 'App\Models\Product', // foreign key model
'pivot' => true, // on create&update, do you need to add/delete pivot table entries?
],
// More fields...
]);
It works fine, showing a select multiple field with related models. But the query used is SELECT * FROM products, which is highly expensive (table products have thousands of records with about 25 columns).
In this example I only need id and name fields. I am looking for something like Query Builder select() method.
Is there a way for optimizing this type of query?
Thanks in advance!
Not sure if this is actually an answer, but I'll post it anyway.
The best solution (as pointed by #tabacitu) was using select2_from_ajax field. It doesn't slow page load and make an ajax request for retrieving data only when user clicks on the select field.

cakephp unable to detect model association after manually adding foreign key column to database table

I have two models in a CakePHP application: Users and Groups. Initially I had not relationships between the two. Now in the database table groups, I manually add the column user_id so that now I have the following relationships between the two:
Groups belongsTo Users
Users hasMany Groups
Now after doing this for some reason I keep getting the following error:
1054: Unknown column 'Group.user_id' in 'field list'
I tried to run CakePHP's bake command to see if Cake will automatically detect the relationship between my two models. When I try to bake the User model, CakePHP does not even detect that there is a hasMany relationship between User and Group. Any idea why is this happenning and how I can fix this please?
Thank you
Perhaps I am misunderstanding how you want your associations, but I would imagine that:
A User belongsTo a Group
A Group hasMany Users
Example User model:
class User extends AppModel {
public $belongsTo = array(
'Group' => array(
'className' => 'Group',
'foreignKey' => 'group_id'
)
);
}
Example Group model:
class Group extends AppModel {
public $hasMany = 'User';
}
Your Users table will need a group_id field.
All of this is assuming that you want one group per user. If you want Users to have multiple groups then you may wish to define a HABTM relationship between Users and Groups, or perhaps researching Access Control Lists may point you in a better direction (I have yet to utilize Cake's ACL component yet).
Either way, I highly suggest reviewing the Cakebook page on Associations: Linking Models Together.

Save a HABTM record to database CakePHP

I am working on a followers / following system with CakePHP 2.
I have setup my database with a users table, and a user_users table. The users table is the main table containing every user on the system, whilst the user_users table contains the records of followers.
I then have a UsersController, User model and Follower model.
I can successful output a button to either say Follow or Following dependent on whether the currently logged in user is following the user of which the profile they are viewing belongs to, however what I am unable to understand how to do, is create new following relationships in the table. In other words, I do not know how to create records in the user_users table.
I am not sure where the logic for this should go, and thus what my "Follow" button should point to.
This is probably a very simple question, but I am totally stumped. I have tried adding a "follow" action to the UsersController but I cannot get that to work.
Any help much appreciated,
Duncan
HATBM isn't a good fit in this situation. From the cookbook:
HABTM data is treated like a complete set, each time a new data
association is added the complete set of associated rows in database
is dropped and created again so you will always need to pass the whole
data set for saving. For an alternative to using HABTM see hasMany
through (The Join Model)
For this reason, HABTM is mainly good for pretty 'dumb' relationships. I've used it in cases such as where a User has to select many Interests - and they just get a list of checkboxes, where they can click multiple Interests, and save them all in one hit.
In your case, it'll be easier to have a separate table with it's own model. I'd call it Relationships or something similar. It would have an id, followed_by_id, following_id, and any other fields you may need.
I've dug up some code from an old cake 1.3 app, but it should help you out. Your Relationships model would look something like this:
<?php
class Relationship extends AppModel {
var $name = 'Relationship';
var $belongsTo = array(
'FollowedBy' => array(
'className' => 'User',
'foreignKey' => 'followed_by_id'
),
'Following' => array(
'className' => 'User',
'foreignKey' => 'following_id'
)
);
}
?>
Your User's model would have to have relationships like this:
var $hasMany = array(
'Followers' => array(
'className' => 'Relationship',
'foreignKey' => 'following_id',
'dependent'=> true
),
'FollowingUsers' => array(
'className' => 'Relationship',
'foreignKey' => 'followed_by_id',
'dependent'=> true
),
);
Then in your relationships controller, you'd have methods something like this:
function add($following_id = null) {
$this->Relationship->create();
$this->Relationship->set('followed_by_id',$this->Auth->User('id'));
$this->Relationship->set('following_id',$following_id);
$this->Relationship->save();
$this->redirect($this->referer());
}
function delete($id = null) {
$this->Relationship->delete($id);
$this->redirect($this->referer());
}
Note that in that code, I'm modifying the database with a GET request - which I really shouldn't be doing (it's old code, from years ago). You'll want to enforce a POST request for both the add and delete methods, since they're modifying the database.
But still, that code should set you on the right track.

Kohana 3.3 table structure

I have a simple crud application in Kohana 3.3, with a few different types of data or models. Let’s say those models are:
Users
Locations
Skills
I have a table for each of those models, but the tables aren’t related to one another in any way. I’ve been trying to define relationships with ORM, but I’m still confused. As an example:
I have a number of locations. Each location has many users.
I know I can define that with:
class Model_Location extends ORM {
/**
* A location has many users
*
* #var array Relationships
*/
protected $_has_many = array(
'users' => array('model' => 'user'),
);
}
As I understand it, I can connect the two by referencing the ID of the parent location from the row in the user table. However, what if each user can belong to many locations? Am I supposed to store serialised data in a foreign key? Should I create a “look-up table”? If so, how should it look?
How can I for example, query the database for a location and all users attached to it? Is ORM even the right technology to be using for this kind of thing?
You are looking for the through parameter, have a look at the docs.
You need an extra table that stores the ids of both models
table users_locations, fields: user_id and location_id (both indexed, of course)
And in your model:
protected $_has_many = array(
'users' => array(
'model' => 'user'
'through' => 'users_locations',
),
);
And vice versa in the user model:
protected $_has_many = array(
'locations' => array(
'model' => 'location'
'through' => 'users_locations',
),
);

Accessing 3rd association

I have following models:
- Agenda (id, user_id, event_id) -> belongsto Event, User
- Event (id, etc) -> hasmany Agenda
- User (id, city_id, etc) -> hasmany Agenda, belongsto City
- City (id, name) -> hasmany User
While in the Agendas controller, I want to display the City.name of an user that has a certain event_id in his Agendas. How can I add City data to the User array of the Agendas array?
N.B. I don't want to use recursive 2 because that loads way to much data into the array.
Your best option looks to be the CakePHP Containable Behaviour http://book.cakephp.org/view/474/Containable
It allows you to limit the associated model data that is returned - it basically has the power of recursive 2 in being able to find deep association data, but has much better performance and is cleaner because it gives you only the data you need.
Something like the following code should return the City data within the User data, including the condition of event_id.
$this->Agenda->find('all', array(
'contain' => array(
'User' => array(
'City'
),
),
'conditions' => array(
'Agenda.event_id' => $event_id
)
));
Just to complete that code snippet, ensure var $actsAs = array('Containable'); is at the top of the model (or in the app_model.php if you want the containable behaviour accessible to all models)

Categories