List posts by category in CakePHP - php

I'm new to CakePHP. Please help me to write a function to retrieve posts under a particular category for my blog app built using CakePHP.
My table structure:
posts: id, post, body, created, category_id
category: id, group
Also I had defined:
Inside post model - var $belongsTo = 'Category';
Inside category model - var $hasMany = 'Post';

find() is the generic query method for Models in CakePHP.
An example would be:
$results = $this->Post->find('recursive' => -1, 'conditions' => array('Post.category_id' => 1));
debug($results);
There are many ways to achieve what you want. I encourage you to read the docs or working through the CakePHP Blog Tutorial.

$this->Post->find('all', array('conditions' => array('Post.category_id' => $category_id)));
where $category_id is the id of category that you want to retrieve results from database
hope this helps

Related

Show a list of records to have a join to another table using CakePHP

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

CakePHP - listing entries grouped by program_id

I have the following relations between my models
Program hasMany Classroom
Classroom belongsTo Program
What I am trying to achieve is to display all the classrooms, grouped by program, for example:
Program 1
-Classroom 1
-Classroom 2
-Classroom 3
Program 2
-Classroom 1
-Classroom 2... etc
My index action in ClassroomsController:
function index() {
$this->Access->grantAdmin();
$this->Classroom->recursive = 1;
$this->set('classrooms', $this->paginate());
}
I tried to use this:
var $paginate = array(
'group' => 'program_id'
);
But it didn't work as I expected. Any ideas? Thanks
Try adding:
var $actsAs = array('Containable');
to the Program model. Then in the controller:
$programs = $this->Classroom->Program->find('all', array(
'contain' => array(
'Classroom',
),
));
$this->set(compact('programs'));
This should return your data in the hierarchy you're looking for. See the Containable behaviour documentation for more details. Note that you don't need the $this->Classroom->recursive statement if you use Containable.
Your own code example shows that you're using pagination. It should be fairly easy to put containable and pagination together by looking around the docs.

cakePHP - adding new functionality to the model or controller

I'm new to cakePHP and MVC development and trying to create something with cakePHP but can't figure out how to do this :
I'm creating a simple CRUD application which takes in Albums and Songs through simple data entry forms. I created the DB and used the Cake console app to create all the models / controllers etc and it works well. I can CRUD both albums and songs no problem, and the song table in the DB is connected to the album table with a foreign key so all the links and associations are there in the model.
What I want to do is be able to click on an album and see the songs associated with that album, ,but I'm not sure how to go about it. Do I need to add a query in the model, or does that functionality go into the Controller ?
My take is : in the album list make the album names links, which call a |viewAlbum| function in the Songs Controller with the albumID. Not sure where to go from here though ......
Can anyone point me in the right direction ?
Cheers,
Colm
#JohnP Thank you for your reply. How do you create a link to call that function in the controller though ? I have :
echo $html->link(__($album['Album']['title'], true),
array('controller'=>'Songs',
'action'=>'viewAlbum',
$album['Album']['id']));
Where viewAlbum is the name of the function in the songs controller. Any ideas on why this doesn't work ?
Protos -
If I understand correctly -- you're using John's example, and you need to fix the link in your view that calls his controller?
<?
echo $this->Html->link(__($album['Album']['title'], true), array('controller'=>'Album', 'action'=>'viewSongs', $id));
?>
John's example explained how to create a method in the Albums controller, suggested hitting a method in the Songs model that returned the desired results.
So your link would target the Album controller, and its action should be the controller method.
This method makes less sense in the Songs controller, because it requires an Album id. You just want the Album controller to pull associated data from the Songs model / table. John's answer is exactly correct but maybe too complicated if you're just getting started with Cake. John split the needed functionality by putting a method in the Song model, called by a method in the Albums controller, which pulls results for your view to display.
I'm switching this to "fat controller," which is easier to follow for short code but less MVC.
You need a hasMany relationship from Albums to Songs - each Album hasMany Songs:
// ../models/album.php
class Album extends AppModel {
var $name = 'Album';
var $hasMany = array(
'Song' => array(
'className' => 'Song',
'foreignKey' => 'album_id'
)
);
Your controller action will look like this:
// ../controllers/albums_controller.php
function viewSongs($id = null) {
if(isset($id) && $id != null) {
$albums = $this->Album->find('first',
array('conditions'=>array('Album.id'=>$id));
$songs = $this->Album->Song->find('all',
array('conditions'=>array('Song.album_id'=>$id)));
// This returns variables to the view to use
$this->set(compact('albums', 'songs'));
}
}
Your view will be called viewSongs.ctp, and it'll look something like this:
// ../views/albums/viewSongs.ctp
<?php
foreach($albums as $album) {
echo "<h2>{$album['name']}</h2>";
echo "<ul>";
foreach ($songs as $song) {
echo "<li>{$song['Song']['name']}</li>"
}
echo "</ul>";
}
And your link in ../views/albums/view.ctp would be like:
<?php
echo $this->Html->link('View Songs', array('controller'=>'albums',
'action'=>'viewSongs', $id));
?>
Cake's native ORM already does this for you. If you actually go into the view page for an album, it should be showing you all the related songs there it self. This works only if you have setup the relationships properly.
If you want to code this behavior yourself, you could put a viewSongs action in your AlbumController. This method would look at the album ID passed to it, and call a method (e.g. getSongsByAlbum($aid)) in your Song model. Inside that method in your song model would be a call that looks something like
$opts = array(
'conditions' => array(
'album_id' => $aid
)
);
return $this->find('all', $opts);

Retrieving models without getting associated models - CakePHP

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

CakePHP paginate results with a condition on another table linked with HABTM

I've done some searching but I can't find anything relevant enough/working for my scenario. I've got:
Jobs <--> HABTM (Users_jobs table) <--> Users
I would like to do a paginate() from my Job controller with a condition on the User.id, as I do need to fetch -and paginate- all the jobs from the current user.
If you do provide a link to another topic/site, please provide an explanation with it and how you would apply it to my case.
Cheers,
Nicolas.
Make sure your HABTM associations are setup correctly (cake bake would expect a join table of jobs_users rather than users_jobs (see: Cakephp-HABTM) If they are, change the references to JobsUser in the example below to UsersJob to match the layout you described.
//In an action in jobs_controller.php
//Fake a HasOne association to the JobsUser model, setting $reset to false
$this->Job->bindModel(array(
'hasOne' => array(
'JobsUser'
)
), false);
//Setup the paginate conditions, grouping on job.id
//Set $user_id to the user to filter results by (can also be an array of users)
$options = array(
'group' => 'Job.id',
'conditions' => array(
'JobsUser.user.id' => $user_id
)
);
$this->paginate = $options;
$users = $this->paginate();

Categories