CakePHP Complex Relationship - cakephp

I have the following tables:
teams(id, name)
team_users(id, team_id, user_id)
users (id, username)
projects (id, project_name, team_id)
A team hasMany users, Users hasMany teams, a project belongsTo a team.
If I call $this->User->find(); It returns the information of the user and the team's he belongs to.
What I want to do is, I would like to get a count of the projects he is associated with. Meaning:
John Doe is a member of Team X and Y. X has 2 projects and Y has 3 projects. I would like to return number of projects as 5, some sort of virtual field. Is it possible?

If you had properly set up you model relationship this query is all you need:
$this->User->virtualFields = array('total_projects' => 'COUNT(*)');
$user_projects = $this->User->find('all',array('fields' => array('total_projects', '*')));
//$user_projects["User"]["total_projects"] -> this will result to 5 base on your question above or you can debug this by: debug($user_projects) so you can see the content of the array

Use the "counterCache" option in the relation.
http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#belongsto
class Project extends AppModel {
public $belongsTo = array(
'Team' => array(
'className' => 'Team',
'foreignKey' => 'team_id',
'counterCache' => true
)
);
}
You'll need to add a new field project_count to your teams table, CakePHP will do the rest.

Related

How to write associated linking for a model?

Let's say I have a "users" table with a field called "user_type", for simple explanation if user_type is "C" this user is a clinic's owner, if user_type is "D" this user is a dentist.
I would like dentists in this system belong to clinics which he/she associates to.
My questions are...
Do I need clinics and dentists database tables???
If I didn't need those 2 tables then how many models do I need, just a User model or Clinic and Dentist models as well???
If all I need is just a User model, please hint me how do I write the User model and associated linking ($belongsTo, $hasMany)???
P.S. Clinic's owners and dentists are users of the system.
Please advise me.
Thanks.
More about the system
This system is intended for clinic's owners as the paid customers of the system. The system will allow them to advertise the job offers for dentists and assistants.
The dentists and assistants can register themselves to the system after they get hired by the clinic's owner, the clinic's owner will become their employer and keep records of how they work in the system. If they quit the clinic also update his/her employed status.
The records that each clinic keep can be seen by other clinic.
It's actually like employer and employee model, but because of online system so I thought of User model.
Should I decide the system like, any user just register first, so he/she become a user of the system, after that they need to fill in more information whether they are employer (clinic/s owner) or employee (dentists or assistants), these information are kept in seperate tables such as clinics, dentists, assistants, and there are finally the models in CakePHP???
Thanks
I'll try to answer this question.
Separate Model for 'Clinic'
If you want to store information about a clinic (e.g. address, name etc) in your system, then you should have a database table to store that information and a Model to use that information.
Separate Model for 'Dentists' and 'Clinic owners'
Regarding separate database-tables for 'Dentists' and 'Clinic owners'
You mention that both 'Dentists' and 'Clinic owners' are users of the system (and should probably both be able to log in). Also, it is likely that a 'Clinic owner' is also a 'Dentist'.
Because of this, I think you are safe to use a 'user type' to indicate if a person is (also) a Clinic Owner.
Naming of database tables
Regarding the naming of the database-tables.
Although both Dentists and Clinic owners are both 'Users' of the system, you may ask yourself if 'users' is the best name to describe both.
If Clinic owners are also Dentists, I would suggest to name your table 'dentists' as it better describes what is 'in it'. It is not a problem to use a different name for your 'users' table. It's still possible to use the table to log in as long as you specify the right model in the Auth component via the userModel option. For example:
public $components = array(
'Auth' => array(
'authenticate' => array(
'Form' => array(
'userModel' => 'Dentist',
// other settings for 'Form' authentication
)
)
)
);
See this part of the documentation: Configuring Authentication handlers
Example for database-tables and Models
To summarise your situation, your database should look something like this;
CREATE TABLE dentists
(
id int(4) NOT NULL AUTO_INCREMENT,
username varchar(50) NOT NULL,
password char(128) NOT NULL, -- SHA512 hash (128 characters)
user_type char(1) NOT NULL DEFAULT 'D',
name varchar(75),
address varchar(75),
email varchar(75) NOT NULL,
-- etc..
-- primary key for CakePHP
PRIMARY KEY (id),
-- make sure usernames are unique
UNIQUE KEY unique_username (username)
);
CREATE TABLE clinics
(
id int(4) NOT NULL AUTO_INCREMENT,
-- Note: NOT named according to 'CakePHP' conventions
-- as 'owner_id' is probably clearer than 'dentist_id'
owner_id int(4) NOT NULL,
name varchar(75),
address varchar(75),
-- etc..
PRIMARY KEY (id)
);
The models;
class Dentist extends AppModel {
public $hasOne = array(
'Clinic' => array(
'className' => 'Clinic',
/**
* because we're not following CakePHP
* conventions here.
*
* This should be 'dentist_id' according to CakePHP conventions
* but 'owner_id' is probably clearer in this case
*/
'foreignKey' => 'owner_id',
'dependent' => true
),
);
}
class Clinic extends AppModel {
public $belongsTo = array(
'Dentist' => array(
'className' => 'Dentist',
'foreignKey' => 'owner_id',
/**
* Optionally
*/
'conditions' => array('Dentist.user_type' => 'C'),
),
);
}
note
In my design, a clinic can only have a single owner, but a Dentist can own multiple clinics. If a clinic
can have multiple owners, a HasAndBelongsToMany relation should be used
More separation of 'Dentists' and 'Clinic Owners'
If desired, you can always set up another Model for 'Clinic Owners' (ClinicOwners), connected to
the same database table as 'Dentists' and with a default condition of (user_type = 'C'). If you need more information on this, I'll add an example
update
Some additional hints, based on additional information provided.
Deciding if Dentists, Assistants and ClinicOwners should have their own database-table should be based on their usage. If all are 'basically' equal, apart from their permissions, then it's possible to store them in the same database-table.
Even if they are in the same database-table, it is possible (for convenience) to create different models that use the same database-table, for example:
class Assistants extends AppModel {
public $useTable = 'dentists'; // or 'users' if you decide on that
/**
* This model automatically filters results
* to show only records with user_type 'A' (assistant)
*
* {#inheritdoc}
*
* #param array $queryData
*
* #return mixed
*/
public function beforeFind(array $queryData)
{
if (!empty($queryData['conditions'])) {
$queryData['conditions'] = array(
'AND' => array(
$this->alias . '.user_type' => 'A',
$queryData['conditions'],
)
);
} else {
$queryData['conditions'] = array(
$this->alias . '.user_type' => 'A'
);
}
return $queryData;
}
}
Permissions
Because permissions will be based on the user_type, it's worth considering to rename 'user_type' to 'role' (or similar). You'll probably be needing ACL (Access Control Lists) to
define exact permissions per 'type of user', i.e. the users 'role'. You might want to create a 'roles' database-table for that as well (and a Role model)
Fully explaining ACL is quite difficult in a few lines, so I suggest to read the documentation on this subject;
Access Control Lists
And the examples:
Simple Acl controlled Application
Simple Acl controlled Application - part 2

