How to find categories inside contained model in CakePHP

I had a recent question about finding records through conditions in habtm . Now I could search for posts in the category I was looking for.

Now my questions are: how to get the categories of each post. Sometimes a message has one or more categories in this query:

$this->set('posts', $this->Category->find( 'first', array( 'conditions' => array( 'Category.uri' => $uri ), 'contain' => array('Post') ) )); 

I would suggest something like this:

 $this->set('posts', $this->Category->find( 'first', array( 'conditions' => array( 'Category.uri' => $uri ), 'contain' => array('Post' => array( 'contain' => 'Category' )) ) )); 

This is what my models look like.

 // Category Model class Category extends AppModel { var $name = 'Category'; var $hasAndBelongsToMany = array( 'Post' => array( 'className' => 'Post' ) ); var $actsAs = array('Containable'); } // Post Model class Post extends AppModel { var $name = 'Post'; var $hasAndBelongsToMany = array( 'Category' => array( 'className' => 'Category' ) ); var $actsAs = array('Containable'); var $virtualFields = array( 'date_posted' => 'DATE_SUB(Post.created, INTERVAL 7 DAY)' ); } 

An example of the data would be:

 categories id name 1 holidays 2 destinations posts id title 1 I am a post 2 I am another post categories_posts post_id category_id 1 1 2 2 2 1 

I get messages from the holidays.

 Array ( [Category] => Array ( [id] => 3 [name] => holidays [uri] => holidays [created] => 2010-11-25 20:43:03 [modified] => 2010-11-25 20:43:03 ) [Post] => Array ( [0] => Array ( [id] => 1 [title] => I am a post ), [1] => Array ( [id] => 2 [title] => I am a another post ) ) ) 

The problem is that 1 of the messages is in two categories. I would like this information also to be available.

+4
source share
3 answers

I leave my other answer as it is, because it contains a lot of good information for those who might stumble upon it. As for solving your problem more elegantly? You will need to flip the find on it and search from the Post model using ad-hoc, for example:

 // From the CategoriesController $this->Category->Post->find('all', array( 'joins' => array( array( 'table' => 'categories_posts', 'alias' => 'CategoryFilter', 'type' => 'inner', 'conditions' => array( 'CategoryFilter.post_id = Post.id' ) ), array( 'table' => 'categories', 'alias' => 'CategoryUriFilter', 'type' => 'inner', 'conditions' => array( 'CategoryUriFilter.id = CategoryFilter.category_id' ) ) ), 'conditions' => array( 'CategoryUriFilter.uri' => $uri ), 'contain' => array('Category') )); 

The returned array will be formatted a little differently, but it should contain all the data you were looking for.

+1
source

To close. Try the following:

 $this->set('posts', $this->Category->find( 'first', array( 'conditions' => array( 'Category.uri' => $uri ), 'contain' => array('Post' => array('Category')) ) )); 

Tangent

Controller spelling code, for example, has problems:

  • It inflates your controller methods and makes your controllers less readable.
  • This prevents reuse of business logic among your different controllers.

You can satisfactorily fix these problems by moving the search call to the model method:

 // Categories Controller $this->set('posts', $this->Category->get_posts_with_cats($uri)); // Category Model function get_posts_with_cats($uri) { $this->find('first', array( 'conditions' => array('Category.uri' => $uri), 'contain' => array('Post' => array('Category')) )); } 

It makes your code More Awesome :

  • The controller is nice and skinny and super readable.
  • Now you can call the same method from any related model. So, for example, in PostsController you could call: $this->Post->Category->get_posts_with_cats($uri); . Now your code is DRY .

Touch # 2

After you learn how to embed constrained models, as I showed you, you may be tempted to do something like this in the future:

 $this->find('first', array( 'conditions' => array('Category.uri' => $uri), 'contain' => array( 'Post' => array( 'Category' => array( 'Tag' => array('Author') ) ) ) )); 

Good news? . You will get the required model data from the above method (so far you have identified all these relationships).

Scary news? CakePHP does not know how to optimize queries for such deep associations. Thus, as your database grows, you will receive dozens (if not hundreds (if not thousands) of SELECT queries to the database, effectively leading your application to a crawl (if not stopped).

The good people who develop CakePHP are working on safer behavior for CakePHP 2.0, but you have to wait until you get this level of accuracy.

+3
source

I really did this to get what I want:

 function getPostsFromCategory($uri) { return $this->populatePosts($this->find( 'first', array( 'conditions' => array( 'Category.uri' => $uri ), 'contain' => array('Post' => array('Category')) ) )); } function populatePosts($categoryPosts) { $posts = $categoryPosts['Post']; $count = count($categoryPosts['Post']); for ($i = 0; $i < $count; $i++) { $categories = $this->Post->find('first', array('conditions' => array('Post.id' => $categoryPosts['Post'][$i]['id']), 'contains' => array('Category'))); // unset($categories['Post']); foreach ($categories['Category'] as $cat) { $categoryPosts['Post'][$i]['Categories'][] = $cat; } unset($categories); } return $categoryPosts; } 

I really don't know if this can be done in more CakePHP-y though:

I am returning something like this:

 Array ( [Category] => Array ( [id] => 3 [name] => festivals [uri] => festivals [created] => 2010-11-25 20:43:03 [modified] => 2010-11-25 20:43:03 ) [Post] => Array ( [0] => Array ( [id] => 28 [title] => A Post [Categories] => Array ( [0] => Array ( [id] => 3 [name] => festivals [uri] => festivals [created] => 2010-11-25 20:43:03 [modified] => 2010-11-25 20:43:03 ) [1] => Array ( [id] => 4 [name] => destinations [uri] => destinations [created] => 2010-11-28 13:04:52 [modified] => 2010-11-28 13:04:52 ) ) ) ) 
0
source

All Articles