WIP|FEATURE: Provide working suggest implementation

Add all necessary code to provide suggest feature of elasticsearch.
This commit is contained in:
Daniel Siepmann 2017-12-21 11:13:56 +01:00
parent 0716b5a3d2
commit a16bc2a60b
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
15 changed files with 524 additions and 1 deletions

View file

@ -24,6 +24,7 @@ use Codappix\SearchCore\Connection\FacetInterface;
use Codappix\SearchCore\Connection\ResultItemInterface; use Codappix\SearchCore\Connection\ResultItemInterface;
use Codappix\SearchCore\Connection\SearchRequestInterface; use Codappix\SearchCore\Connection\SearchRequestInterface;
use Codappix\SearchCore\Connection\SearchResultInterface; use Codappix\SearchCore\Connection\SearchResultInterface;
use Codappix\SearchCore\Connection\SuggestInterface;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface; use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
class SearchResult implements SearchResultInterface class SearchResult implements SearchResultInterface
@ -43,6 +44,11 @@ class SearchResult implements SearchResultInterface
*/ */
protected $facets = []; protected $facets = [];
/**
* @var array<SuggestInterface>
*/
protected $suggests = [];
/** /**
* @var array<ResultItemInterface> * @var array<ResultItemInterface>
*/ */
@ -92,6 +98,18 @@ class SearchResult implements SearchResultInterface
return $this->facets; return $this->facets;
} }
/**
* Return all suggests, if any.
*
* @return array<SuggestInterface>
*/
public function getSuggests()
{
$this->initSuggests();
return $this->suggests;
}
public function getCurrentCount() public function getCurrentCount()
{ {
return $this->result->count(); return $this->result->count();
@ -119,6 +137,17 @@ class SearchResult implements SearchResultInterface
} }
} }
protected function initSuggests()
{
if ($this->suggests !== []) {
return;
}
foreach ($this->result->getSuggests() as $suggestName => $suggest) {
$this->suggests[$suggestName] = $this->objectManager->get(Suggest::class, $suggestName, $suggest);
}
}
// Countable - Interface // Countable - Interface
public function count() public function count()
{ {

View file

@ -0,0 +1,78 @@
<?php
namespace Codappix\SearchCore\Connection\Elasticsearch;
/*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
use Codappix\SearchCore\Connection\SuggestInterface;
class Suggest implements SuggestInterface
{
/**
* @var string
*/
protected $name = '';
/**
* @var array<SuggestOption>
*/
protected $options = [];
/**
* @var array
*/
protected $rawOptions = [];
public function __construct($name, array $suggest, ConfigurationContainerInterface $configuration)
{
$this->name = $name;
$this->rawOptions = $suggest[0]['options'];
}
public function getName()
{
return $this->name;
}
public function getOptions()
{
$this->initOptions();
return $this->options;
}
public function getUniqueOptions()
{
$this->initOptions();
return array_unique($this->options);
}
protected function initOptions()
{
if ($this->options !== []) {
return;
}
foreach ($this->rawOptions as $rawOption) {
$this->options[] = new SuggestOption($rawOption);
}
}
}

View file

@ -0,0 +1,49 @@
<?php
namespace Codappix\SearchCore\Connection\Elasticsearch;
/*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use Codappix\SearchCore\Connection\SuggestOptionInterface;
class SuggestOption implements SuggestOptionInterface
{
/**
* @var string
*/
protected $text = '';
/**
* @param array $option
*/
public function __construct(array $option)
{
$this->text = $option['text'];
}
public function getText()
{
return $this->text;
}
public function __toString()
{
return $this->text;
}
}

View file

@ -31,6 +31,11 @@ interface SearchRequestInterface extends QueryInterface
*/ */
public function getSearchTerm(); public function getSearchTerm();
/**
* @param array $filter
*/
public function setFilter(array $filter);
/** /**
* @return bool * @return bool
*/ */
@ -40,4 +45,29 @@ interface SearchRequestInterface extends QueryInterface
* @return array * @return array
*/ */
public function getFilter(); public function getFilter();
/**
* @param FacetRequestInterface $facet
*/
public function addFacet(FacetRequestInterface $facet);
/**
* @return array
*/
public function getFacets();
/**
* @param SuggestRequestInterface $suggest
*/
public function addSuggest(SuggestRequestInterface $suggest);
/**
* @return array
*/
public function getSuggests();
/**
* @param ConnectionInterface $connection
*/
public function setConnection(ConnectionInterface $connection);
} }