Friendships in CakePHP 2.x

I'm having trouble setting up friendships with CakePHP 2.
I have two database tables: users and friends. My users table has the following columns:
id
email
password
And my friends table has the following columns:
id
user_id
friend_id
approved
I have friends set up as a hasAndBelongsToMany relationship in my Users model:
<?php
class User extends AppModel {
public $hasAndBelongsToMany = array(
'Friends' => array(
'className' => 'User',
'joinTable' => 'friends',
'foreignKey' => 'user_id',
'associationForeignKey' => 'friend_id',
'unique' => true
)
);
}
Now, when I try and retrieve friends for a user, it only lists friendships that the specified user initiated, i.e. where user_id is equal to the user ID; it doesn't show me friends where the other person may have initiated the request (i.e. where the current user's ID is in the friend_id column).
How can I fetch friends, so records where either the user_id or friend_id column is equal to a particular ID?
I don't think you understand how HABTM works. Read this part of the book. You will need a friends_users table in addition to the tables you have for the relationship to work. I think if you were going to set it up this way, you'd need to define a Friendship as having and belonging to many Users.
However, I question whether with your current setup you want a HABTM relationship. It seems like a user hasMany friends, and that's it. Look into using that relationship, and it'll give you the relevant ID as you expect it to. Don't forget to define Friend belongsTo User.
Here beings my canonical Cake 2.0 Friendship tutorial. I downloaded cakePHP 2.1 so I had a fresh start. I first changed my security salt and cipher, then added my database connection. Then I structured my database as follows:
Database:
users table:
id | int(11)
created | datetime
username | varchar(255)
friendships table:
id | int(11)
user_from | varchar(255)
user_to | varchar(255)
created | datetime
status | varchar(50)
Obviously, your users table can/will have more stuff, but this is the minimum I needed.
Models:
Okay this is the tricky part. Here are the relationship I defined in my User model.
class User extends AppModel {
/* Other code if you have it */
var $hasMany = array(
'FriendFrom'=>array(
'className'=>'Friendship',
'foreignKey'=>'user_from'
),
'FriendTo'=>array(
'className'=>'Friendship',
'foreignKey'=>'user_to'
)
);
var $hasAndBelongsToMany = array(
'UserFriendship' => array(
'className' => 'User',
'joinTable' => 'friendships',
'foreignKey' => 'user_from',
'associationForeignKey' => 'user_to'
)
);
/* Again, other code */
}
Here is my Friendship model:
class Friendship extends AppModel {
/* Other code if you have it */
var $belongsTo = array(
'UserFrom'=>array(
'className'=>'User',
'foreignKey'=>'user_from'
),
'UserTo'=>array(
'className'=>'User',
'foreignKey'=>'user_to'
)
);
/* Again, other code */
}
Note on models: The friendship model belongs to 2 users. The user model has 3 associations. The two hasMany relationships in the User Model are both aliases for the accessing the Friendship model's data, so we can use $this->User->FriendTo or $this->User->FriendFrom from controllers to get to the Friendship model. I at first called these UserFrom and UserTo, mirroring the setup of the Friendship model, but Cake threw a hissyfit about similarities, so I had to make them more distinct.
Controllers and Views:
I baked controllers and views using the bake utility. I then created two users (Daniel and Martin) and created a new friendship from Daniel to Martin with a status of requested. I then updated the friendship status to confirmed.
I created the following viewless custom user action to demonstrate data retrieval about a friendship from the UsersController:
public function test() {
$data = $this->User->FriendFrom->find('all',
array(
'conditions'=>array('user_from'=>1),
'contain'=>array('UserTo')
)
);
die(debug($data));
}
This find uses the hasMany relationship of the UserModel to access the Friendship model and get the related user_from and user_to data for the relationships where the user with the id of 1 initiated the relationships.
Your specific find:
Martin, the find you're looking for is super simple under this system, and while you could do it differently, you'd always be dealing with a similar method, simply as long as there are always two sides to a relationship. All you have to do is get a list of relationships where your user ID is either user1 or user2 (in my case, just so I know who initiated the relationship, I have them stored as user_to and user_from- I think this is what intimidated you). Then I iterate through the whole array, selecting the relevant friend data based on whether I am user1 or 2 in that given array. It's a really simple method, and I just put it in my user Model. Change the die(debug()); to return $friendslist to be able to call it from your controller and get an array back.
public function getFriends($idToFind) {
$data = $this->FriendFrom->find('all',
array(
'conditions'=>array(
'OR'=> array(
array('user_to'=> $idToFind),
array('user_from'=> $idToFind)
)
)
)
);
$friendslist = array();
foreach ($data as $i) {
if ($i['FriendFrom']['user_from'] == $idToFind){
$friendslist[] = $i['UserTo'];
}
elseif ($i['FriendFrom']['user_to'] == $idToFind){
$friendslist[] = $i['UserFrom'];
}
}
die(debug($friendslist));
}

