Commit f207171b authored by Bas de Nooijer's avatar Bas de Nooijer

Implemented MinimumScoreFilter for grouping

parent 29b2be3a
<?php
require(__DIR__.'/init.php');
htmlHeader();
// create a client instance
$client = new Solarium\Client($config);
// enable the filter plugin and get a query instance
$filter = $client->getPlugin('minimumscorefilter');
$query = $client->createQuery($filter::QUERY_TYPE);
$query->setRows(50);
$query->setFields(array('id','name','score'));
$query->setQuery('memory');
$query->setFilterRatio(.8);
$query->setFilterMode($query::FILTER_MODE_MARK);
// get grouping component and set a field to group by
$groupComponent = $query->getGrouping();
$groupComponent->addField('inStock');
// maximum number of items per group
$groupComponent->setLimit(10);
// get a group count
$groupComponent->setNumberOfGroups(true);
// this executes the query and returns the result
$resultset = $client->select($query);
$groups = $resultset->getGrouping();
foreach ($groups as $groupKey => $fieldGroup) {
echo '<h1>'.$groupKey.'</h1>';
echo 'Matches: '.$fieldGroup->getMatches().'<br/>';
echo 'Number of groups: '.$fieldGroup->getNumberOfGroups();
foreach ($fieldGroup as $valueGroup) {
echo '<h2>'.(int)$valueGroup->getValue().'</h2>';
foreach ($valueGroup as $document) {
echo '<hr/><table>';
// the documents are also iterable, to get all fields
foreach ($document as $field => $value) {
// this converts multivalue fields to a comma-separated string
if (is_array($value)) {
$value = implode(', ', $value);
}
echo '<tr><th>' . $field . '</th><td>' . $value . '</td></tr>';
}
echo '</table>';
// by setting the FILTER_MARK option we get a special method to test each document
if ($document->markedAsLowScore()) {
echo '<b>MARKED AS LOW SCORE</b>';
}
}
}
}
htmlFooter();
......@@ -131,6 +131,9 @@
<li><a href="7.5-plugin-bufferedadd.php">7.5 Buffered Add for documents</a></li>
<li><a href="7.6-plugin-prefetchiterator.php">7.6 Prefetch iterator for select queries</a></li>
<li><a href="7.7-plugin-minimumscorefilter.php">7.7 Minimum score filter for select queries</a></li>
<ul style="list-style:none;">
<li><a href="7.7.1-plugin-minimumscorefilter-grouping.php">7.7.1 Minimum score filter for select queries using grouping</a></li>
</ul>
</ul>
</ul>
......
<?php
/**
* Copyright 2014 Bas de Nooijer. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this listof conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of the copyright holder.
*
* @copyright Copyright 2014 Bas de Nooijer <solarium@raspberry.nl>
* @license http://github.com/basdenooijer/solarium/raw/master/COPYING
* @link http://www.solarium-project.org/
*/
/**
* @namespace
*/
namespace Solarium\Plugin\MinimumScoreFilter;
use Solarium\Exception\OutOfBoundsException;
/**
* Minimumscore filter
*/
class Filter
{
/**
* Apply filter to document array
*
* @param array $documents
* @param float $maxScore
* @param float $ratio
* @param string $mode
* @return array
*/
public function filterDocuments($documents, $maxScore, $ratio, $mode)
{
$threshold = $maxScore * $ratio;
switch ($mode) {
case Query::FILTER_MODE_REMOVE:
foreach ($documents as $key => $document) {
if ($document->score < $threshold) {
unset($documents[$key]);
}
}
break;
case Query::FILTER_MODE_MARK:
foreach ($documents as $key => $document) {
$documents[$key] = new Document($document, $threshold);
}
break;
default:
throw new OutOfBoundsException('Unknown filter mode in query: ' . $mode);
break;
}
return $documents;
}
}
......@@ -146,4 +146,19 @@ class Query extends SelectQuery
return 'Solarium\Plugin\MinimumScoreFilter\Result';
}
/**
* Get all registered components
*
* @return AbstractComponent[]
*/
public function getComponents()
{
if (isset($this->components[self::COMPONENT_GROUPING])) {
$this->components[self::COMPONENT_GROUPING]->setOption('resultquerygroupclass', 'Solarium\Plugin\MinimumScoreFilter\QueryGroupResult');
$this->components[self::COMPONENT_GROUPING]->setOption('resultvaluegroupclass', 'Solarium\Plugin\MinimumScoreFilter\ValueGroupResult');
}
return parent::getComponents();
}
}
<?php
/**
* Copyright 2011 Bas de Nooijer. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this listof conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of the copyright holder.
*
* @copyright Copyright 2011 Bas de Nooijer <solarium@raspberry.nl>
* @license http://github.com/basdenooijer/solarium/raw/master/COPYING
* @link http://www.solarium-project.org/
*/
/**
* @namespace
*/
namespace Solarium\Plugin\MinimumScoreFilter;
use Solarium\QueryType\Select\Result\Grouping\QueryGroup as StandardQueryGroupResult;
class QueryGroupResult extends StandardQueryGroupResult
{
/**
* Constructor
*
* @param int $matches
* @param int $numFound
* @param int $start
* @param float $maximumScore
* @param array $documents
* @param Query $query
*/
public function __construct($matches, $numFound, $start, $maximumScore, $documents, $query)
{
$filter = new Filter;
$mode = $query->getFilterMode();
$ratio = $query->getFilterRatio();
$documents = $filter->filterDocuments($documents, $maximumScore, $ratio, $mode);
parent::__construct($matches, $numFound, $start, $maximumScore, $documents, $query);
}
}
......@@ -59,43 +59,12 @@ class Result extends SelectResult
{
foreach ($mapData as $key => $data) {
if ($key == 'documents') {
$data = $this->filterDocuments($data, $mapData['maxscore']);
$filter = new Filter;
$mode = $this->getQuery()->getFilterMode();
$ratio = $this->getQuery()->getFilterRatio();
$data = $filter->filterDocuments($data, $mapData['maxscore'], $ratio, $mode);
}
$this->$key = $data;
}
}
/**
* Apply filter to document array
*
* @param array $documents
* @param float $maxScore
* @return array
*/
protected function filterDocuments($documents, $maxScore)
{
$mode = $this->getQuery()->getFilterMode();
$ratio = $this->getQuery()->getFilterRatio();
$threshold = $maxScore * $ratio;
switch ($mode) {
case Query::FILTER_MODE_REMOVE:
foreach ($documents as $key => $document) {
if ($document->score < $threshold) {
unset($documents[$key]);
}
}
break;
case Query::FILTER_MODE_MARK:
foreach ($documents as $key => $document) {
$documents[$key] = new Document($document, $threshold);
}
break;
default:
throw new OutOfBoundsException('Unknown filter mode in query: ' . $mode);
break;
}
return $documents;
}
}
<?php
/**
* Copyright 2011 Bas de Nooijer. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this listof conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are
* those of the authors and should not be interpreted as representing official
* policies, either expressed or implied, of the copyright holder.
*
* @copyright Copyright 2011 Bas de Nooijer <solarium@raspberry.nl>
* @license http://github.com/basdenooijer/solarium/raw/master/COPYING
* @link http://www.solarium-project.org/
*/
/**
* @namespace
*/
namespace Solarium\Plugin\MinimumScoreFilter;
use Solarium\QueryType\Select\Result\Grouping\ValueGroup as StandardValueGroup;
class ValueGroupResult extends StandardValueGroup
{
/**
* Constructor
*
* @param string $value
* @param int $numFound
* @param int $start
* @param array $documents
* @param Query $query
*/
public function __construct($value, $numFound, $start, $documents, $maximumScore, $query)
{
$filter = new Filter;
$mode = $query->getFilterMode();
$ratio = $query->getFilterRatio();
$documents = $filter->filterDocuments($documents, $maximumScore, $ratio, $mode);
parent::__construct($value, $numFound, $start, $documents);
}
}
......@@ -70,6 +70,16 @@ class Grouping extends Component
*/
protected $type = SelectQuery::COMPONENT_GROUPING;
/**
* Default options
*
* @var array
*/
protected $options = array(
'resultquerygroupclass' => 'Solarium\QueryType\Select\Result\Grouping\QueryGroup',
'resultvaluegroupclass' => 'Solarium\QueryType\Select\Result\Grouping\ValueGroup',
);
/**
* Fields for grouping
*
......@@ -511,4 +521,50 @@ class Grouping extends Component
{
return $this->getOption('format');
}
/**
* Set the query group result class
*
* @param string $value classname
* @return self Provides fluent interface
*/
public function setResultQueryGroupClass($value)
{
return $this->setOption('resultquerygroupclass', $value);
}
/**
* Get the current resultquerygroupclass option
*
* The value is a classname, not an instance
*
* @return string
*/
public function getResultQueryGroupClass()
{
return $this->getOption('resultquerygroupclass');
}
/**
* Set the value group result class
*
* @param string $value classname
* @return self Provides fluent interface
*/
public function setResultValueGroupClass($value)
{
return $this->setOption('resultvaluegroupclass', $value);
}
/**
* Get the current resultvaluegroupclass option
*
* The value is a classname, not an instance
*
* @return string
*/
public function getResultValueGroupClass()
{
return $this->getOption('resultvaluegroupclass');
}
}
......@@ -65,6 +65,8 @@ class Grouping implements ComponentParserInterface
if (isset($data['grouped'])) {
// parse field groups
$valueResultClass = $grouping->getOption('resultvaluegroupclass');
$documentClass = $query->getOption('documentclass');
foreach ($grouping->getFields() as $field) {
if (isset($data['grouped'][$field])) {
$result = $data['grouped'][$field];
......@@ -83,8 +85,10 @@ class Grouping implements ComponentParserInterface
$start = (isset($valueGroupResult['doclist']['start'])) ?
$valueGroupResult['doclist']['start'] : null;
$maxScore = (isset($valueGroupResult['doclist']['maxScore'])) ?
$valueGroupResult['doclist']['maxScore'] : null;
// create document instances
$documentClass = $query->getOption('documentclass');
$documents = array();
if (isset($valueGroupResult['doclist']['docs']) &&
is_array($valueGroupResult['doclist']['docs'])) {
......@@ -94,7 +98,7 @@ class Grouping implements ComponentParserInterface
}
}
$valueGroups[] = new ValueGroup($value, $numFound, $start, $documents);
$valueGroups[] = new $valueResultClass($value, $numFound, $start, $documents, $maxScore, $query);
}
$groups[$field] = new FieldGroup($matches, $groupCount, $valueGroups);
......@@ -102,6 +106,7 @@ class Grouping implements ComponentParserInterface
}
// parse query groups
$groupResultClass = $grouping->getOption('resultquerygroupclass');
foreach ($grouping->getQueries() as $groupQuery) {
if (isset($data['grouped'][$groupQuery])) {
$result = $data['grouped'][$groupQuery];
......@@ -122,7 +127,7 @@ class Grouping implements ComponentParserInterface
}
// create a group result object
$group = new QueryGroup($matches, $numFound, $start, $maxScore, $documents);
$group = new $groupResultClass($matches, $numFound, $start, $maxScore, $documents, $query);
$groups[$groupQuery] = $group;
}
}
......
......@@ -38,6 +38,8 @@
*/
namespace Solarium\QueryType\Select\Result\Grouping;
use Solarium\QueryType\Select\Query\Query;
/**
* Select component grouping query group result
*
......@@ -80,6 +82,11 @@ class QueryGroup implements \IteratorAggregate, \Countable
*/
protected $documents;
/**
* @var Query
*/
protected $query;
/**
* Constructor
*
......@@ -88,16 +95,20 @@ class QueryGroup implements \IteratorAggregate, \Countable
* @param int $start
* @param float $maximumScore
* @param array $documents
* @param Query $query
*/
public function __construct($matches, $numFound, $start, $maximumScore, $documents)
public function __construct($matches, $numFound, $start, $maximumScore, $documents, $query = null)
{
$this->matches = $matches;
$this->numFound = $numFound;
$this->start = $start;
$this->maximumScore = $maximumScore;
$this->documents = $documents;
$this->query = $query;
}
/**
* Get matches value
*
......
......@@ -38,6 +38,8 @@
*/
namespace Solarium\QueryType\Select\Result\Grouping;
use Solarium\QueryType\Select\Query\Query;
/**
* Select component grouping field value group result
*
......@@ -73,6 +75,18 @@ class ValueGroup implements \IteratorAggregate, \Countable
*/
protected $documents;
/**
* Maximum score in group
*
* @var float
*/
protected $maximumScore;
/**
* @var Query
*/
protected $query;
/**
* Constructor
*
......@@ -80,13 +94,17 @@ class ValueGroup implements \IteratorAggregate, \Countable
* @param int $numFound
* @param int $start
* @param array $documents
* @param float $maxScore
* @param Query $query
*/
public function __construct($value, $numFound, $start, $documents)
public function __construct($value, $numFound, $start, $documents, $maxScore = null, $query = null)
{
$this->value = $value;
$this->numFound = $numFound;
$this->start = $start;
$this->documents = $documents;
$this->maximumScore = $maxScore;
$this->query = $query;
}
/**
......@@ -119,6 +137,16 @@ class ValueGroup implements \IteratorAggregate, \Countable
return $this->start;
}
/**
* Get maximumScore value
*
* @return int
*/
public function getMaximumScore()
{
return $this->maximumScore;
}
/**
* Get all documents
*
......
......@@ -100,6 +100,28 @@ class GroupingTest extends \PHPUnit_Framework_TestCase
);
}
public function testSetAndGetResultQueryGroupClass()
{
$value = 'classX';
$this->grouping->setResultQueryGroupClass($value);
$this->assertEquals(
$value,
$this->grouping->getResultQueryGroupClass()
);
}
public function testSetAndGetResultValueGroupClass()
{
$value = 'classY';
$this->grouping->setResultValueGroupClass($value);
$this->assertEquals(
$value,
$this->grouping->getResultValueGroupClass()
);
}
public function testSetAndGetFieldsSingle()
{
$value = 'fieldC';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment