I have multiple tables associated with each other model names are below:
Menu
SubMenu
Option
SubMenu has the menu_id,
Option has the submenu_id
I want to show Menu>SubMenu>Option.
How can I do that 3 level association?
Your best option is to use the Containable behavior.
I understand that your relationships are already defined in the Model classes.
In your MenuControllers action, add
$this->Menu->contain(array(
'SubMenu'=>array(
'Option'
)
));
just before
$menus=$this->Menu->find();
Don't forget to load the behavior in the Model:
class Menu extends AppModel {
public $actsAs = array('Containable');
}
Or by calling in your action:
$this->Menu->Behaviors->load('Containable');
First you have to set up correct associations in models. In Menu model (I assume you have correct foreign keys set up):
public $hasMany = array('SubMenu');
in SubMenu:
public $hasMany = array('Option');
public $belongsTo= array('Menu');
in Option model:
public $belongsTo= array('SubMenu');
And then you can do this(in model):
$this->find(...);
Use Containable and limit to fields you want to use.
More on model associations.
$this->Menu->bindModel(array('hasMany' => array('submenu' =>array( 'className'=> 'submenu', 'foreignKey' => 'menu_id'))));
$this->submenu->bindModel(array('hasMany' => array('option' =>array('className' => 'option','foreignKey' => 'submenu_id')))); '
Related
I have two tables in a database, one as event(eventID,name,location), and the other one as eventImages(id,eventID,path).
I need to get the images related to each event
I have tried the following statement in CakePHP:
<?php
App::uses('AppModel','Model');
class EventImages extends AppModel {
public $belongsTo = array('Event' => array('className' => 'Event','foreignKey' => 'eventID'));
}
but no data is retrieved in the controller any missing statement?
You have to define relation in Event model
public $hasMany = array('EventImages'=>array('className'=> 'EventImages', 'foreignKey'=>'eventID') //for multiple image
According to naming convention in cakephp Model name should be singular. Also you have to define public $primarykey = 'eventID' in Event model as cakephp by default use id field as primary key.
I just started cakephp following there tutorials
I'm able to grab the posts table in my post controller and spew it onto my index.ctp
In my view for the post controller i also want to list the User name that posted the article. My post table has a user_id, so i need to match it to my user table and pass it along
class PostsController extends AppController {
public function index() {
//passes values to the view
$this->set('posts', $this->Post->find('all'));
//is "Post" a post method? or is it the name of the table? i'm unsure of the syntax
$this->set('users', $this->Users->find('all')); //this does not work
}
}
thank you for your help with this basic question
You must use 'recursive'
$this->Post->find('all', array(
'recursive' => 2,
// ...
));
Of course, you first need to link models together
I assume that you have already set a belongsTo association (Post belongsTo User) and/or a hasMany association (User hasMany Post). If so, cake will automaticly brings the associated models (unless you put $recursive = -1 on your model).
Thus you'll have access to the users related to each post on the view: posts[i]['User']
You can also use this on your view to see the view variables:
debug($this->viewVars)
put this on your Post model if you don't:
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
)
);
Make sure that you load models corretly (in case you want to load the User model inside PostsController).
So simply add this attribute inside your class controller.
public $uses = array('Post','User');
to link models together . u need to add the association inside your Post model .
public $belongsTo = array(
'User'=>array(
'className'=> 'User',
'foreignKey'=>'user_id'
)
);
and i you want to retrieve data from database you have to set your recursivity and there is two ways
first one :
$posts = $this->Post->find('all',array('recursive'=>2));
// or
$this->Post->recursive = 2;
$posts = $this->Post->find('all');
second one : use the Containable behavior
set the recursivity to -1 in the AppModel and include the behavior
public $recursive = -1;
public $actsAs = array('Containable');
so simply u can retieve posts with any other linked models like that
$posts = $this->Post->find('all',array(
'contain'=>array('User'),
// ...
)));
I'm using Associations to Link Models together in CakePHP.
I have a Player model that is linked to a Statistic model using a $hasMany relationship. So basically one player can have many statistics.
After I set up these relationships, I use cake bake to generate the controllers and views for both the Player and Statistic models.
Everything works fine and works as expected, but when I go to add a statistic using the add template, the player_id is used as the association.
My question is how do I set up the model to use the player_id as the association but use something like the player's first and last name as the drop down menu in the add template.
Currently if I go to add a new statistic, I get a drop down box that called "Player" that lists all of the player id's but what I want is for the player's first_name and last_name to be in that drop down box instead of the id. I realize that I can modify the controller or template to accomplish this, but I Want to know if I can do this while setting up the model so that cake bake can take care of it.
Player Model:
<?php
class Player extends AppModel {
public $name = 'Player';
public $belongsTo = array(
'School' => array(
'className' => 'School',
'foreignKey' => 'school_id'
)
);
public $hasMany = 'Statistic';
}
Statistic Model:
<?php
class Statistic extends AppModel {
public $name = 'Statistic';
public $belongsTo = array(
'Player' => array(
'className' => 'Player',
'foreignKey' => 'player_id'
)
);
}
Players Table
`id|school_id|first_name|last_name|number|position
Cake uses the model's displayField attribute when choosing the default columns for a list. If none is defined for the model it will look for name or title. So in your model you can use:
public $displayField = 'first_name';
This will display the player's first name in the list.
If you want the display field to be the concatenation of two fields, you can use a virtual field in the model like so:
public $virtualFields = array(
'name' => "TRIM(CONCAT(Player.first_name, ' ', Player.last_name))"
);
Note the above works for Mysql. For another kind of database you will need to adjust the syntax. For example, for Sqlite it would be:
TRIM(Player.first_name || ' ' || Player.last_name)
Then add in your model:
public $displayField = 'name';
I have an application that stores Posts and Topics and joins them using a Topic_Posts table.
The associations for the application are as follows:
Post.php
class Post extends AppModel
{
public $name = 'Post';
public $belongsTo = 'User';
public $hasMany = array('Answer');
// Has many topics that belong to topic post join table... jazz
public $hasAndBelongsToMany = array(
'Topic' => array('with' => 'TopicPost')
);
}
Topic.php
class Topic extends AppModel
{
public $hasMany = array(
'TopicPost'
);
}
TopicPost.php
class TopicPost extends AppModel {
public $belongsTo = array(
'Topic', 'Post'
);
}
When a user views a topic e.g. /topics/view/topicname I want to show all the posts that contain that Topic.
So far I have the following method in my TopicsController for the view:
public function view ( $slug )
{
$topic = $this->Topic->find('first', array('conditions'=>array('Topic.slug'=>$slug)));
$this->set('topic', $topic);
$this->set('title_for_layout', $topic['Topic']['title'] . ' – Topics');
$this->paginate = array
(
'Post' => array
(
'limit'=>15,
'conditions'=>array
(
'Post.status'=>array(1,2),
'TopicPost.topic_id' => $topic['Topic']['id'],
),
'order'=>array('Post.datetime'=>'desc'),
'contain'=>array('User'=>'Profile', 'TopicPost')
)
);
$posts = $this->paginate('Post'); // this one
$this->set('posts', $posts);
}
And so that I can use Posts and TopicPosts I have added: public $uses = array('Topic','TopicPost','Post'); to the top of the controller and made all models act as containable.
So basically I need to find Posts that have a match in the database model TopicPosts for the id of the topic I'm viewing.
I just couldn't get it to work the "proper" way. I'm not sure if this is a bug in cake or something, but the paginate function simply refuses to budge.. The proper way to do this would probably be to write your own paginate function in your Post model, there is some info on how to do that in the cookbook.
Meanwhile, I offer you the workaround below. It's not optimal (at least not without caching) but it works. You can do it the proper way when/if you run into performance problem, but until then, this code below should do it.
public function view ( $slug )
{
$topic = $this->Topic->find('first', array('conditions'=>array('Topic.slug'=>$slug)));
$this->set('topic', $topic);
$this->set('title_for_layout', $topic['Topic']['title'] . ' – Topics');
// step 1: get post IDs related to your topic
$postIDs = $this->Topic->TopicPost->find
(
'list',
array
(
'fields' => array('TopicPost.post_id'),
'conditions' => array('TopicPost.topic_id' => $topic['Topic']['id'])
)
);
$this->paginate = array
(
'Post' => array
(
'limit'=>15,
'conditions'=>array
(
'Post.status' => array(1,2),
// step 2: include them in your paginate conditions
'Post.id' => $postIDs,
),
'order' => array('Post.datetime'=>'desc'),
)
);
$posts = $this->paginate('Post');
$this->set('posts', $posts);
}
(Please note that I've stripped some of the stuff in my tests as I didn't have some of the stuff in your app, so don't forget to put it back)
I think you have a problem with your model relationships, I don't understand why you have a HABTM relationship on the posts model, when you are actually emulating this (using the Has-many-through method) on the TopicPost model itself. If you don't want to use the HABTM behaviour built into cake (and I don't blame you), you should setup relationships like this:
class PostTopic extends AppModel { // note PostTopic, name should be alphabetical
public $belongsTo = array('Post', 'Topic');
}
class Post extends AppModel {
public $hasMany = array('PostTopic');
}
class Topic extends AppModel {
public $hasMany = array('PostTopic');
}
Then to fetch the IDs of posts or topics that relate, you simply load the PostTopic class and do a search:
// ie in Post Controller
$this->paginate($this->Post->PostTopic, array('PostTopic.topic_id' => $post['PostTopic']['topic_id']));
I have a similar setup on my site, where users can add a product to their inventory, It's kind of a HABTM relatipnship but with more data attached to it. See the Product, Inventory and Users models here for a more complicated example.
From question comment:
Also just to note the DB structure and relationships work fine as I
can pull the topics for a post fine using TopicPost so I KNOW that
works, it's just getting Posts for a topic that seems to be not
working...
Right, because you setup the HABTM for Post->Topic, but not for Topic->Post. The 2.0 book defines an example just like yours (but using recipes and ingredients) to describe the use case.
The main difference between hasMany and HABTM is that a link between
models in HABTM is not exclusive. For example, we’re about to join up
our Recipe model with an Ingredient model using HABTM. Using tomatoes
as an Ingredient for my grandma’s spaghetti recipe doesn’t “use up”
the ingredient. I can also use it for a salad Recipe.
The same can be said for a Post using a Topic (your name for a tag/category). And just like their example:
Remember to define a HABTM association in the Ingredient model if
you’d like to fetch Recipe data when using the Ingredient model.
Or, in your case, the Topic model.
So here's the models that you should use:
class Post extends AppModel
{
public $name = 'Post';
public $belongsTo = 'User';
public $hasMany = array('Answer');
// Has many topics that belong to topic post join table... jazz
public $hasAndBelongsToMany = array(
'Topic' => array('with' => 'TopicPost')
);
}
class Topic extends AppModel
{
// Has many posts that belong to topic post join table... jazz
public $hasAndBelongsToMany = array(
'Post' => array('with' => 'TopicPost')
);
}
Now, you don't really need TopicPost unless you are doing something special, but it is fine as it is. And, though you can use that model for adding Topics to to a post (or vice-versa), I'd suggest you use the build in method of handling HABTM saves. Review this page for details on saving HABTM.
If you decide to remove it, make sure you define the join tables and conditions for the HABTM options in each model per the docs
I'm starting a cakephp app, I've never used it in real world so I'm a bit confused how HABTM works, even though I read the documentation I couldn't get even the $this->User->Subscription and didn't see any extra object dumped
What I want is to create a HATBM between users and subscriptions
so I created three tables (users,subscriptions,users_subscribers)
Then in my User.php model I did this
var $hasAndBelongsToMany =
array(
'Subscription' =>
array('className'=>'Subscription',
'joinTable' => 'users_subscribers',
'foreignKey' => 'user_id',
'associationForeignKey' => 'subscription_id',
'unique' => true,
)
);
SUbscription.php
var $hasAndBelongsToMany = array(
'User'=>array('className'=>'User',
'joinTable' => 'users_subscribers',
'foreignKey' => 'subscription_id',
'associationForeignKey' => 'user_id',
'unique' => true));
Even with the tags example and following it, I cannot get the relation set, I also added the line <?php echo $this->element('sql_dump'); ?> to see if its running which it isn't...
Could anyone guide me how exactly you get HATBM to work, what else do I need to verify?
Full code:
pages_controller.php
http://sao.pastebin.com/PWQMhE2z
User model:
http://sao.pastebin.com/PWqwAj1v
Subscription model:
http://sao.pastebin.com/MfVFR4Kw
subscriptions SCHEMA:
http://sao.pastebin.com/mLRcEp1c
User SCHEMA:
http://sao.pastebin.com/UeTRHh3u
users_subscriptions SCHEMA:
http://sao.pastebin.com/4UeSDZte
The simplest and fastest way to get this working is by following CakePHP's rule of configuration over customization.
This means following the CakePHP conventions unless you have a very good reason not to.
I'd strongly recommend starting with a basic setup that you know works, and then modifying that if you need to. Here's a quick and easy way to get up and running.
The Database Tables
Start with three database tables: users, subscriptions and subscriptions_users. The schemas you already have are ok, but I'd make a couple modifications to make sure things go smoothly:
Add a name or title column to the users table. Either that, or you'll have to add the $displayField property to your User model. If you don't do this you'll miss out on some of the "automagic" that CakePHP provides. More info on $displayField
Change the join table's name to subscriptions_users. This is the CakePHP convention and there's no reason not to save yourself the time and worry of following it. :-)
Use the following schema for the join table:
CREATE TABLE subscriptions_users (
subscription_id int(11) NOT NULL,
user_id int(11) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Note that there aren't any keys defined. From the CakePHP manual: "To avoid any issues - don't define a combined primary key for these two fields, if your application requires it you can define a unique index."
The Models
Try to keep your code clean. There are a lot of sensible defaults implemented in CakePHP and there's no point in defining them when they're already defined.
The following models should work for you:
user.php
<?php
class User extends AppModel {
var $hasAndBelongsToMany = array('Subscription');
}
?>
subscription.php
<?php
class User extends AppModel {
var $hasAndBelongsToMany = array('User');
}
?>
Pretty simple. Just be sure your model files are named correctly: user.php and subscription.php, all lowercase.
Also, note that you don't have to set any of the relationship options (className, joinTable, etc.) unless they need to be something besides the default. Ninety percent of the time the defaults should serve you just fine.
You should be up and running now. You can make sure the model objects are being loaded and are accessible in your controllers like this:
users_controller.php
<?php
class UsersController extends AppController {
var $name = 'Users';
function index() {
$this->autoRender = false;
var_dump(is_object($this->User));
var_dump(is_object($this->User->Subscription));
}
}
?>
subscriptions_controller.php
<?php
class SubscriptionsController extends AppController {
var $name = 'Subscriptions';
function index() {
$this->autoRender = false;
var_dump(is_object($this->Subscription));
var_dump(is_object($this->Subscription->User));
}
}
?>
The output of /users and /subscriptions should both be bool(true) bool(true).
You can see the full models by doing pr($this->User);.
Deleting records
If you delete a single record using, for example, $this->User->delete($user_id), all the records in the join table with that user ID will automatically be deleted as well.
If you want to delete a single record from a HABTM join table, without deleting the records that it links to, in effect, "unjoining" the two records, you can do it through the SubscriptionsUser model. This is a model that is created on the fly whenever there's a HABTM relationship.
See here for an example: CakePHP hasAndBelongsToMany (HABTM) Delete Joining Record
I did a test app with a basic schema and I get all the relations right. I suspect your woes have to do with the fact that you did $uses = array('User', 'Subscription');Why don't you try with $uses = $uses = array('User'); and then try
$this->User->find('all');
$this->User->Subscription->find('all');
You also need to define the same HABTM relation in your Subscription.php model. If I recall correctly, CakePHP internally fetches some of the required information from the other side's HABTM configuration.
I always use two "hasMany" relations and one "belongsTo" relation to get the HABTM effect in CakePHP with more control.
Try this:
User model (user.php)
class User extends AppModel {
public $name = 'User';
public $actsAs = array('Containable');
public $hasMany = array('UserSubscription');
}
Subscription model (subscription.php)
class Subscription extends AppModel {
public $name = 'Subscription';
public $actsAs = array('Containable');
public $hasMany = array('UserSubscription');
}
UserSubscription model (user_subscription.php)
class UserSubscription extends AppModel {
public $name = 'UserSubscription';
public $belongsTo = array('User','Subscription');
}
pages/home action (pages_controller.php)
public function home() {
$data = array(
'User' => array(
'id' => 1, 'user_email' => 'allenskd#gmail.com', 'user_password' => 'fdfdkert', 'user_salt' => 'haha', 'user_displayname' => 'David'
),
'UserSubscription' => array(
'user_id' => '1', 'subscription_id' => '1'
),
'Subscription' => array(
'id' => 1, 'title' => 'My first plan', 'price' => '30.00', 'subscriber_count' => '1'
),
);
$this->Subscription->save($data);
$this->User->saveAll($data);
$test = $this->User->find('all', array('contain' => array('UserSubscription' => array('Subscription'))));
pr($test);
}