An alternative way to define custom find types

Published on and tagged with cakephp  model

Since changeset 7640 an alternative way for defining custom find types is possible. Rafael Bandeira already mentioned this approach around one month ago, only, at that time it was a hack because it violated the (visibility) conventions of CakePHP…

In the meantime the visibility of this private functionality has been changed to protected, i.e. it is now possible to access it from your own models.

Ok, let’s have a look at some code. We will define a “recent” find type for the Post model:

class Post extends AppModel {	
    public function __construct($id = false, $table = null, $ds = null) {
        $this->_findMethods = $this->_findMethods + array('recent' => true);
        parent::__construct($id, $table, $ds);
    }

    protected function _findRecent($state, $query, $results = array()) {
        if ($state == 'before') {
            $query['order'] = 'Post.created DESC';
            $query['limit'] = 10;
            return $query;
        } else {
            return $results;
        }
    }
}

First, we have to enable our custom find type in the constructor. I don’t know why you have to do that, because a disabled find type doesn’t make much sense to me…

The next step is to implement a protected method for our find type. The name of this method must start with “_find”, followed by the name of the find type (with the first character uppercased). It is a bit a special method, as it gets called twice per find operation: before the find is performed, and after it is performed. This also explains the first parameter, $state, which is either “before” or “after”. In the “before” state you have to prepare and return the query settings (which are passed in with the $query parameter), and in the “after” state you have the possibility to modify the results from the $results parameter.

The last step is to actually use our custom find type as shown in the following examples:

$this->Post->find('recent');
$this->Post->find('recent', array('conditions' => array('Post.published' => true)));

I’m not sure whether this approach should be really used, as the approach with overriding the find() method seems to be easier (see my earlier article Defining custom find types).

Anyway, happy baking :)

13 comments baked

  • rafaelbandeira3

    I think that it may not be easier but it opens our eyes for a common task that we used to do with results of mysql_fetch_array(), when we would only use plan php, that is reorganize the structure of the returned data to fit our needings.
    As cake has it’s own defined data structures (default, list, threaded), one is more likely to pass it to the view and then break his head into pieces looping thru data entries trying to bend it to accoplish his goal, when he could easily re-index, refactor, merge, glue, and all sort of manipulations not only in the data but in it’s structure.
    Using this approach you are really encouraged to do this, and you can use built-in methods as examples.
    It’s like “Don’t fear handling your own data!”

  • taylor luk

    How about abit more automagic (convention over config…)

    auto finder always start with this ‘_find’

    class AppModel extends Model {
        public function __construct($id = false, $table = null, $ds = null) {
            $finders = array();
            foreach(get_class_methods($this) as $method) {
                if (strpos($method, '_find') !== false) {
                    $method = str_replace('_find', '', strtolower($method));
                    $finders[$method] = true;
                }
            }
            $this->_findMethods = $finders;
            parent::__construct($id, $table, $ds);
        }
    }
  • cakebaker

    @Rafael, taylor: Thanks for your comments!

    @Rafael: Yes, it is possible that people get encouraged to modify the structure of the results. On the other hand it could also be confusing that you have to implement a two-state method. I think the future will show whether people will use this approach ;-)

    @taylor: Yes, your snippet becomes handy if you really adopt this approach. Thanks for sharing!

  • Rafael Bandeira

    @taylor: the snippet is very usefull but needs some fixes: first of all it will miss find(‘all’) as there’s no _findAll() method and second is that it’s strtolower() on method’s name takes a little bit of readability, don’t you think?

    $this->find('pricingreport'); 
    // is a little bit worse than
    $this->find('pricingReport');
    
    $this->find('totalworkedtimereport');
    // is much worse than 
    $this->find('totalWorkedTimeReport');

    ok it’s about taste, but the find(‘all’) is much more than an issue:

    class AppModel extends Model {
    	public function __construct($id = false, $table = null, $ds = null) {
    		foreach(get_class_methods($this) as $method) {
    			if (strpos($method, '_find') !== false) {
    				$method = Inflector::variable(str_replace('_find', '', $method));
    				$finders[$method] = true;
    			}
    		}
    		$this->_findMethods = $finders + array('all' => true);
    		parent::__construct($id, $table, $ds);
    	}
    }

    @cakebaker: so far, have you started using it? I’m using it a lot, actually I only use it, all reports, views, tables and lists are generated this way… how about you?

  • cakebaker

    @Rafael: No, I haven’t used it in practice yet. And I’m not sure whether I will adopt this approach. I think it is easier to write a method findRecent() which internally uses the find() method instead of writing a _findRecent() method as shown in the article.

  • rafaelbandeira3

    @cakebaker: and how do you manage pagination then? – ok, a findRecent wouldn’t really need pagination, but for a report for example.

  • cakebaker

    @Rafael: By implementing the paginate() method in my model.

  • rafaelbandeira3

    @cakebaker : so instead of using a two-state method, you endup with a second method dealing with it. Well I think it’s a matter of use case and then taste – because with 10 to 15 find methods generating different and dynamic reports, if I would implement a call for each on my paginate(), I would end in a messy trouble. btw, thanks for the thread

  • cakebaker

    @Rafael: Hm, and how do you say the pagination functionality which find type it should use? I thought it uses always find(‘all’)…

  • rafaelbandeira3

    @cakebaker: I wrote about it last month, on:
    http://rafaelbandeira3.wordpress.com/2008/09/11/paginating-different-find-types/

    and the follow-up explaining how to enable paginateCount for it is on my drafts, waiting to be edited… but I would really think much more usable if you could write about it… you certainlly doc much better than I do.

  • Pretty, Painless and Productive debug with DebugKit « Something on Everything

    […] a merchand a use case of the [7640] changes a real world […]

  • rafaelbandeira3

    hey Daniel, sorry for this spammy pingback, I checked the checkbox to auto pingback links, and it even pinged my own posts back… really sorry.

  • cakebaker

    @Rafael: No problem, wordpress is sometimes a bit arbitrary ;-)

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License