Retrieving values from multiple tables using CakePHP model associations

I have a ProductsController in which I am retrieving Products data and need to also retrieve the Category Name. (Note: My Products table has only Category_ID in it), how can I do that using CakePHP model associations?
I have seen examples in which the ID of the main data table (in my case, Products table) is a Foreign Key in the Associated Table. However, my case slightly different in that the Category_ID (from the secondary table) is part of the Main table (Products table).
I am not able to retrieve the Category Name using CakePHP model config. Can you help?
My ProductsController is on Products table which has
ID
Prod_Name
Category_ID
....
My Categories table is like
ID
Cat_Name
In my ProductsController I want to retrieve Cat_Name for Products being retrieved.
In your Product Model, use the association:
var $belongsTo = array(
'Category' => array(
'className' => 'Category',
'foreignKey' => 'category_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
When retrieving your Products data use find method:
$this->set('variable', $this->Product->find('all'));
Once its in your View, it is an array containing all the products and its category.
Like this:
<?php
foreach($variable as $itemInTable):
echo 'Product:' . $itemInTable['Product']['Prod_Name'];
echo 'Its Category:' . $itemInTable['Category']['Cat_Name'];
endforeach;
?>
fzmaster's answer is correct. When you have a foreign key in Table A that corresponds to an id in Table B, it is said that the Model A "belongs to" Model B. At the same time, there could be an inverse relationship where Model B "has many" Model As.
The associations are fairly straightforward within that context and if you use the Cake naming conventions, you can associate the models with minimal additional code:
class Product extends AppModel{
var $belongsTo = array( 'Category' );
}
class Category extends AppModel{
var $hasMany = array( 'Product' );
}
At that point, CakePHP's Model::Find() method will automatically retrieve associated models unless you limit it with $recursive or by using the Containable behavior.

CakePHP update extra field on HABTM join table

I have problem with updating (better updating not recreating) extra field in HABTM join table. I searched google and other sources, but struggled for 4 days now.
I have models:
class Tutorial extends AppModel {
var $hasAndBelongsToMany = array(
'TutorialCategory' => array(
'with' => 'TutorialCategoriesTutorial',
'order' => 'TutorialCategoriesTutorial.order_by ASC',
'unique' => true,
);
}
class TutorialCategory extends AppModel {
var $hasAndBelongsToMany = array(
'Tutorial' => array(
'with' => 'TutorialCategoriesTutorial',
'unique' => true,
);
}
join table tutorial_categories_tutorial have id, tutorial_id, tutorial_category_id, order_by fields.
I am trying to update order_by field like:
$order = 1;
foreach($tutorials as $i => $tutorial) {
$this->data[$i]['Tutorial']['id'] = $tutorial['Tutorial']['id];
$this->data[$i]['TutorialCategory']['id'] = $tutorial['TutorialCategory']['id];
$this->data[$i]['TutorialCategoriesTutorial']['order_by'] = $order;
++$order;
}
$this->Tutorial->bindModel(array('hasMany' => array('TutorialCategoriesTutorial')));
$saved = $this->Tutorial->saveAll($this->data);
This is deleting and crating new records in join table, but not setting order_by at all. I want to update record and set now order_by value. I tried hasMany through but no luck.
Please help and/or give advice and explanation.
Thank you!
As you have added extra data (order field) to the HABTM join model, you have actually exceeded the capabilities of a simple HABTM relationship with CakePHP. What you actually need to setup is a hasMany Through relationship.
In your case you'll basically make a "membership" model with Tutorial ID, catergory id and as much data as you want to assign to it. You will then define the relatioships as Membership belongsTo Tutorial & Category. The book probably has a better example than what I've just explained!
The main reason for this is that each "membership" record is treated as a normal record with no HABTM behaviour attached to it, so you can edit, delete and add records individually and easily.

Cakephp HABTM relation

i have theses tables
article , article_currency, currency in database
and i made HABTM between article and currency like this
var $hasAndBelongsToMany = array('currency'=>array('className' => 'currency','joinTable'=>'article_currency','foreignKey' => 'articleid','associationForeignKey' => 'CurrencyID'));
var $hasAndBelongsToMany = array('articlemodel'=>array('className' => 'articlemodel','joinTable'=>'article_currency','foreignKey' => 'CurrencyID','associationForeignKey' => 'ArticleID'));
and here cake genrate the model ArticleCurrency for me
when i try to change its name using with to 'article_currency'
is give me this error
Database table article_currencies for model article_currency was not found.
how i can solve this
Table names are plural by convention: articles, currencies, articles_currencies
http://book.cakephp.org/view/24/Model-and-Database-Conventions

Resources