From 989cc36766ed97d2a2fa71048977a5f50acc6a5a Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Thu, 23 Aug 2012 17:49:32 +0200 Subject: [PATCH] NEW Added search-time boost support to SolrIndex --- README.md | 1 + code/search/SearchQuery.php | 16 +++++++-- code/solr/SolrIndex.php | 66 +++++++++++++++++++++++++++++-------- tests/SolrIndexTest.php | 48 +++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 16 deletions(-) create mode 100644 tests/SolrIndexTest.php diff --git a/README.md b/README.md index e24b55f..442228c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/code/search/SearchQuery.php b/code/search/SearchQuery.php index 61b0a9d..9f8a322 100644 --- a/code/search/SearchQuery.php +++ b/code/search/SearchQuery.php @@ -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); } diff --git a/code/solr/SolrIndex.php b/code/solr/SolrIndex.php index e023b02..75f7697 100644 --- a/code/solr/SolrIndex.php +++ b/code/solr/SolrIndex.php @@ -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; + } } diff --git a/tests/SolrIndexTest.php b/tests/SolrIndexTest.php new file mode 100644 index 0000000..73e755f --- /dev/null +++ b/tests/SolrIndexTest.php @@ -0,0 +1,48 @@ +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'); + } +} \ No newline at end of file