View file

@ -39,6 +39,13 @@ interface SearchResultInterface extends \Iterator, \Countable, QueryResultInterf
*/ */
public function getFacets(); public function getFacets();
/**
* Return all suggests, if any.
*
* @return array<SuggestInterface>
*/
public function getSuggests();
/** /**
* Returns the number of results in current result * Returns the number of results in current result
* *

View file

@ -0,0 +1,46 @@
<?php
namespace Codappix\SearchCore\Connection;
/*
* Copyright (C) 2016 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* A single suggest, e.g. autocomplete.
*/
interface SuggestInterface
{
/**
* @return string
*/
public function getName();
/**
* Returns all possible options for this suggest.
*
* @return array<SuggestOptionInterface>
*/
public function getOptions();
/**
* Returns all possible options unique for this suggest.
*
* @return array<SuggestOptionInterface>
*/
public function getUniqueOptions();
}

View file

@ -0,0 +1,41 @@
<?php
namespace Codappix\SearchCore\Connection;
/*
* Copyright (C) 2016 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* A single possible option of a suggest.
*/
interface SuggestOptionInterface
{
/**
* Returns the text of this suggest option.
*
* @return string
*/
public function getText();
/**
* Returns the text of this suggest option.
*
* @return string
*/
public function __toString();
}

View file

@ -0,0 +1,42 @@
<?php
namespace Codappix\SearchCore\Connection;
/*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* Used to request suggest from connection.
*/
interface SuggestRequestInterface
{
/**
* The identifier of the suggest, used as key in arrays and to get the
* suggest from search request, etc.
*
* @return string
*/
public function getIdentifier();
/**
* The field to use for suggest building.
*
* @return string
*/
public function getField();
}

View file

@ -56,6 +56,12 @@ class SearchController extends ActionController
] ]
)); ));
} }
if ($this->arguments->hasArgument('searchRequest')) {
$this->arguments->getArgument('searchRequest')->getPropertyMappingConfiguration()
->allowAllProperties()
;
}
} }
/** /**

View file

@ -21,6 +21,7 @@ namespace Codappix\SearchCore\DataProcessing;
*/ */
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Utility\ArrayUtility;
/** /**
* Copies values from one field to another one. * Copies values from one field to another one.
@ -43,7 +44,7 @@ class CopyToProcessor implements ProcessorInterface
$this->addArray($result, $record); $this->addArray($result, $record);
$result = array_filter($result); $result = array_filter($result);
$record[$configuration['to']] = implode(PHP_EOL, $result); $record = ArrayUtility::setValueByPath($record, $configuration['to'], implode(PHP_EOL, $result));
return $record; return $record;
} }

View file

@ -0,0 +1,51 @@
<?php
namespace Codappix\SearchCore\DataProcessing;
/*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Executes TYPO3's explode function on a certain field.
*/
class ExplodeProcessor implements ProcessorInterface
{
public function processRecord(array $record, array $configuration)
{
if (!isset($configuration['delimiter'])) {
$configuration['delimiter'] = ' ';
}
if (is_array($record[$configuration['field']])) {
$record[$configuration['field']] = implode(
$configuration['delimiter'],
$record[$configuration['field']]
);
}
$record[$configuration['field']] = GeneralUtility::trimExplode(
$configuration['delimiter'],
$record[$configuration['field']],
true
);
return $record;
}
}

View file

