mirror of
https://github.com/silverstripe/silverstripe-fulltextsearch
synced 2024-10-22 14:05:29 +02:00
NEW Added search-time boost support to SolrIndex
This commit is contained in:
parent
2a3e882d70
commit
989cc36766
@ -11,6 +11,7 @@ An attempt to add stable support for Fulltext Search engines like Sphinx and Sol
|
||||
## Requirements
|
||||
|
||||
* SilverStripe 3.0
|
||||
* (optional) [silverstripe-phockito](https://github.com/hafriedlander/silverstripe-phockito) (for testing)
|
||||
|
||||
## Documentation
|
||||
|
||||
|
@ -31,11 +31,23 @@ class SearchQuery extends ViewableData {
|
||||
if (self::$present === null) self::$present = new stdClass();
|
||||
}
|
||||
|
||||
function search($text, $fields = null, $boost = 1) {
|
||||
/**
|
||||
* @param [type] $text [description]
|
||||
* @param [type] $fields [description]
|
||||
* @param array $boost Map of field names to float values. The higher the value,
|
||||
* the more important the field gets for relevancy.
|
||||
*/
|
||||
function search($text, $fields = null, $boost = array()) {
|
||||
$this->search[] = array('text' => $text, 'fields' => $fields ? (array)$fields : null, 'boost' => $boost, 'fuzzy' => false);
|
||||
}
|
||||
|
||||
function fuzzysearch($text, $fields = null, $boost = 1) {
|
||||
/**
|
||||
* @param [type] $text [description]
|
||||
* @param [type] $fields [description]
|
||||
* @param array $boost Map of field names to float values. The higher the value,
|
||||
* the more important the field gets for relevancy.
|
||||
*/
|
||||
function fuzzysearch($text, $fields = null, $boost = array()) {
|
||||
$this->search[] = array('text' => $text, 'fields' => $fields ? (array)$fields : null, 'boost' => $boost, 'fuzzy' => true);
|
||||
}
|
||||
|
||||
|
@ -99,6 +99,7 @@ abstract class SolrIndex extends SearchIndex {
|
||||
if ($class != $field['origin'] && !is_subclass_of($class, $field['origin'])) return;
|
||||
|
||||
$value = $this->_getFieldValue($object, $field);
|
||||
|
||||
$type = isset(self::$filterTypeMap[$field['type']]) ? self::$filterTypeMap[$field['type']] : self::$filterTypeMap['*'];
|
||||
|
||||
if (is_array($value)) foreach($value as $sub) {
|
||||
@ -169,8 +170,15 @@ abstract class SolrIndex extends SearchIndex {
|
||||
Solr::service(get_class($this))->commit(false, false, false);
|
||||
}
|
||||
|
||||
public function search($query, $offset = -1, $limit = -1) {
|
||||
$service = Solr::service(get_class($this));
|
||||
/**
|
||||
* @param SearchQuery $query
|
||||
* @param integer $offset
|
||||
* @param integer $limit
|
||||
* @return ArrayData Map with the following keys:
|
||||
* - 'Matches': ArrayList of the matched object instances
|
||||
*/
|
||||
public function search(SearchQuery $query, $offset = -1, $limit = -1) {
|
||||
$service = $this->getService();
|
||||
|
||||
SearchVariant::with(count($query->classes) == 1 ? $query->classes[0]['class'] : null)->call('alterQuery', $query, $this);
|
||||
|
||||
@ -186,12 +194,15 @@ abstract class SolrIndex extends SearchIndex {
|
||||
$fuzzy = $search['fuzzy'] ? '~' : '';
|
||||
|
||||
foreach ($parts[0] as $part) {
|
||||
if ($search['fields']) {
|
||||
$fields = (isset($search['fields'])) ? $search['fields'] : array();
|
||||
if(isset($search['boost'])) $fields = array_merge($fields, array_keys($search['boost']));
|
||||
if ($fields) {
|
||||
$searchq = array();
|
||||
foreach ($search['fields'] as $field) {
|
||||
$searchq[] = "{$field}:".$part.$fuzzy;
|
||||
foreach ($fields as $field) {
|
||||
$boost = (isset($search['boost'][$field])) ? '^' . $search['boost'][$field] : '';
|
||||
$searchq[] = "{$field}:".$part.$fuzzy.$boost;
|
||||
}
|
||||
$q[] = '+('.implode(' ', $searchq).')';
|
||||
$q[] = '+('.implode(' OR ', $searchq).')';
|
||||
}
|
||||
else {
|
||||
$q[] = '+'.$part;
|
||||
@ -259,27 +270,39 @@ abstract class SolrIndex extends SearchIndex {
|
||||
$fq[] = ($missing ? "+{$field}:[* TO *] " : '') . '-('.implode(' ', $excludeq).')';
|
||||
}
|
||||
|
||||
if ($q) header('X-Query: '.implode(' ', $q));
|
||||
if ($fq) header('X-Filters: "'.implode('", "', $fq).'"');
|
||||
if(!headers_sent()) {
|
||||
if ($q) header('X-Query: '.implode(' ', $q));
|
||||
if ($fq) header('X-Filters: "'.implode('", "', $fq).'"');
|
||||
}
|
||||
|
||||
if ($offset == -1) $offset = $query->start;
|
||||
if ($limit == -1) $limit = $query->limit;
|
||||
if ($limit == -1) $limit = SearchQuery::$default_page_size;
|
||||
|
||||
$res = $service->search($q ? implode(' ', $q) : '*:*', $offset, $limit, array('fq' => implode(' ', $fq)), Apache_Solr_Service::METHOD_POST);
|
||||
$res = $service->search(
|
||||
$q ? implode(' ', $q) : '*:*',
|
||||
$offset,
|
||||
$limit,
|
||||
array('fq' => implode(' ', $fq)),
|
||||
Apache_Solr_Service::METHOD_POST
|
||||
);
|
||||
|
||||
$results = new ArrayList();
|
||||
|
||||
foreach ($res->response->docs as $doc) {
|
||||
$result = DataObject::get_by_id($doc->ClassName, $doc->ID);
|
||||
if($result) $results->push($result);
|
||||
if($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
|
||||
foreach ($res->response->docs as $doc) {
|
||||
$result = DataObject::get_by_id($doc->ClassName, $doc->ID);
|
||||
if($result) $results->push($result);
|
||||
}
|
||||
$numFound = $res->response->numFound;
|
||||
} else {
|
||||
$numFound = 0;
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
$ret['Matches'] = new PaginatedList($results);
|
||||
$ret['Matches']->setLimitItems(false);
|
||||
// Tell PaginatedList how many results there are
|
||||
$ret['Matches']->setTotalItems($res->response->numFound);
|
||||
$ret['Matches']->setTotalItems($numFound);
|
||||
// Results for current page start at $offset
|
||||
$ret['Matches']->setPageStart($offset);
|
||||
// Results per page
|
||||
@ -287,4 +310,19 @@ abstract class SolrIndex extends SearchIndex {
|
||||
|
||||
return new ArrayData($ret);
|
||||
}
|
||||
|
||||
protected $service;
|
||||
|
||||
/**
|
||||
* @return SolrService
|
||||
*/
|
||||
public function getService() {
|
||||
if(!$this->service) $this->service = Solr::service(get_class($this));
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
public function setService(SolrService $service) {
|
||||
$this->service = $service;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
48
tests/SolrIndexTest.php
Normal file
48
tests/SolrIndexTest.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
class SolrIndexTest extends SapphireTest {
|
||||
|
||||
function setUpOnce() {
|
||||
parent::setUpOnce();
|
||||
|
||||
Phockito::include_hamcrest();
|
||||
}
|
||||
|
||||
function testBoost() {
|
||||
$serviceMock = $this->getServiceMock();
|
||||
$index = new SolrIndexTest_FakeIndex();
|
||||
$index->setService($serviceMock);
|
||||
|
||||
$query = new SearchQuery();
|
||||
$query->search(
|
||||
'term',
|
||||
null,
|
||||
array('Field1' => 1.5, 'HasOneObject_Field1' => 3)
|
||||
);
|
||||
$index->search($query);
|
||||
|
||||
Phockito::verify($serviceMock)->search(
|
||||
'+(Field1:term^1.5 OR HasOneObject_Field1:term^3)',
|
||||
anything(), anything(), anything(), anything()
|
||||
);
|
||||
}
|
||||
|
||||
protected function getServiceMock() {
|
||||
$serviceMock = Phockito::mock('SolrService');
|
||||
$fakeResponse = new Apache_Solr_Response(new Apache_Solr_HttpTransport_Response(null, null, null));
|
||||
Phockito::when($serviceMock)
|
||||
->search(anything(), anything(), anything(), anything(), anything())
|
||||
->return($fakeResponse);
|
||||
return $serviceMock;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SolrIndexTest_FakeIndex extends SolrIndex {
|
||||
function init() {
|
||||
$this->addClass('SearchUpdaterTest_Container');
|
||||
|
||||
$this->addFilterField('Field1');
|
||||
$this->addFilterField('HasOneObject.Field1');
|
||||
$this->addFilterField('HasManyObjects.Field1');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user