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'),
// ...
)));
Related
I have Two diff tables as given below:
users and posts
Need data from user's table order by count of posts table
Relationship is defined as:
User Model:
public $hasMany = array('Post');
Post Model
Public $belongsTo = array('User');
counterCache - Cache your count()
This function helps you cache the count of related data. Instead of counting the records manually via find('count'), the model itself tracks any addition/deleting towards the associated $hasMany model and increases/decreases a dedicated integer field within the parent model table.
The name of the field consists of the singular model name followed by a underscore and the word “count”:
my_model_count
Let’s say you have a model called ImageComment and a model called Image, you would add a new INT-field to the image table and name it image_comment_count.
Once you have added the counter field you are good to go. Activate counter-cache in your association by adding a counterCache key and set the value to true:
<?php
class Image extends AppModel {
public $belongsTo = array(
'ImageAlbum' => array('counterCache' => true)
);
}
From now on, every time you add or remove a Image associated to ImageAlbum, the number within image_count is adjusted automatically.
You can also specify counterScope. It allows you to specify a simple condition which tells the model when to update (or when not to, depending on how you look at it) the counter value.
Using our Image model example, we can specify it like so:
<?php
class Image extends AppModel {
public $belongsTo = array(
'ImageAlbum' => array(
'counterCache' => true,
'counterScope' => array('Image.active' => 1) // only count if "Image" is active = 1
));
}
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 use the find('all') function to retrieve the post records from my database, but this will also return all the User information that is associated with the Post model with a belongsTo - hasMany relationship.
The downside of this is that the user model contains password and other important information. Is this considered a security issue? I am nowhere echo-ing the information on the view.
Thanks
EDIT:
I modified my code but I am still getting the associated models.
$this->set('posts_list',$this->Post->find('all',array('contain' => false, 'order' => array('Post.price ASC'))));
Any ideas?
You have several options. You can set the recursive property on a model:
$this->Post->recursive = -1;
$posts = $this->Post->find('all');
Alterantively, you can specify recursive as an option to your search:
$posts = $this->Post->find('all', array(
'recursive' => -1,
'conditions' => ...
);
You can also use the Containable behaviour in your Post model. In that case you can specify an empty set:
class Post extends AppModel {
var $actsAs = array('Containable');
}
$this->Post->contain();
$posts = $this->Post->find('all');
Or, specified in the query:
$posts = $this->Post->find('all', array(
'contain' => false,
);
The upside for the Containable behaviour is when you later on associate other models with your post. Suppose that you implement a Tag model. Now you want to find a post with it's tags, but not the use model:
$posts = $this->Post->find('all', array(
'contain' => array('Tag'),
);
Not necessarily.
But you are retrieving information when you don't need it. It's not a problem now, but keep in mind this becomes a huge problem when you have a lot of associated data
Consider setting your recursive attribute to -1 (or 0 if needed)
$this->Model->recursive = -1;
This will pull data only from the selected model
Or for more fine tuned selection, you can use the Containable behavior : http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
This allows you to select which associations to keep when retrieving data.
just so you know
$this->Model->recursive = -1 will remove all associations
$this->Model->recursive = 0 will remove only hasMany assosiation (so it keeps belongsTo)
Do u use this:
$this->Post->find('all')// If u access it from Post controller
OR,
$this->User->Post->find('all')//If u access it from User controller
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);
}