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
|
## Requirements
|
||||||
|
|
||||||
* SilverStripe 3.0
|
* SilverStripe 3.0
|
||||||
|
* (optional) [silverstripe-phockito](https://github.com/hafriedlander/silverstripe-phockito) (for testing)
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
@ -31,11 +31,23 @@ class SearchQuery extends ViewableData {
|
|||||||
if (self::$present === null) self::$present = new stdClass();
|
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);
|
$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);
|
$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;
|
if ($class != $field['origin'] && !is_subclass_of($class, $field['origin'])) return;
|
||||||
|
|
||||||
$value = $this->_getFieldValue($object, $field);
|
$value = $this->_getFieldValue($object, $field);
|
||||||
|
|
||||||
$type = isset(self::$filterTypeMap[$field['type']]) ? self::$filterTypeMap[$field['type']] : self::$filterTypeMap['*'];
|
$type = isset(self::$filterTypeMap[$field['type']]) ? self::$filterTypeMap[$field['type']] : self::$filterTypeMap['*'];
|
||||||
|
|
||||||
if (is_array($value)) foreach($value as $sub) {
|
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);
|
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);
|
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'] ? '~' : '';
|
$fuzzy = $search['fuzzy'] ? '~' : '';
|
||||||
|
|
||||||
foreach ($parts[0] as $part) {
|
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();
|
$searchq = array();
|
||||||
foreach ($search['fields'] as $field) {
|
foreach ($fields as $field) {
|
||||||
$searchq[] = "{$field}:".$part.$fuzzy;
|
$boost = (isset($search['boost'][$field])) ? '^' . $search['boost'][$field] : '';
|
||||||
|
$searchq[] = "{$field}:".$part.$fuzzy.$boost;
|
||||||
}
|
}
|
||||||
$q[] = '+('.implode(' ', $searchq).')';
|
$q[] = '+('.implode(' OR ', $searchq).')';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$q[] = '+'.$part;
|
$q[] = '+'.$part;
|
||||||
@ -259,27 +270,39 @@ abstract class SolrIndex extends SearchIndex {
|
|||||||
$fq[] = ($missing ? "+{$field}:[* TO *] " : '') . '-('.implode(' ', $excludeq).')';
|
$fq[] = ($missing ? "+{$field}:[* TO *] " : '') . '-('.implode(' ', $excludeq).')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!headers_sent()) {
|
||||||
if ($q) header('X-Query: '.implode(' ', $q));
|
if ($q) header('X-Query: '.implode(' ', $q));
|
||||||
if ($fq) header('X-Filters: "'.implode('", "', $fq).'"');
|
if ($fq) header('X-Filters: "'.implode('", "', $fq).'"');
|
||||||
|
}
|
||||||
|
|
||||||
if ($offset == -1) $offset = $query->start;
|
if ($offset == -1) $offset = $query->start;
|
||||||
if ($limit == -1) $limit = $query->limit;
|
if ($limit == -1) $limit = $query->limit;
|
||||||
if ($limit == -1) $limit = SearchQuery::$default_page_size;
|
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();
|
$results = new ArrayList();
|
||||||
|
if($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
|
||||||
foreach ($res->response->docs as $doc) {
|
foreach ($res->response->docs as $doc) {
|
||||||
$result = DataObject::get_by_id($doc->ClassName, $doc->ID);
|
$result = DataObject::get_by_id($doc->ClassName, $doc->ID);
|
||||||
if($result) $results->push($result);
|
if($result) $results->push($result);
|
||||||
}
|
}
|
||||||
|
$numFound = $res->response->numFound;
|
||||||
|
} else {
|
||||||
|
$numFound = 0;
|
||||||
|
}
|
||||||
|
|
||||||
$ret = array();
|
$ret = array();
|
||||||
$ret['Matches'] = new PaginatedList($results);
|
$ret['Matches'] = new PaginatedList($results);
|
||||||
$ret['Matches']->setLimitItems(false);
|
$ret['Matches']->setLimitItems(false);
|
||||||
// Tell PaginatedList how many results there are
|
// Tell PaginatedList how many results there are
|
||||||
$ret['Matches']->setTotalItems($res->response->numFound);
|
$ret['Matches']->setTotalItems($numFound);
|
||||||
// Results for current page start at $offset
|
// Results for current page start at $offset
|
||||||
$ret['Matches']->setPageStart($offset);
|
$ret['Matches']->setPageStart($offset);
|
||||||
// Results per page
|
// Results per page
|
||||||
@ -287,4 +310,19 @@ abstract class SolrIndex extends SearchIndex {
|
|||||||
|
|
||||||
return new ArrayData($ret);
|
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