Sphinx Behavior
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
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;
$query['page'] = 1;
}
else if (empty($query['limit']))
{
$query['limit'] = 9999999;
$query['page'] = 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(1, 3, 4))),
'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
Comment
1 Thanks
Comment
2 update
Comment
3 Thanks
Question
4 Possible to use find() without specifying a limit?
Here's my code if it helps:
Controller Class:
<?php$sphinx = array('matchMode' => 'SPH_MATCH_ALL', 'sortMode' => array('SPH_SORT_EXTENDED' => '@relevance DESC'));
$results = $this->Resume->find('all', array('search' => 'of', 'limit'=>10, 'sphinx' => $sphinx));?>
Comment
5 smth like this:)
limit => 100000000 ;)
Comment
6 try this piece of code:
if ($model->findQueryType == 'count') .......withif ($model->findQueryType == 'count')
{
$model->recursive = -1;
$query['limit'] = 1;
$query['page'] = 1;
}
else if (empty($query['limit']))
{
$query['limit'] = 9999999;
$query['page'] = 1;
}
Question
7 Another name for field "id"?
first of all:
Thank you very much for this manual!!!!
Since I'm using UUIDs (name:id) for my tables, I inserted an additional field "aiid" (auto-increment id) for sphinx search.
Searching via console works perfectly.
Searching via Cake does not work.
Do I have to define that the Sphinx within Cake should use another table field than "id"?
Best!
stebu
Comment
8 got it
var $primaryKey = 'aiid';to my models.Question
9 Problems with Sphinx Filter
The sphinx was implemented in my project but I can't to use the filters like the example in url http://planetcakephp.org/aggregator/items/1319-cakephp-sphinx-applying-filters-to-your-search
I debugging the behaviors and the sphinx api but I didn't find nothing. The array structure that I use is
'filter' => array( array('price >','300'))
I prove too:
'filter' => array( array('price','300')) - without '>'
If anybody can help me, I great them very much.
Thank you
-------------UPDATED-----------------
I can use Sphinx in my project. The problem was a configuration in sphinx because I didn't add the parameter in the file sphinx.conf.
It's neccesary to add "sql_attr_uint with = " the name of the integer variable .
Comment
10 I have solved my problem
Comment
11 Wonderful Thank you
How does this interact with the Model Caching?
Trying this out on Training-Classes.com
Comment
12 Pagination
Implementing this on Web Design Birmingham
Comment
13 "Field Weight" ability
Insert
between lines 93 and 94 of sphinx.php behavior.case 'fieldWeights':
$this->runtime[$model->alias]['sphinx']->SetFieldWeights($setting);
break;
Usage within the controller (assuming your table owns fields 'title' and 'tags'):
$pagination['Article']['sphinx']['matchMode'] = SPH_MATCH_EXTENDED;
$pagination['Article']['sphinx']['sortMode'] = array (SPH_SORT_EXTENDED=>'@relevance DESC');
$pagination['Article']['sphinx']['fieldWeights'] = array ('title'=>5, 'tags'=>50);
Comment
14 re
Comment
15 view file sample
thanks
Comment
16 setFilterRange Question
Controller Class:
<?php
$sphinx = array('matchMode' => SPH_MATCH_EXTENDED, 'sortMode' => array( $sortmode => $sorttype), 'filterRange' => array('1' => 'dateopened', '2' => $min, '3' => $max, FALSE));
$this->set('results', $this->Search->find('all', array('search' => $phrase.$openSearch, 'sphinx' => $sphinx)));
?>
Using the original code in the behavior, this didn't work at all -- $arg[1] would be 'd', $arg[2] 'a', and so on, breaking the first parameter letter by letter. I'm having a hard time seeing how any of the foreach statements in there could work, since they seem to be doing a full array operation on individual elements. So I rewrote it like this:
case 'filterRange':
// case 'filterFloatRange':
$method = 'Set' . $key;
$i=1;
foreach ($setting as $arg)
{
$para[$i] = $arg;
echo $arg."!<br />"; //this works, shows me what I would expect
$i++;
}
if (!empty($para[3])) $this->runtime[$model->alias]['sphinx']->SetfilterRange($para[1], $para[2], $para[3], $para[4]);
break;
The result is that my date range is ignored in the results. I'm using Sphinx 9.8.1 on this server, so it could be that SetFilterRange just doesn't work with that release. Or I might need something in my sphinx.conf file that I don't have -- I'm a bit unclear on that. But I'm certain (having debugged it) that the routine in the behavior is running, and I'm fairly certain that it's outputting a proper API call to SetFilterRange. Any idea why that call wouldn't be honored?
Thanks!
Comment
17 Solved my own problem