I have HABTM for Users and Groups. I want to show in a Group view - all the Groups that belong to a User. Or to put it differently - all the Groups that have the User.
I am getting tangled in the MVC and am not able to figure it out. Here are my two models:
class Course extends AppModel
public $name = 'Course';
public $hasAndBelongsToMany = array('User' =>
array(
'unique' => false
)
);
And...
public $name = 'User';
public $hasAndBelongsToMany = array('Course' =>
array(
'unique' => true
)
);
The table name in the database is courses_users - this table houses group ids and user ids.
Should be simple enough but I'm new to CakePHP so I'd love some help. Thank you!
CakePHP has recursive set to 1 by default, which means that assuming you have not changed the recursive setting, it will automatically fetch all associated courses when you call find on a user, assuming you set up the HABTM relationship when doing the find. Therefore, all you have to do is:
$this->User->find('first', array('conditions' => array('User.id' => $this->Auth->user('id'))));
In your User model, I don't think it's strictly necessary, but I like to specify the join table and such on HABTM relationships:
public $hasAndBelongsToMany = array('Course' =>
array(
'unique' => true,
'dependent' => false,
'joinTable' => 'courses_users',
'foreignKey' => 'user_id',
'associationForeignKey' => 'course_id',
)
);
Keep in mind that in HABTM relationships, you don't ever really touch the joinTable beyond specifying which table to use as the joinTable when setting up the relationship. CakePHP will automatically do the rest of the work.
Related
I have 3 tables in the following format.
users
id
FirstName
LastName
userjobs
id
jobinfo
starredjobs
id
user_id
userjob_id
comments
enddate
The 'starredjobs' table holds all the jobs which an user starred/added to favorites.
I have defined the following relationships in their respective model files.
user.php
protected $_has_many = array( 'starredjobs' => array('model' => 'starredjobs' , 'foreign_key'=>'user_id'),
starredjob.php
protected $_belongs_to = array('user' => array('model' => 'user','foreign_key' => 'user_id'));
protected $_has_many = array('jobs' => array('model'=> 'userjob', 'foreign_key'=> 'job_id'));
userjobs.php
none
The idea is to retrieve all the starred jobs and details regarding jobs from the user object. A user can 'n' number of jobs and A job can be starred by 'n' number of users.
Am i defining relationships correctly?
Short answer: No. What you have here is a typical n:m relationship which can easily be used in Kohana using has_many "through" (as is used in the default roles users-relationship). But this doesn't allow for extra attributes in the "middle" table, so you need to use 2 has_many with corresponding belongs_to.
This can be described in plain English like so:
One user has many starredjobs.
One job has many starredjobs.
One starredjob belongs to one user and one job
Also consider the difference between far_key and foreign_key (official doc sadly doesn't cover it), but one easy rule to remember: The key in the other table is far away -> it is the far_key.
This would give you the following
user.php
$_has_many = array(
'starredjobs' => array(
'model' => 'Starredjob',
'far_key' => 'user_id'
)
);
userjob.php
$_has_many = array(
'starredbyuser' => array(
'model' => 'Starredjob',
'far_key' => 'userjob_id'
)
);
starredjob.php
$_belongs_to = array(
'user' => array(
'model' => 'User',
'foreign_key' => 'user_id'
),
'job' => array(
'model' => 'Userjob',
'foreign_key' => 'userjob_id'
)
);
Now you can do various things such as:
//get all jobs starred by given $user
foreach ($user->starredjobs->find_all() as $starredjob) {
//info on userjob via $starredjob->job->jobinfo, etc.
//info from pivot table via $starredjob->comments, etc.
}
//get all users that starred a given $userjob
foreach ($userjob->starredbyuser->find_all() as $starredjob) {
//info on user via $starredjob->user->FirstName, etc.
//info from pivot table via $starredjob->comments, etc.
}
Why are my global belongsTo conditions defined within a model not being applied?
I'm using CakePHP v2.4.2.
In model Order.php:
public $belongsTo = array(
...,
'Agent' => array(
'className' => 'Party',
'foreignKey' => 'agent_id',
'conditions' => array(...)
),
...
In controller OrdersController.php:
$agents = $this->Order->Agent->find('list');
In rendered view the following SQL statement is being applied:
SELECT `Agent`.`id`, `Agent`.`name` FROM `zeevracht2`.`parties` AS `Agent` WHERE 1 = 1;
I tried different conditions, but even a simple string containing true isn't being applied (while adding this condition to PartiesController.php within a $this->Order->Agent->find(); works fine:
$agents = $this->Order->Agent->find('list', array(
'conditions' => array('true')
));
leads to:
SELECT `Agent`.`id`, `Agent`.`name` FROM `zeevracht2`.`parties` AS `Agent` WHERE true;
After collaborating on IRC I found the answer on my own question.
It seems that conditions in the belongsTo condition of a model is only applied to the JOIN when querying an Order.
I was trying to filter Party on specific roles, e.g. Agent, which is an alias of a Party with a role as agent. So the association should be conditioned with a role set as agent. Ideally, this would automatically condition any $this->Order->Agent->find() calls. But unfortunately, this is not possible due to a technical issue which is being addressed in the development of version 3 of CakePHP.
The solution is to have two types of conditions on the association: one for the JOIN and one for the association itself.
Why one for the JOIN? E.g. when a Post belongs to User, but only post.validated should be displayed.
If you’re trying to find the Agent that a particular Order belongs to, then you should get the record back when querying orders. So something like:
<?php
class OrdersController extends AppController {
public function view($id) {
$order = $this->Order->findById($id);
pr($order); exit;
}
}
Should yield something like:
Array
(
[Order] => Array
(
[id] => 83
…
)
[Agent] => Array
(
[id] => 1
…
)
)
In your question where you then make an additional model call like $this->Order->Agent->find('list');, that’s making a new query, which will fetch all agents with no conditions. The conditions key, where you pass true, will have no affect, because that’s not a condition. Conditions should be an array, like this:
$this->Order->Agent->find('all', array(
'conditions' => array(
'Agent.id' => 1
)
));
But as I say, if your Order model belongs to your Agent model, then you should get an Agent result set back when you get your Order result set. If not, try adding the Containable behavior to your Order model:
<?php
class Order extends AppModel {
public $actsAs = array(
'Containable'
);
}
Try this:
public $belongsTo = array(
...,
'Agent' => array(
'className' => 'Party',
'foreignKey' => false,
'conditions' => array('joinField'=>'joinedField', ...more conditions)
),
Okay, so I have a form in CakePHP that submits to a database. In this form it submits a field called client_id and it is stored in the database as such as well.
Then I have a view to allow me to view all of the invoices that have ever been created. To view the client responsible for the invoice, I can currently only see the id entered in the form by placing: <?php echo $invoice['Invoice']['client_id']; ?> in the view.
The invoices go to one database called: invoices
The clients name is not stored in the invoices table, just the id
The clients information is stored in one database called: clients
I want to be able to actually display out the clients real name in the invoices view rather than the client id.
I tried adding the following query to my index controller, But i'm not sure what to do after this or if this is even right.
$this->set('clients', $this->Invoice->query("SELECT * FROM clients WHERE id='89545'"));
In order to keep this question short in the first place, Please request specific code by commenting. i.e. Controller code, view code, etc... Thank you in advance.
Additional Thoughts
If I wasn't using CakePHP I could use something like the following, So I guess I just don't know how to put this into cakephp "language".
<?php
query($con, "SELECT name FROM clients WHERE id='$client_id'");
echo $row['name'];
?>
roughly!
Update
Here are my models
First one being the Client.php
<?php
class Client extends AppModel {
public $hasMany = array(
'Invoice' => array(
'className' => 'Invoice',
'foreignKey' => 'client_id'
)
);
}
?>
Second one being the Invoice.php
<?php
class Invoice extends AppModel {
public $belongsTo = array(
'Client' => array(
'className' => 'Client',
'foreignKey' => 'client_id'
)
);
}
?>
And finally, to get my invoices from the database, I am using the following inside: InvoicesController.php
public function index() {
$this->set('invoices', $this->Invoice->find('all'));
}
Have you properly set up your model associations? If so you should be able to do something like $invoice['Client']['client_name'] assuming you didn't use recursive = -1.
/edit: Ok I don't mind throwing some code out, but this is a fundamental concept you'll have to try to wrap your head around. Every model that is connected to another model has to have the association set up or things will be painful.
I am assuming a Client hasMany Invoice. So each invoice is specific to a client, and they can have multiple invoices (ex. October 2013 vs November 2013 invoice). From the CakePHP page we see:
class User extends AppModel {
public $hasMany = array(
'Comment' => array(
'className' => 'Comment',
'foreignKey' => 'user_id',
'conditions' => array('Comment.status' => '1'),
'order' => 'Comment.created DESC',
'limit' => '5',
'dependent' => true
)
);
}
So using that as our template, we end up with:
public $hasMany = array(
'Invoice' => array(
'className' => 'Invoice',
'foreignKey' => 'client_id'
)
);
And that goes in Client.php. As the inverse of hasMany is belongsTo, we have an Invoice belongsTo Client. Again, using the CakePHP page as the template, we end up with:
public $belongsTo = array(
'Client' => array(
'className' => 'Client',
'foreignKey' => 'client_id'
)
);
And that goes in Invoice.php. Once you set up those associations, whenever you do something like $this->Invoice->find('all'); or $this->paginate('Invoice');, with proper $recursive settings, Cake will grab the corresponding Client record. This allows you to do what I said before, something like $invoice['Client']['client_name'].
You can also use the $actsAs = array('Containable'); in the model in order to use the 'contain' directive in your controller's find method (instead of using recursive which most likely will get unwanted data on models with many relations)
Assume we have 3 models, User, House and Profile with the following associations between them:
User hasMany House (House belongsTo User)
User hasOne Profile (Profile belongsTo User)
I would like to query for houses (inside the HousesController class), whose associated Profiles satisfy the given Profile conditions. Consider the following example:
Assuming that gender is a property of Profile model, I would like to retrieve all the houses, whose owner is male.
I have found this question, which is close enough to what I seek for. In my case, the relationship between models is more complex (House belongsTo User hasOne Profile) and I can't get it to work. I have tried something like this, without any luck:
$this->House->find('all', array(
'contain' => array(
'User' => array(
'Profile' => array(
'conditions' => array(
'Profile.gender' => 'male'
))))));
The above call returns all the houses and in case the gender is male, it includes the respective User's Profile in the result. Otherwise, the User's Profile remains empty. What exactly I need is to return only the houses whose owner is male.
I have actually implemented it using the 'joins' option of Model::find() function, but I would like to know whether is this possible without using 'joins' and if yes, how?
I would recommend explicitly declaring the relationships using the bindModel method.
You could do this from your Houses controller like so:
/**
* Bind the models together using explicit conditions
*/
$this->House->bindModel(array(
'belongsTo' => array(
'User' => array(
'foreignKey' => false,
'conditions' => array('House.user_id = User.id')
),
'Profile' => array(
'foreignKey' => false,
'conditions' => array('Profile.user_id = User.id')
)
)
));
/**
* Standard find, specifying 'Profile.gender' => 'male'
*/
$houses = $this->House->find('all', array(
'conditions' => array(
'Profile.gender' => 'male'
)
));
When I was working on my current project, I ran into a rather complex issue. I'll point out my problem much more detailed right now:
There are three Models: User, UsersExtendedField and UsersExtended.
UsersExtendedField contains custom fields that can be managed manually. All custom fields can be shown in the Users view as well, and be filled out of course. The values are then stored in UsersExtended, which has got two foreignKeys: user_id and field_id.
The relations look like this: User hasMany UsersExtendedField, UsersExtendedField hasMany UsersExtended, UsersExtended belongsTo User, UsersExtendedField.
The problem: When accessing the Users view, a form with user information input is shown. Any UsersExtendedFields are available as well, and since these hasMany UsersExtended, they've got plenty of UsersExtended values. But I want to reduce those to only the value(s) that belong to the User, whose view is shown at the moment. Here are my (desired) relations:
Croogo::hookBehavior('User', 'Crooboard.ExtendedUser', array(
'relationship' => array(
'hasMany' => array(
'UsersExtendedField' => array(
'className' => 'Crooboard.UsersExtendedField',
'foreignKey' => '',
'conditions' => array('status' => 1)
),
),
),
));
class UsersExtendedField extends AppModel {
var $name = 'UsersExtendedField';
var $displayField = 'fieldname';
var $hasMany = array(
'UsersExtended' => array(
'className' => 'Crooboard.UsersExtended',
'foreignKey' => 'field_id',
'conditions' => array(
'UsersExtended.user_id = User.id'
)
),
);
}
This is not the full code, these are the important parts. The problem starts right where I wrote 'UsersExtended.user_id = User.id'. Obviously, this won't work. But I do not have any idea how to access the User.id here. I also could not imagine a HABTM structure to solve this task. Do you have any idea how to get the semantics of this 'UsersExtended.user_id = User.id' to work?
Thank your very much for taking the time to read through this and helping me!
It sounds like you need to set up your HABTM relationship properly.
You already have the join table, UsersExtended, which contains your foreign keys.
Remove all previous relationships and set up HABTM in each of your User and UserExtendedField models.
The relationship code in your User model would look like this:
var $hasAndBelongsToMany = array(
'UsersExtended' => array(
'className' => 'UsersExtended',
'joinTable' => 'UsersExtended', //assuming this is the
//name of that model's table
'foreignKey' => 'user_id',
'associationForeignKey' => 'field_id'
)
);
For more information check out the page in the cakephp book
In addition, this blog post helped me grasp the relationship concepts when I was learning cakephp.