Commit 06e957a9 authored by Bas de Nooijer's avatar Bas de Nooijer Committed by GitHub

Merge pull request #477 from solariumphp/develop

Merging develop to master for new release
parents a2a87ddb 8d25cfb2
# CHANGELOG # CHANGELOG
## 3.8.0 - 2017-01-31
- bugfix: use GET request for extracting remote files
- added: support for simple group format in response parser
- added: helper for fetching ValueGroup from a Grouped result
- bugfix: prevent ParallelExecution Curl spinloop
- added: Guzzle 3 and Guzzle 6 client adapters
- improvement: various fixes in documentation
## 3.7.0 - 2016-10-28 ## 3.7.0 - 2016-10-28
- added: support for nested documents in update query - added: support for nested documents in update query
......
...@@ -19,7 +19,8 @@ ...@@ -19,7 +19,8 @@
"phpunit/phpunit": "~3.7", "phpunit/phpunit": "~3.7",
"squizlabs/php_codesniffer": "~1.4", "squizlabs/php_codesniffer": "~1.4",
"zendframework/zendframework1": "~1.12", "zendframework/zendframework1": "~1.12",
"satooshi/php-coveralls": "~1.0" "satooshi/php-coveralls": "~1.0",
"guzzlehttp/guzzle": "^3.8 || ^6.2"
}, },
"suggest": { "suggest": {
"minimalcode/search": "Query builder compatible with Solarium, allows simplified solr-query handling" "minimalcode/search": "Query builder compatible with Solarium, allows simplified solr-query handling"
......
...@@ -7,7 +7,7 @@ Solarium offers a plugin system to allow for easy extension by users. But plugin ...@@ -7,7 +7,7 @@ Solarium offers a plugin system to allow for easy extension by users. But plugin
BufferedAdd plugin BufferedAdd plugin
================== ==================
When you need to do a lot of document inserts or updates, for instance a bulk update or initial indexing, it’s most efficient to do this in batches. This makes a lot more difference than you might think, for some benchmarks see [http://www.raspberry.nl/2011/04/08/solr-update-performance/ this blogpost](http://www.raspberry.nl/2011/04/08/solr-update-performance/_this_blogpost "wikilink"). When you need to do a lot of document inserts or updates, for instance a bulk update or initial indexing, it’s most efficient to do this in batches. This makes a lot more difference than you might think, for some benchmarks see [this_blog post](http://www.raspberry.nl/2011/04/08/solr-update-performance/).
This can be done very easily with this plugin, you can simply keep feeding documents, it will automatically create batch update queries for you. This can be done very easily with this plugin, you can simply keep feeding documents, it will automatically create batch update queries for you.
...@@ -250,7 +250,7 @@ Some important notes: ...@@ -250,7 +250,7 @@ Some important notes:
2. Rows and start (paging and offset) still work, but again this is not adjusted for filtering. So if you sets rows to 10, you might get less because of the filtering. 2. Rows and start (paging and offset) still work, but again this is not adjusted for filtering. So if you sets rows to 10, you might get less because of the filtering.
3. While it's not strictly necessary, you should sort by score as this is much more efficient. This also fits with the expected use case of getting only the best scoring documents. 3. While it's not strictly necessary, you should sort by score as this is much more efficient. This also fits with the expected use case of getting only the best scoring documents.
4. Result document marking is done using a decorator, so you should still be able to use a custom document class. 4. Result document marking is done using a decorator, so you should still be able to use a custom document class.
5. Be aware of the issues related to 'normalizing' scores: [http://wiki.apache.org/lucene-java/ScoresAsPercentages more info](http://wiki.apache.org/lucene-java/ScoresAsPercentages_more_info "wikilink"). This filter only uses score to calculate a the relevancy relative to the best result and doesn't return this calculated score, but be sure to test your results! In cases like an autocomplete or 'best-bet' type of search this filter can be very useful. 5. Be aware of the issues related to 'normalizing' scores [more info](http://wiki.apache.org/lucene-java/ScoresAsPercentages). This filter only uses score to calculate a the relevancy relative to the best result and doesn't return this calculated score, but be sure to test your results! In cases like an autocomplete or 'best-bet' type of search this filter can be very useful.
Example usage Example usage
------------- -------------
...@@ -319,7 +319,7 @@ Some important notes: ...@@ -319,7 +319,7 @@ Some important notes:
- The execution time is limited by the slowest request. If you execute 3 queries with timings of 0.2, 0.4 and 1.2 seconds the execution time for all will be (near) 1.2 seconds. - The execution time is limited by the slowest request. If you execute 3 queries with timings of 0.2, 0.4 and 1.2 seconds the execution time for all will be (near) 1.2 seconds.
- If one of the requests fails the other requests will still be executed and the results parsed. In the result array the entry for the failed query will contain an exception instead of a result object. It’s your own responsibility to check the result type. - If one of the requests fails the other requests will still be executed and the results parsed. In the result array the entry for the failed query will contain an exception instead of a result object. It’s your own responsibility to check the result type.
- All query types are supported, and you can even mix query types in the same execute call. - All query types are supported, and you can even mix query types in the same execute call.
- For testing this plugin you can use a special Solr delay component I’ve created (and used to develop the plugin). For more info see [http://www.raspberry.nl/2012/01/04/solr-delay-component/ this blog post](http://www.raspberry.nl/2012/01/04/solr-delay-component/_this_blog_post "wikilink"). - For testing this plugin you can use a special Solr delay component I’ve created (and used to develop the plugin). For more info see [this blog post](http://www.raspberry.nl/2012/01/04/solr-delay-component/).
- Add queries using the addQuery method. Supply at least a key and a query instance. Optionally you can supply a client instance as third argument. This can be used to execute queries on different cores or even servers. If omitted the plugin will use it's own client instance. - Add queries using the addQuery method. Supply at least a key and a query instance. Optionally you can supply a client instance as third argument. This can be used to execute queries on different cores or even servers. If omitted the plugin will use it's own client instance.
Example usage Example usage
......
...@@ -17,7 +17,7 @@ See the example code below. ...@@ -17,7 +17,7 @@ See the example code below.
Executing a RealtimeGet query Executing a RealtimeGet query
----------------------------- -----------------------------
First of all create a RealtimeGet query instance and set one ore multiple IDs. Use the `realtimeGet` method of the client to execute the query object. First of all create a RealtimeGet query instance and set a single ID or multiple IDs. Use the `realtimeGet` method of the client to execute the query object.
See the example code below. See the example code below.
......
...@@ -29,7 +29,9 @@ Examples ...@@ -29,7 +29,9 @@ Examples
These are two examples of update query usages. See the following sections for the details and examples of all available commands. These are two examples of update query usages. See the following sections for the details and examples of all available commands.
Add documents: ```php Add documents:
```php
<?php <?php
require(__DIR__.'/init.php'); require(__DIR__.'/init.php');
...@@ -68,7 +70,9 @@ htmlFooter(); ...@@ -68,7 +70,9 @@ htmlFooter();
``` ```
Delete by query: ```php Delete by query:
```php
<?php <?php
require(__DIR__.'/init.php'); require(__DIR__.'/init.php');
......
...@@ -2,7 +2,7 @@ This command commits any buffered commands to the index. This buffering is done ...@@ -2,7 +2,7 @@ This command commits any buffered commands to the index. This buffering is done
If you use the autoCommit feature on the Solr core you probably don't want to use the commit command, because that would trigger a commit in between the autoCommit interval. In all other cases it makes sense to add a commit to your update (or at least to the last update request if you issue multiple) to apply your update to the Solr index. If you use the autoCommit feature on the Solr core you probably don't want to use the commit command, because that would trigger a commit in between the autoCommit interval. In all other cases it makes sense to add a commit to your update (or at least to the last update request if you issue multiple) to apply your update to the Solr index.
This command command starts the commit process on the server, bit the actual commit process might take a while. If your update command with a commit returns successfully this only means Solr received the commands and is processing them, the result might not be visible for a while. The time this takes depends on many factors: hardware, index size, number of index segments, the commands to execute, the search load, etcetera. It can vary between milliseconds and minutes. This command starts the commit process on the server, bit the actual commit process might take a while. If your update command with a commit returns successfully this only means Solr received the commands and is processing them, the result might not be visible for a while. The time this takes depends on many factors: hardware, index size, number of index segments, the commands to execute, the search load, etcetera. It can vary between milliseconds and minutes.
Options Options
------- -------
......
<?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 Solarium\Core\Client\Adapter;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\RequestOptions;
use Solarium\Core\Configurable;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Client\Endpoint;
use Solarium\Exception\HttpException;
/**
* Guzzle HTTP adapter.
*/
class Guzzle extends Configurable implements AdapterInterface
{
/**
* The Guzzle HTTP client instance.
*
* @var GuzzleClient
*/
private $guzzleClient;
/**
* Execute a Solr request using the cURL Http.
*
* @param Request $request The incoming Solr request.
* @param Endpoint $endpoint The configured Solr endpoint.
*
* @return Response
*
* @throws HttpException Thrown if solr request connot be made.
*
* @codingStandardsIgnoreStart AdapterInterface does not declare type-hints
*/
public function execute($request, $endpoint)
{
//@codingStandardsIgnoreEnd
$requestOptions = [
RequestOptions::HEADERS => $this->getRequestHeaders($request),
RequestOptions::BODY => $this->getRequestBody($request),
RequestOptions::TIMEOUT => $endpoint->getTimeout(),
];
// Try endpoint authentication first, fallback to request for backwards compatibility
$authData = $endpoint->getAuthentication();
if (empty($authData['username'])) {
$authData = $request->getAuthentication();
}
if (!empty($authData['username']) && !empty($authData['password'])) {
$requestOptions[RequestOptions::AUTH] = [$authData['username'], $authData['password']];
}
try {
$guzzleResponse = $this->getGuzzleClient()->request(
$request->getMethod(),
$endpoint->getBaseUri() . $request->getUri(),
$requestOptions
);
$responseHeaders = [
"HTTP/{$guzzleResponse->getProtocolVersion()} {$guzzleResponse->getStatusCode()} "
. $guzzleResponse->getReasonPhrase(),
];
foreach ($guzzleResponse->getHeaders() as $key => $value) {
$responseHeaders[] = "{$key}: " . implode(', ', $value);
}
return new Response((string)$guzzleResponse->getBody(), $responseHeaders);
} catch (\GuzzleHttp\Exception\RequestException $e) {
$error = $e->getMessage();
throw new HttpException("HTTP request failed, {$error}");
}
}
/**
* Gets the Guzzle HTTP client instance.
*
* @return GuzzleClient
*/
public function getGuzzleClient()
{
if ($this->guzzleClient === null) {
$this->guzzleClient = new GuzzleClient($this->options);
}
return $this->guzzleClient;
}
/**
* Helper method to create a request body suitable for a guzzle 3 request.
*
* @param Request $request The incoming solarium request.
*
* @return null|resource|string
*/
private function getRequestBody(Request $request)
{
if ($request->getMethod() !== Request::METHOD_POST) {
return null;
}
if ($request->getFileUpload()) {
return fopen($request->getFileUpload(), 'r');
}
return $request->getRawData();
}
/**
* Helper method to extract headers from the incoming solarium request and put them in a format
* suitable for a guzzle 3 request.
*
* @param Request $request The incoming solarium request.
*
* @return array
*/
private function getRequestHeaders(Request $request)
{
$headers = [];
foreach ($request->getHeaders() as $headerLine) {
list($header, $value) = explode(':', $headerLine);
if ($header = trim($header)) {
$headers[$header] = trim($value);
}
}
if (!isset($headers['Content-Type'])) {
if ($request->getMethod() == Request::METHOD_GET) {
$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
} else {
$headers['Content-Type'] = 'application/xml; charset=utf-8';
}
}
return $headers;
}
}
<?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\Core\Client\Adapter;
use Guzzle\Http\Client as GuzzleClient;
use Solarium\Core\Configurable;
use Solarium\Core\Client\Adapter\AdapterInterface;
use Solarium\Core\Client\Request;
use Solarium\Core\Client\Response;
use Solarium\Core\Client\Endpoint;
use Solarium\Exception\HttpException;
/**
* Guzzle3 HTTP adapter.
*/
class Guzzle3 extends Configurable implements AdapterInterface
{
/**
* The Guzzle HTTP client instance.
*
* @var GuzzleClient
*/
private $guzzleClient;
/**
* Execute a Solr request using the cURL Http.
*
* @param Request $request
* @param Endpoint $endpoint
*
* @return Response
*/
public function execute($request, $endpoint)
{
$guzzleRequest = $this->getGuzzleClient()->createRequest(
$request->getMethod(),
$endpoint->getBaseUri() . $request->getUri(),
$this->getRequestHeaders($request),
$this->getRequestBody($request),
[
'timeout' => $endpoint->getTimeout(),
'connecttimeout' => $endpoint->getTimeout(),
]
);
// Try endpoint authentication first, fallback to request for backwards compatibility
$authData = $endpoint->getAuthentication();
if (empty($authData['username'])) {
$authData = $request->getAuthentication();
}
if (!empty($authData['username']) && !empty($authData['password'])) {
$guzzleRequest->setAuth($authData['username'], $authData['password']);
}
try {
$this->getGuzzleClient()->send($guzzleRequest);
$guzzleResponse = $guzzleRequest->getResponse();
$responseHeaders = array_merge(
["HTTP/1.1 {$guzzleResponse->getStatusCode()} {$guzzleResponse->getReasonPhrase()}"],
$guzzleResponse->getHeaderLines()
);
return new Response($guzzleResponse->getBody(true), $responseHeaders);
} catch (\Guzzle\Http\Exception\RequestException $e) {
$error = $e->getMessage();
if ($e instanceof \Guzzle\Http\Exception\CurlException) {
$error = $e->getError();
}
throw new HttpException("HTTP request failed, {$error}");
}
}
/**
* Gets the Guzzle HTTP client instance.
*
* @return GuzzleClient
*/
public function getGuzzleClient()
{
if ($this->guzzleClient === null) {
$this->guzzleClient = new GuzzleClient(null, $this->options);
}
return $this->guzzleClient;
}
/**
* Helper method to create a request body suitable for a guzzle 3 request.
*
* @param Request $request The incoming solarium request.
*
* @return null|resource|string
*/
private function getRequestBody(Request $request)
{
if ($request->getMethod() !== Request::METHOD_POST) {
return null;
}
if ($request->getFileUpload()) {
return fopen($request->getFileUpload(), 'r');
}
return $request->getRawData();
}
/**
* Helper method to extract headers from the incoming solarium request and put them in a format
* suitable for a guzzle 3 request.
*
* @param Request $request The incoming solarium request.
*
* @return array
*/
private function getRequestHeaders(Request $request)
{
$headers = [];
foreach ($request->getHeaders() as $headerLine) {
list($header, $value) = explode(':', $headerLine);
if ($header = trim($header)) {
$headers[$header] = trim($value);
}
}
if (!isset($headers['Content-Type'])) {
if ($request->getMethod() == Request::METHOD_GET) {
$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
} else {
$headers['Content-Type'] = 'application/xml; charset=utf-8';
}
}
return $headers;
}
}
...@@ -156,12 +156,14 @@ class ParallelExecution extends AbstractPlugin ...@@ -156,12 +156,14 @@ class ParallelExecution extends AbstractPlugin
$timeout = $this->getOption('curlmultiselecttimeout'); $timeout = $this->getOption('curlmultiselecttimeout');
while ($active && $mrc == CURLM_OK) { while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($multiHandle, $timeout) != -1) { if (curl_multi_select($multiHandle, $timeout) == -1) {
usleep(100);
}
do { do {
$mrc = curl_multi_exec($multiHandle, $active); $mrc = curl_multi_exec($multiHandle, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM); } while ($mrc == CURLM_CALL_MULTI_PERFORM);
} }
}
$this->client->getEventDispatcher()->dispatch(Events::EXECUTE_END, new ExecuteEndEvent()); $this->client->getEventDispatcher()->dispatch(Events::EXECUTE_END, new ExecuteEndEvent());
......
...@@ -103,6 +103,7 @@ class RequestBuilder extends BaseRequestBuilder ...@@ -103,6 +103,7 @@ class RequestBuilder extends BaseRequestBuilder
$file = $query->getFile(); $file = $query->getFile();
if (preg_match('/^(http|https):\/\/(.+)/i', $file)) { if (preg_match('/^(http|https):\/\/(.+)/i', $file)) {
$request->addParam('stream.url', $file); $request->addParam('stream.url', $file);
$request->setMethod(Request::METHOD_GET);
} elseif (is_readable($file)) { } elseif (is_readable($file)) {
$request->setFileUpload($file); $request->setFileUpload($file);
$request->addParam('resource.name', basename($query->getFile())); $request->addParam('resource.name', basename($query->getFile()));
......
...@@ -61,49 +61,39 @@ class Grouping implements ComponentParserInterface ...@@ -61,49 +61,39 @@ class Grouping implements ComponentParserInterface
*/ */
public function parse($query, $grouping, $data) public function parse($query, $grouping, $data)
{ {
if (!isset($data['grouped'])) {
return new Result(array());
}
$groups = array(); $groups = array();
if (isset($data['grouped'])) {
// parse field groups // parse field groups
$valueResultClass = $grouping->getOption('resultvaluegroupclass'); $valueResultClass = $grouping->getOption('resultvaluegroupclass');
$documentClass = $query->getOption('documentclass'); $documentClass = $query->getOption('documentclass');
// check grouping fields as well as the grouping function (either can be used in the query) // check grouping fields as well as the grouping function (either can be used in the query)
foreach (array_merge($grouping->getFields(), array($grouping->getFunction())) as $field) { foreach (array_merge($grouping->getFields(), array($grouping->getFunction())) as $field) {
if (isset($data['grouped'][$field])) { if (!isset($data['grouped'][$field])) {
continue;
}
$result = $data['grouped'][$field]; $result = $data['grouped'][$field];
$matches = (isset($result['matches'])) ? $result['matches'] : null; $matches = (isset($result['matches'])) ? $result['matches'] : null;
$groupCount = (isset($result['ngroups'])) ? $result['ngroups'] : null; $groupCount = (isset($result['ngroups'])) ? $result['ngroups'] : null;
$valueGroups = array(); if ($grouping->getFormat() === GroupingComponent::FORMAT_SIMPLE) {
foreach ($result['groups'] as $valueGroupResult) { $valueGroups = [$this->extractValueGroup($valueResultClass, $documentClass, $result, $query)];
$value = (isset($valueGroupResult['groupValue'])) ? $groups[$field] = new FieldGroup($matches, $groupCount, $valueGroups);
$valueGroupResult['groupValue'] : null; continue;
$numFound = (isset($valueGroupResult['doclist']['numFound'])) ?
$valueGroupResult['doclist']['numFound'] : null;
$start = (isset($valueGroupResult['doclist']['start'])) ?
$valueGroupResult['doclist']['start'] : null;
$maxScore = (isset($valueGroupResult['doclist']['maxScore'])) ?
$valueGroupResult['doclist']['maxScore'] : null;
// create document instances
$documents = array();
if (isset($valueGroupResult['doclist']['docs']) &&
is_array($valueGroupResult['doclist']['docs'])) {
foreach ($valueGroupResult['doclist']['docs'] as $doc) {
$documents[] = new $documentClass($doc);
}
} }
$valueGroups[] = new $valueResultClass($value, $numFound, $start, $documents, $maxScore, $query); $valueGroups = array();
foreach ($result['groups'] as $valueGroupResult) {
$valueGroups[] = $this->extractValueGroup($valueResultClass, $documentClass, $valueGroupResult, $query);
} }
$groups[$field] = new FieldGroup($matches, $groupCount, $valueGroups); $groups[$field] = new FieldGroup($matches, $groupCount, $valueGroups);
} }
}
// parse query groups // parse query groups
$groupResultClass = $grouping->getOption('resultquerygroupclass'); $groupResultClass = $grouping->getOption('resultquerygroupclass');
...@@ -131,8 +121,43 @@ class Grouping implements ComponentParserInterface ...@@ -131,8 +121,43 @@ class Grouping implements ComponentParserInterface
$groups[$groupQuery] = $group; $groups[$groupQuery] = $group;
} }
} }
}
return new Result($groups); return new Result($groups);
} }
/**
* Helper method to extract a ValueGroup object from the given value group result array.
*
* @param string $valueResultClass The grouping resultvaluegroupclass option.
* @param string $documentClass The name of the solr document class to use.
* @param array $valueGroupResult The group result from the solr response.
* @param Query $query The current solr query.
*
* @return object
*/
private function extractValueGroup($valueResultClass, $documentClass, $valueGroupResult, $query)
{
$value = (isset($valueGroupResult['groupValue'])) ?
$valueGroupResult['groupValue'] : null;
$numFound = (isset($valueGroupResult['doclist']['numFound'])) ?
$valueGroupResult['doclist']['numFound'] : null;
$start = (isset($valueGroupResult['doclist']['start'])) ?
$valueGroupResult['doclist']['start'] : null;
$maxScore = (isset($valueGroupResult['doclist']['maxScore'])) ?
$valueGroupResult['doclist']['maxScore'] : null;
// create document instances
$documents = array();
if (isset($valueGroupResult['doclist']['docs']) &&
is_array($valueGroupResult['doclist']['docs'])) {
foreach ($valueGroupResult['doclist']['docs'] as $doc) {
$documents[] = new $documentClass($doc);
}
}
return new $valueResultClass($value, $numFound, $start, $documents, $maxScore, $query);
}
} }
This diff is collapsed.
This diff is collapsed.
...@@ -161,6 +161,43 @@ class GroupingTest extends \PHPUnit_Framework_TestCase ...@@ -161,6 +161,43 @@ class GroupingTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(), $result->getGroups()); $this->assertEquals(array(), $result->getGroups());
} }
public function testParseMissingGroupField()
{
//data does not contain 'fieldA'
$data = array(
'grouped' => array(
'functionF' => array(
'matches' => 8,
'ngroups' => 3,
'groups' => array(
array(
'groupValue' => true,
'doclist' => array(
'numFound' => 5,
'docs' => array(
array('id' => 3, 'name' => 'fun'),
),
),
),
),
),
'cat:1' => array(
'matches' => 40,
'doclist' => array(
'numFound' => 22,
'docs' => array(
array('id' => 2, 'name' => 'dummy2'),
array('id' => 5, 'name' => 'dummy5'),
),
),
),
),
);
$result = $this->parser->parse($this->query, $this->grouping, $data);
$this->assertNull($result->getGroup('fieldA'));
}
public function testFunctionGroupParsing() public function testFunctionGroupParsing()
{ {
$fieldGroup = $this->result->getGroup('functionF'); $fieldGroup = $this->result->getGroup('functionF');
...@@ -176,4 +213,40 @@ class GroupingTest extends \PHPUnit_Framework_TestCase ...@@ -176,4 +213,40 @@ class GroupingTest extends \PHPUnit_Framework_TestCase
$docs = $valueGroup->getDocuments(); $docs = $valueGroup->getDocuments();
$this->assertEquals('fun', $docs[0]->name); $this->assertEquals('fun', $docs[0]->name);
} }
public function testsParseWithSimpleFormat()
{
$data = array(
'grouped' => array(
'fieldA' => array(
'matches' => 25,
'ngroups' => 12,
'doclist' => array(
'numFound' => 13,
'docs' => array(
array('id' => 1, 'name' => 'test'),
array('id' => 2, 'name' => 'test2'),
),
),
),
),
);
$this->grouping->setFormat(Component::FORMAT_SIMPLE);
$result = $this->parser->parse($this->query, $this->grouping, $data);
$fieldGroup = $result->getGroup('fieldA');
$valueGroups = $fieldGroup->getValueGroups();
$this->assertEquals(25, $fieldGroup->getMatches());
$this->assertEquals(12, $fieldGroup->getNumberOfGroups());
$this->assertEquals(1, count($valueGroups));
$valueGroup = $valueGroups[0];
$this->assertEquals(13, $valueGroup->getNumFound());
$docs = $valueGroup->getDocuments();
$this->assertEquals('test2', $docs[1]->name);
}
} }
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