I have several doctrine models that have relationships to other models. Let's call those ItemsOne, ItemsTwo, ItemsThree. Each has a relationship defined to the Products doctrine class (products table) and the relationship is defined as:
$this->hasMany(
'Models_Products as Products',
array(
'local' => 'id',
'foreign' => 'product_id',
)
);
Nothing out of the ordinary there.
I also have another table (prices) which stores namespaced data. By that I mean that the particular table stores data based on a predefined key (i.e. Vinyl, CD, DVD) so the rows would show something like this:
media_namespace entity_id quantity unit_price
CD 4 1000 0.99
DVD 4 2500 1.25
CD 7 3750 0.25
Vinyl 15 75 4.25
The entity_id is the id of each of the models accessing this table. So for instance CD/entity_id = 4 refers to ItemsOne model, DVD/entity_id = 4 refers to the ItemsTwo model, etc.
I want to create a relationship - and I don't know if this can be done - whereas it will be namespaced in the model. So in the above snipped for the products relationship I need something like (for the ItemsOne model):
$this->hasMany(
'Models_Prices as Prices',
array(
'local' => 'id',
'foreign' => 'entity_id',
)
);
However the above will return all the prices with the entity_id matching the ItemsOne table which is partly correct. In the above example if entity_id = 4, it will return the CD record but also the DVD one which is not what I need. I want it to be filtering the data based on the media_namespace table. So in short what I need is:
prices.media_namespace = 'CD' AND prices.entity_id = itemsone.id
Can the above be reflected in a hasMany relationship in Doctrine? I have searched and cannot something that would help.
Any pointers are more than appreciated!
You need to use Doctrine subclasses. Create a subclass for each namespace. This will let you reference a specific set of data by class name.
class Models_Price
subclass Models_Price_CD
subclass Models_Price_DVD
subclass Models_Price_Vinyl
In the Price class, you'll need to specify the following:
public function setUp()
{
parent::setUp();
$this->setSubclasses(
array(
'Price_CD' => array(
'media_namespace' => 'CD',
),
'Price_DVD' => array(
'media_namespace' => 'DVD',
),
'Price_Vinyl' => array(
'media_namespace' => 'Vinyl',
),
)
);
}
Let's make the assumption that ItemsOne uses namespace CD, ItemsTwo uses namespace DVD, and ItemsThree uses namespace Vinyl.
For this situation, you would add the following relationship to each class:
ItemsOne:
$this->hasMany(
'Models_Price_CD as Prices',
array(
'local' => 'id',
'foreign' => 'entity_id',
)
);
ItemsTwo:
$this->hasMany(
'Models_Price_DVD as Prices',
array(
'local' => 'id',
'foreign' => 'entity_id',
)
);
ItemsThree:
$this->hasMany(
'Models_Price_Vinyl as Prices',
array(
'local' => 'id',
'foreign' => 'entity_id',
)
);
Now, each of the Prices relations will return only what you expect. Doctrine, seeing the subclass, will automatically namespace all the queries you perform on that table.
Related
I am using cakephp 2.5, and need to join Vehicle table with Address table where vei_id is the foreign key
I have one find operation that is generating a wrong condition for the two models: Vehicle and Address.
Address has the vei_id column wich is the foreign key to join the vehicle table.
The query is generating vehicle_id as the column to join the two tables, the probem is that this column does not even exists.
I have mapped the two models using the vei_id column.
How can i avoid this situation ? seems cakephp try to guess the join column even if i have already write the condition using the column i want.
//Vehicle Model
public $hasOne = array(
'Address' => array(
'className' => 'Address',
'conditions' => array('Vehicle.vei_id = Address.vei_id'),
'foreignkey' => false
)
//Address Model
public $belongsTo = array(
'Vehicle' => array(
'className' => 'Vehicle',
'conditions'=> array('Vehicle.vei_id=Address.vei_id'),
'foreignKey' => 'vei_id'
),
);
//At vehiclecontroller
$data = $this->Vehicle->find('first', array(
'conditions' => array('Vehicle.vei_id' => $vehicleId),
'contain' => array(
'Address' => array('conditions'=> array('Address.vei_id'=>'Vehicle.vei_id',
'Vehicle.vei_id' => $vehicleId
),
)),
));
it generates this line :
LEFT JOIN Address ON (
Address.vehicle_id = Vehicle.vei_id
AND Address.vei_id = 'Vehicle.vei_id'
AND Vehicle.vei_id = 123
)
Where this column does not exists :
Address.vehicle_id = Vehicle.vei_id
Your query looks little bit confusing:
Just look at following conditions within contain:
'contain' => array(
'Address' => array('conditions'=>
array(
'Address.vei_id'=>'Vehicle.vei_id', // why is this ?
'Vehicle.vei_id' => $vehicleId
),
));
Why are you using following conditions within contain ?
Address.vei_id'=>'Vehicle.vei_id
Did you do that to join two tables ?
When you use contain these things are done by cakephp's convention.
Address table is already joined with vehicle table.
See here:Cakephp contain.
Also why not to follow cakephp convention?
If you have vehicles table,
the foreign key would be vehicle_id according to cakephp convention.
And if you have users table foreign key would be user_id.
These things also reduces your work and make things easier.
See Here: (cakephp model and database conventions).
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.
This is with CakePHP 2.4. I have a table (table1) which is connected to three others (tables 2 to 4) through a hasMany through connection. All four tables are in a non-default database. Although I'm using hasMany from tables 2-4 to table 1 and in the class for table1
public $belongsTo = array('table2', 'table3', 'table4');
When I try and display the view for table1 I get the error
Table table1 for model model1 was not found in datasource default
So it's looking in datasource default even though all four tables have are in the non-default database (specified using $useDbConfig).
Although the documentation doesn't say why you would want to specify the classname, even if it follows the naming convention, this appears to be the solution:
public $belongsTo = array('table2', => array(
'className' => 'table2'),
'table3'=> array(
'className' => 'table3'),
'table4'=> array(
'className' => 'table4'));
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));
}
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.