@ -23,6 +23,7 @@ namespace Codappix\SearchCore\Domain\Model;
use Codappix\SearchCore\Connection\ConnectionInterface; use Codappix\SearchCore\Connection\ConnectionInterface;
use Codappix\SearchCore\Connection\FacetRequestInterface; use Codappix\SearchCore\Connection\FacetRequestInterface;
use Codappix\SearchCore\Connection\SearchRequestInterface; use Codappix\SearchCore\Connection\SearchRequestInterface;
use Codappix\SearchCore\Connection\SuggestRequestInterface;
/** /**
* Represents a search request used to process an actual search. * Represents a search request used to process an actual search.
@ -46,6 +47,11 @@ class SearchRequest implements SearchRequestInterface
*/ */
protected $facets = []; protected $facets = [];
/**
* @var array
*/
protected $suggests = [];
/** /**
* @var int * @var int
*/ */
@ -131,6 +137,26 @@ class SearchRequest implements SearchRequestInterface
return $this->facets; return $this->facets;
} }
/**
* Add a suggest to gather in this search request.
*
* @param SuggestRequestInterface $suggest
*/
public function addSuggest(SuggestRequestInterface $suggest)
{
$this->suggests[$suggest->getIdentifier()] = $suggest;
}
/**
* Returns all configured suggests to fetch in this search request.
*
* @return array
*/
public function getSuggests()
{
return $this->suggests;
}
/** /**
* Define connection to use for this request. * Define connection to use for this request.
* Necessary to allow implementation of execute for interface. * Necessary to allow implementation of execute for interface.

View file

@ -0,0 +1,66 @@
<?php
namespace Codappix\SearchCore\Domain\Model;
/*
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use Codappix\SearchCore\Connection\SuggestRequestInterface;
class SuggestRequest implements SuggestRequestInterface
{
/**
* @var string
*/
protected $identifier = '';
/**
* @var string
*/
protected $field = '';
/**
* TODO: Add validation / exception?
* As the suggests come from configuration this might be a good idea to
* help integrators find issues.
*
* @param string $identifier
* @param string $field
*/
public function __construct($identifier, $field)
{
$this->identifier = $identifier;
$this->field = $field;
}
/**
* @return string
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* @return string
*/
public function getField()
{
return $this->field;
}
}

View file

@ -78,6 +78,7 @@ class QueryFactory
$this->addBoosts($searchRequest, $query); $this->addBoosts($searchRequest, $query);
$this->addFilter($searchRequest, $query); $this->addFilter($searchRequest, $query);
$this->addFacets($searchRequest, $query); $this->addFacets($searchRequest, $query);
$this->addSuggest($searchRequest, $query);
// Use last, as it might change structure of query. // Use last, as it might change structure of query.
// Better approach would be something like DQL to generate query and build result in the end. // Better approach would be something like DQL to generate query and build result in the end.
@ -222,4 +223,26 @@ class QueryFactory
]); ]);
} }
} }
/**
* @param SearchRequestInterface $searchRequest
* @param array $query
*/
protected function addSuggest(SearchRequestInterface $searchRequest, array &$query)
{
foreach ($searchRequest->getSuggests() as $suggest) {
$query = ArrayUtility::arrayMergeRecursiveOverrule($query, [
'suggest' => [
'suggest' => [
$suggest->getIdentifier() => [
'prefix' => $searchRequest->getSearchTerm(),
'completion' => [
'field' => $suggest->getField(),
],
],
],
],
]);
}
}
} }

View file

@ -26,6 +26,7 @@ use Codappix\SearchCore\Connection\ConnectionInterface;
use Codappix\SearchCore\Connection\SearchRequestInterface; use Codappix\SearchCore\Connection\SearchRequestInterface;
use Codappix\SearchCore\Connection\SearchResultInterface; use Codappix\SearchCore\Connection\SearchResultInterface;
use Codappix\SearchCore\Domain\Model\FacetRequest; use Codappix\SearchCore\Domain\Model\FacetRequest;
use Codappix\SearchCore\Domain\Model\SuggestRequest;
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface; use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
/** /**
@ -72,6 +73,7 @@ class SearchService
$searchRequest->setConnection($this->connection); $searchRequest->setConnection($this->connection);
$this->addSize($searchRequest); $this->addSize($searchRequest);
$this->addConfiguredFacets($searchRequest); $this->addConfiguredFacets($searchRequest);
$this->addConfiguredSuggests($searchRequest);
$this->addConfiguredFilters($searchRequest); $this->addConfiguredFilters($searchRequest);
return $this->connection->search($searchRequest); return $this->connection->search($searchRequest);
@ -115,6 +117,32 @@ class SearchService
} }
} }
/**
* Add suggests from configuration to request.
*
* @param SearchRequestInterface $searchRequest
*/
protected function addConfiguredSuggests(SearchRequestInterface $searchRequest)
{
$suggestsConfig = $this->configuration->getIfExists('searching.suggests');
if ($suggestsConfig === null) {
return;
}
foreach ($suggestsConfig as $identifier => $suggestConfig) {
if (!isset($suggestConfig['field']) || trim($suggestConfig['field']) === '') {
// TODO: Finish throw
throw new \Exception('message', 1499171142);
}
$searchRequest->addSuggest($this->objectManager->get(
SuggestRequest::class,
$identifier,
$suggestConfig['field']
));
}
}
/** /**
* Add filters from configuration, e.g. flexform or TypoScript. * Add filters from configuration, e.g. flexform or TypoScript.
* *