Apr282009

Pagination with custom find types in CakePHP

With the release of CakePHP 1.2 a whole set of new features were made available to us bakers. One of those features is custom find types, which is one of the coolest things that ever happened since I realized I was cooler than Maverick.

I’m not gonna go through custom find types, you can find more info about them at Matt’s blog, or at this article written by someone whose name I think I’ve heard somewhere. What I’m going to talk about is how to mix your custom find types with pagination, without having to use paginate and paginateCount in your models.

So let’s first start by building yet another posts table, and inserting some records:

CREATE TABLE `posts`(
	`id` INT NOT NULL AUTO_INCREMENT,
	`title` VARCHAR(255) NOT NULL,
	`body` TEXT NOT NULL,
	`published` TINYINT(1) NOT NULL default 0,
	`created` DATETIME,
	`modified` DATETIME,
	PRIMARY KEY(`id`)
);

INSERT INTO `posts`(`title`, `body`, `published`, `created`, `modified`) VALUES
	('Post 1', 'Body for Post 1', 1, NOW(), NOW()),
	('Post 2', 'Body for Post 2', 0, NOW(), NOW()),
	('Post 3', 'Body for Post 3', 0, NOW(), NOW()),
	('Post 4', 'Body for Post 4', 1, NOW(), NOW()),
	('Post 5', 'Body for Post 5', 1, NOW(), NOW()),
	('Post 6', 'Body for Post 6', 0, NOW(), NOW()),
	('Post 7', 'Body for Post 7', 1, NOW(), NOW()),
	('Post 8', 'Body for Post 8', 1, NOW(), NOW()),
	('Post 9', 'Body for Post 9', 1, NOW(), NOW());

Now let’s assume we want a find type called published to fetch only the published posts, and that we also want to be able to paginate using this find type. We will be approaching this through a generic approach, something that can be used throughout all our models. With this in mind, let’s first introduce a model based member variable called $_types, where we define the specific needs of each custom find type. Therefore, that variable will hold what we need as conditions, order, etc. for each custom find type. So let’s build our Post model:

<?php
class Post extends AppModel {
	public $name = 'Post';
	protected $_types = array(
		'published' => array(
			'conditions' => array('Post.published' => 1),
			'order' => array('Post.created' => 'desc')
		)
	);
}
?>

As you can see, we define options for each find type as if we would be calling find() directly. So with the above, instead of doing:

$posts = $this->Post->find('all', array(
	'conditions' => array('Post.published' => 1),
	'order' => array('Post.created' => 'desc')
));

We can now do:

$posts = $this->Post->find('published');

Now, what if we wanted to paginate with the above custom find type? Just as we set pagination parameters through the controller member variable $paginate, we can specify which find type pagination we’ll use. We do so by specifying the find type in the index 0 of the pagination settings. Like so:

$this->paginate['Post'] = array(
	'published',
	'limit' => 10
);

$posts = $this->paginate('Post');

Easy, huh? When this is specified, paginate() does the following:

  1. It issues a find('count') on the Post model, specifying the custom find type (published) in the $options array, through an option named type. Therefore, we can use $options['type'] when our model is about to do the count to use the given options for our custom find type.
  2. It fetches the records by calling find() with the custom find type, find('published') in our example.

So where’s that sexy code? Add the following in your AppModel, making the above available for all our models.

<?php
class AppModel extends Model {
	public function find($type, $options = array()) {
		if (!empty($this->_types)) {
			$types = array_keys($this->_types);
			$type = (is_string($type) ? $type : null);
			if (!empty($type)) {
				if (($type == 'count' && !empty($options['type']) && in_array($options['type'], $types)) || in_array($type, $types)) {
					$options = Set::merge(
						$this->_types[($type == 'count' ? $options['type'] : $type)],
						array_diff_key($options, array('type'=>true))
					);
				}
				if (in_array($type, $types)) {
					$type = (!empty($this->_types[$type]['type']) ? $this->_types[$type]['type'] : 'all');
				}
			}
		}
		return parent::find($type, $options);
	}
}
?>

Now ain’t CakePHP great? Don’t tell me, tell everyone at CakeFest #3.

Bookmark and Share

Related posts:

  1. Modelizing HABTM join tables in CakePHP 1.2: with and auto-with models
  2. CakePHP tip of the day: pay attention to conventions
  3. CakePHP 1.2 tip of the day: be mindful of Model::set


Leave a Comment

