Implementing a custom find type

The recipe, Searching for all items that match search terms, gives us a great starting point to create a custom find type. Custom find types allow us to extend the basic find types any model has, allowing our code to become more readable and extensible.

This recipe shows how to create a custom find type to allow the Post model to be searched against a set of terms, thus extending the functionality shown in the previous recipe.

Getting ready

We need some sample models and data to work with. Follow the Getting ready section of the recipe, Performing GROUP and COUNT queries.

How to do it...

  1. Open the post.php file and add the search find type to the list of find methods using the _findMethods property, together with the actual implementation of the _findSearch() method.
    <?php
    class Post extends AppModel {
    public $belongsTo = array('Blog'),
    public $_findMethods = array('search' => true);
    protected function _findSearch($state, $query, $results = array()) {
    if ($state == 'before') {
    if (!empty($query['terms'])) {
    $fields = array('title', 'body'),
    $conditions = array();
    foreach ((array) $query['terms'] as $term) {
    foreach ($fields as $field) {
    $model = $this->alias;
    if (strpos($field, '.') !== false) {
    list($model, $field) = explode('.', $field);
    }
    $conditions[] = array(
    $model . '.' . $field . ' LIKE ?' => '%'.$term.'%'
    );
    }
    }
    if (empty($query['fields'])) {
    $query['fields'] = array('Post.title', 'Post.body'),
    }
    $query['conditions'][] = array('or' => $conditions);
    }
    return array_diff_key($query, array('terms'=>null));
    }
    return $results;
    }
    }
    ?>
    
  2. We can now use these custom find types by specifying the list of terms to search with using the search find setting:
    $posts = $this->Post->find('search', array(
    'terms' => array(
    'Post 1',
    'Post 2'
    ),
    'recursive' => -1
    ));
    
  3. If we now browse to http://localhost/posts, we get the id and title fields for four posts, as it is partially shown in the following screenshot:
    How to do it...
  4. Let us now also allow the execution of count operations for custom find types. Because we want a generic solution, we will add this to AppModel. Open the file app_model.php in your app/ folder (create it if you don't have one), and override the find() method as shown below:
    <?php
    class AppModel extends Model {
    public function find($conditions=null, $fields=array(), $order=null, $recursive=null) {
    if (
    is_string($conditions) && $conditions=='count' &&
    is_array($fields) && !empty($fields['type']) &&
    array_key_exists($fields['type'],$this->_findMethods)
    ) {
    $fields['operation'] = 'count';
    return parent::find($fields['type'], array_diff_key(
    $fields,
    array('type'=>null)
    ));
    }
    return parent::find($conditions, $fields, $order, $recursive);
    }
    }
    ?>
    
  5. Now edit your app/models/post.php file and make the following changes to the _findSearch() method:
    protected function _findSearch($state, $query, $results = array()) {
    if ($state == 'before') {
    if (!empty($query['terms'])) {
    $fields = array('title', 'body'),
    $conditions = array();
    foreach ((array) $query['terms'] as $term) {
    foreach ($fields as $field) {
    $model = $this->alias;
    if (strpos($field, '.') !== false) {
    list($model, $field) = explode('.', $field);
    }
    $conditions[] = array(
    $model . '.' . $field . ' LIKE ?' => '%'.$term.'%'
    );
    }
    }
    if (empty($query['fields'])) {
    $query['fields'] = array('Post.title', 'Post.body'),
    }
    if (!empty($query['operation']) && $query['operation'] == 'count') {
    $query['fields'] = 'COUNT(*) AS total';
    }
    $query['conditions'][] = array('or' => $conditions);
    }
    return array_diff_key($query, array('terms'=>null));
    } elseif (
    $state == 'after' && !empty($query['operation']) &&
    $query['operation'] == 'count'
    ) {
    return (!empty($results[0][0]['total']) ? $results[0][0]['total'] : 0);
    }
    return $results;
    }
    
  6. If we wanted to obtain the number of posts that match a set of terms, we would do:
    $count = $this->Post->find('count', array(
    'type' => 'search',
    'terms' => array(
    'Post 1',
    'Post 2'
    )
    ));
    

    Which would correctly return 4.

How it works...

Custom find types are defined in the model _findMethods property. We add types by adding the name of the find type to the property as its index, and setting true as its value in the model that contains the find type.

The method responsible for dealing with the actual find type is named using the following syntax: _findType(), where Type is the find type name, with its first case in uppercase. For a find type of name popular, the method would be named _findPopular().

Every find type method receives three arguments:

  • state: The state at which the find operation is currently on. This can be before (used right before the find operation is to be executed), or after (executed after the find operation is finished, and the perfect place to modify the obtained results.) The before state is where we change the query parameters to meet our needs.
  • query: The data for the query, containing typical find settings (such as fields or conditions), and any extra settings specified in the find operation (in our case, terms).
  • results: Only applicable when the state is set to after, and includes the result of the find operation.

When the state of the find is set to before, the custom find type implementation needs to return the query, as an array of find settings. Therefore, in our implementation, we look for a custom find setting named terms. If there are terms specified, we use them to add LIKE- based conditions to a fixed list of fields. Once we are done, we return the modified query.

When the state is set to after, the implementation needs to return the results. This is the opportunity to modify the resulting rows, if needed, before returning them. In our implementation, we simply return them as they were sent to us.

The last part of the recipe shows us how to add count support for our custom find types. This is something that CakePHP does not offer out of the box, so we implement our own solution. We do so by overriding the find() method and checking to make sure a set of conditions are met:

  1. The find operation being executed is set to count
  2. There's a type setting specified in the query
  3. The type setting is in fact a valid custom find type

When these conditions are met, we add a new query parameter named operation, setting it to count, and we then call the parent find() implementation using the custom find type. This way, our find implementation can check for the operation find setting, and when it is set to count, it forces the fields find setting to COUNT(*) in the before state, and correctly gets the result of the count operation in the after state.

See also

  • Paginating a custom find type
  • Searching for all items that match search terms
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.139.42