Sphinx Behavior

By Vilen (xumix)
This behavior helps to use Sphinx search engine in your projects.
First, you need Sphinx http://sphinxsearch.com/ installed and configured. I hope that you've already set up.
Now, get the sphinxapi.php from the sphinx distribution and place it in app/vendors.
Download the code and save it to app/models/behaviors/sphinx.php

Behavior code:

Download code
<?php
/**
 * Behavior for simple usage of Sphinx search engine
 * http://www.sphinxsearch.com
 *
 * @copyright 2008, Vilen Tambovtsev
 * @author  Vilen Tambovtsev
 * @license      http://www.opensource.org/licenses/mit-license.php The MIT License
 */


class SphinxBehavior extends ModelBehavior
{
    
/**
     * Used for runtime configuration of model
     */
    
var $runtime = array();
    var 
$_defaults = array('server' => 'localhost''port' => 3312);

    
/**
     * Spinx client object
     *
     * @var SphinxClient
     */
    
var $sphinx null;

    function 
setup(&$model$config = array())
    {
        
$settings array_merge($this->_defaults, (array)$config);

        
$this->settings[$model->alias] = $settings;

        
App::import('Vendor''sphinxapi');
        
$this->runtime[$model->alias]['sphinx'] = new SphinxClient();
        
$this->runtime[$model->alias]['sphinx']->SetServer($this->settings[$model->alias]['server'],
                                                           
$this->settings[$model->alias]['port']);
    }

    
/**
     * beforeFind Callback
     *
     * @param array $query
     * @return array Modified query
     * @access public
     */
    
function beforeFind(&$model$query)
    {
        if (empty(
$query['sphinx']) || empty($query['search']))
            return 
true;

        if (
$model->findQueryType == 'count')
        {
            
$model->recursive = -1;
            
$query['limit'] = 1;
        }

        foreach (
$query['sphinx'] as $key => $setting)
        {

            switch (
$key)
            {
                case 
'filter':
                    foreach (
$setting as $arg)
                    {
                        
$arg[2] = empty($arg[2]) ? false $arg[2];
                        
$this->runtime[$model->alias]['sphinx']->SetFilter($arg[0], (array)$arg[1], $arg[2]);
                    }
                   break;
                case 
'filterRange':
                case 
'filterFloatRange':
                    
$method 'Set' $key;
                    foreach (
$setting as $arg)
                    {
                        
$arg[3] = empty($arg[3]) ? false $arg[3];
                        
$this->runtime[$model->alias]['sphinx']->{$method}($arg[0], (array)$arg[1], $arg[2], $arg[3]);
                    }
                   break;
                case 
'matchMode':
                   
$this->runtime[$model->alias]['sphinx']->SetMatchMode($setting);
                   break;
                case 
'sortMode':
                    
$this->runtime[$model->alias]['sphinx']->SetSortMode(key($setting), reset($setting));
                    break;
                default:
                    break;
            }
        }
        
$this->runtime[$model->alias]['sphinx']->SetLimits(($query['page'] - 1) * $query['limit'],
                                                           
$query['limit']);

        
$indexes = !empty($query['sphinx']['index']) ? implode(',' $query['sphinx']['index']) : '*';

        
$result $this->runtime[$model->alias]['sphinx']->Query($query['search'], $indexes);

        if (
$result === false)
        {
            
trigger_error("Search query failed: " $this->runtime[$model->alias]['sphinx']->GetLastError());
            return 
false;
        }
        else if(isset(
$result['matches']))
        {
            if (
$this->runtime[$model->alias]['sphinx']->GetLastWarning())
            {
                
trigger_error("Search query warning: " $this->runtime[$model->alias]['sphinx']->GetLastWarning());
            }
        }

        unset(
$query['conditions']);
        unset(
$query['order']);
        unset(
$query['offset']);
        
$query['page'] = 1;
        if (
$model->findQueryType == 'count')
        {
            
$result['total'] = !empty($result['total']) ? $result['total'] : 0;
            
$query['fields'] = 'ABS(' $result['total'] . ') AS count';

        }
        else
        {
            if (isset(
$result['matches']))
                
$ids array_keys($result['matches']);
            else
                
$ids = array(0);
            
$query['conditions'] = array($model->alias '.'.$model->primaryKey => $ids);
            
$query['order'] = 'FIND_IN_SET('.$model->alias.'.'.$model->primaryKey.', \'' implode(','$ids) . '\')';

        }

        return 
$query;
    }
}
?>


Usage:

Model Class:

Download code <?php 
class Film extends AppModel {
var 
$actsAs = array('Sphinx');
}
?>


Controller Class:

Download code <?php 
class FilmsController extends AppController
{
function 
index()
{
    
$sphinx = array('matchMode' => SPH_MATCH_ALL'sortMode' => array(SPH_SORT_EXTENDED => '@relevance DESC'));
    
$results $this->Film->find('all', array('search' => 'search string here''sphinx' => $sphinx));
}


function 
paging()
{
        
$pagination = array('Film' => array('contain' =>
                                       array(
'FilmType',
                                             
'Genre',
                                             
'FilmPicture' => array('conditions' => array('type' => 'smallposter')),
                                             
'Country',
                                             
'Person' => array('conditions' => array('FilmsPerson.profession_id' => array(134))),
                                             
'MediaRating'),
                                        
'order' => array('Film.modified' => 'desc'),
                                        
'conditions' => array('Film.active' => 1),
                                        
'limit' => 30));
        
$pagination['Film']['fields'] = array('Film.id''Film.imdb_rating''Film.title',
                                              
'Film.year''MediaRating.rating');


        
$pagination['Film']['sphinx']['filter'][] = array('country_id'$this->params['named']['country']);
        if (!empty(
$this->params['named']['search']))
        {
            
$search trim($this->params['named']['search']);

            
$sort ', modified DESC';
            if (!empty(
$this->params['named']['sort']))
            {
                
$sort explode('.'$this->params['named']['sort']);
                
$sort ', ' $sort[1] . ' DESC';
            }

            
$pagination['Film']['sphinx']['matchMode'] = SPH_MATCH_ALL;
            
$pagination['Film']['sphinx']['sortMode'] = array(SPH_SORT_EXTENDED => '@relevance DESC' $sort);

            
$pagination['Film']['search'] = $search;
        }
        
$this->paginate $pagination;
        
$films $this->paginate();

}

}
?>

 

Comments 822

CakePHP Team Comments Author Comments
 

Comment

1 Thanks

Thanks for this. Was just about to dive into sphinx, and this will certainly save me a lot of time.
Posted Nov 19, 2008 by Thomas Pedersen
 

Comment

2 update

added more examples and updated code
Posted Dec 3, 2008 by Vilen