10 Comments to "Pagination with custom find types in CakePHP"

  1. Jul132009 at 6:49 am

    ohcibi [Visitor] wrote:

    The urls, which are generated by the paginatorhelper are not e.g. controller/action/page:3 but controller/action/name_of_find_type/page:3.

    If $paginator->options(array(‘url’ => $this->passedArgs)); is called in the view then after every click on a paginator helper link theres one name_of_find_type more in the url.

  2. Aug212009 at 4:26 am

    Pedro [Visitor] wrote:

    hola mariano!

    Took about a week looking for information on how to customize the URL from the paginate method.

    I found several things but nothing really useful, and the truth that I am a little off and I saw that you work with CakePHP.
    The issue is that I have to keep the urls of my site just because they are indexed in Google and the SEO and the structure of URLs is very well composed. For example, I have a url like http:miproyecto.loc/padres which I included in a routes.php enroute to do the following:

    Router::connect (‘/padres’, array(‘controller’=>’contenidos’, ‘action’=>’listado’,'parameters’=>’listar_todo’));

    and when to call the method paginate:

    $array_contenidos = $this->paginate(‘Contenido’,$criterios);

    the url that the paginate method looks like:

    http://miproyecto.loc/contenidos/listado/page:2
    but I want to get
    http:miproyecto.loc/padres-2.

    I tried everything and I managed to change the urls generated by the method of pagination In the function link of the helper paginator.php I have included the following lines

    /* APAÑO PARA PAGINACION */
    $seccion_url = explode(“-”,$this->params['url']['url']);
    $url = ‘http://’.DOMINIO.’/’.$seccion_url[0].’-’.$url['page'];
    /* FIN APAÑO PARA PAGINACION */
    return $this->{$obj}->link($title, $url, $options);

    But now I have the problem to put in all the rules (manually) routes.php (which I can die because there are thousands)
    Router:: connect ( ‘/ preconception-2′, array ( ‘controller’ => ‘content’, ‘action’ => ‘list’, ‘parameters’ =>’ listar_todo ‘,’ named ‘=> array (‘ page ‘=> 2)));

    I hope you can help me and give some advice or instructions.

    Thx

    Muchas gracias y saludos desde España!

  3. Aug252009 at 7:48 am

    Pedro [Visitor] wrote:

    alguna idea mariano…?!

  4. Oct082009 at 3:41 pm

    Jesús Ángel [Visitor] wrote:

    ¡Be careful!

    Model->find() has four args:

    $type, $options = array(), $order = null, $recursive = null

    But the example’s find method only has two args, so you loose the other ones.

    Change:
    public function find($type, $options = array(), $order = null, $recursive = null)

    return parent::find($type, $options, $order = null, $recursive = null);

  5. Oct082009 at 3:43 pm

    Jesús Ángel [Visitor] wrote:

    Be careful.

    Original find method has four args, but this one only has two.

    You have to add the order and recursive params or you will loose them

  6. Oct192009 at 6:11 am

    Leonardo Jorge [Visitor] wrote:

    This implematation looks like Named Scope Behavior

    http://github.com/joelmoss/cakephp-namedscope

  7. Dec262009 at 9:45 am

    Giuliano [Visitor] wrote:

    This isn’t a very good method. It’s clean but doesn’t allow you to pass a variable to your paginate conditions, such as:

    $posts = $this->Post->find(‘all’, array(
    ‘conditions’ =>
    array(‘Post.category’ => $id),
    array(‘Post.published’ => 1),
    ‘order’ => array(‘Post.created’ => ‘desc’)
    ));

    would not work with this method

  8. Dec272009 at 5:58 am

    mariano.iglesias [Member] wrote:

    @Giuliano: You are wrong. You CAN do that. Try it.

  9. Jan252010 at 9:03 am

    adnan wrote:

    @ohcibi, calling the $paginator->options(array(‘url’ => $this->passedArgs)); in the view is never a good idea, though it’s suggested in the Cake Book. Never call it.

    I have found a better way for advance pagination with custom/derived fields in a simple and easy method.

    Here is the details in the blog post: http://abcoder.com/php/cakephp/cakephp-advanced-pagination-sort-by-derived-field/

    Hope you’ll find it helpful.

    Thanks

  10. Apr292010 at 9:04 pm

    Pagination with custom find types in CakePHP | Coding My Thoughts | Source code bank wrote:

    [...] the original post: Pagination with custom find types in CakePHP | Coding My Thoughts If you enjoyed this article please consider sharing [...]

 
Powered by Wordpress and MySQL. Clauz's design for by Cricava