mirror of
https://github.com/Codappix/search_core.git
synced 2024-11-25 03:56:11 +01:00
Merge pull request #122 from Codappix/feature/116-execute-dataprocessor-on-result
FEATURE: 116 execute dataprocessor on result
This commit is contained in:
commit
951edf3871
27 changed files with 909 additions and 184 deletions
|
@ -24,10 +24,14 @@ use Codappix\SearchCore\Connection\FacetInterface;
|
|||
use Codappix\SearchCore\Connection\ResultItemInterface;
|
||||
use Codappix\SearchCore\Connection\SearchRequestInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\Domain\Model\QueryResultInterfaceStub;
|
||||
use Codappix\SearchCore\Domain\Model\ResultItem;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
|
||||
class SearchResult implements SearchResultInterface
|
||||
{
|
||||
use QueryResultInterfaceStub;
|
||||
|
||||
/**
|
||||
* @var SearchRequestInterface
|
||||
*/
|
||||
|
@ -104,7 +108,7 @@ class SearchResult implements SearchResultInterface
|
|||
}
|
||||
|
||||
foreach ($this->result->getResults() as $result) {
|
||||
$this->results[] = new ResultItem($result);
|
||||
$this->results[] = new ResultItem($result->getData());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,41 +157,8 @@ class SearchResult implements SearchResultInterface
|
|||
$this->position = 0;
|
||||
}
|
||||
|
||||
// Extbase QueryResultInterface - Implemented to support Pagination of Fluid.
|
||||
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->searchRequest;
|
||||
}
|
||||
|
||||
public function getFirst()
|
||||
{
|
||||
throw new \BadMethodCallException('Method is not implemented yet.', 1502195121);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
throw new \BadMethodCallException('Method is not implemented yet.', 1502195135);
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
// Return false to allow Fluid to use appropriate getter methods.
|
||||
return false;
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
throw new \BadMethodCallException('Use getter to fetch properties.', 1502196933);
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
throw new \BadMethodCallException('You are not allowed to modify the result.', 1502196934);
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
throw new \BadMethodCallException('You are not allowed to modify the result.', 1502196936);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,5 +25,12 @@ namespace Codappix\SearchCore\Connection;
|
|||
*/
|
||||
interface ResultItemInterface extends \ArrayAccess
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns every information as array.
|
||||
*
|
||||
* Provide key/column/field => data.
|
||||
*
|
||||
* Used e.g. for dataprocessing.
|
||||
*/
|
||||
public function getPlainData() : array;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace Codappix\SearchCore\Connection;
|
|||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\Domain\Search\SearchService;
|
||||
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
|
||||
|
||||
interface SearchRequestInterface extends QueryInterface
|
||||
|
@ -40,4 +41,10 @@ interface SearchRequestInterface extends QueryInterface
|
|||
* @return array
|
||||
*/
|
||||
public function getFilter();
|
||||
|
||||
/**
|
||||
* Workaround for paginate widget support which will
|
||||
* use the request to build another search.
|
||||
*/
|
||||
public function setSearchService(SearchService $searchService);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace Codappix\SearchCore\DataProcessing;
|
|||
*/
|
||||
class CopyToProcessor implements ProcessorInterface
|
||||
{
|
||||
public function processRecord(array $record, array $configuration) : array
|
||||
public function processData(array $record, array $configuration) : array
|
||||
{
|
||||
$all = [];
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace Codappix\SearchCore\DataProcessing;
|
|||
*/
|
||||
class GeoPointProcessor implements ProcessorInterface
|
||||
{
|
||||
public function processRecord(array $record, array $configuration) : array
|
||||
public function processData(array $record, array $configuration) : array
|
||||
{
|
||||
if (! $this->canApply($record, $configuration)) {
|
||||
return $record;
|
||||
|
|
|
@ -21,14 +21,13 @@ namespace Codappix\SearchCore\DataProcessing;
|
|||
*/
|
||||
|
||||
/**
|
||||
* All DataProcessing Processors should implement this interface, otherwise they
|
||||
* will not be executed.
|
||||
* All DataProcessing Processors should implement this interface.
|
||||
*/
|
||||
interface ProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Processes the given record.
|
||||
* Processes the given data.
|
||||
* Also retrieves the configuration for this processor instance.
|
||||
*/
|
||||
public function processRecord(array $record, array $configuration) : array;
|
||||
public function processData(array $record, array $configuration) : array;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|||
*/
|
||||
class RemoveProcessor implements ProcessorInterface
|
||||
{
|
||||
public function processRecord(array $record, array $configuration) : array
|
||||
public function processData(array $record, array $configuration) : array
|
||||
{
|
||||
if (!isset($configuration['fields'])) {
|
||||
return $record;
|
||||
|
|
56
Classes/DataProcessing/Service.php
Normal file
56
Classes/DataProcessing/Service.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\DataProcessing;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 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\Extbase\Object\ObjectManagerInterface;
|
||||
|
||||
/**
|
||||
* Eases work with data processing.
|
||||
*/
|
||||
class Service
|
||||
{
|
||||
/**
|
||||
* @var ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
public function __construct(ObjectManagerInterface $objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the dataprocessor depending on configuration and returns the result.
|
||||
*
|
||||
* @param array|string $configuration Either the full configuration or only the class name.
|
||||
*/
|
||||
public function executeDataProcessor($configuration, array $data) : array
|
||||
{
|
||||
if (is_string($configuration)) {
|
||||
$configuration = [
|
||||
'_typoScriptNodeValue' => $configuration,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->objectManager->get($configuration['_typoScriptNodeValue'])
|
||||
->processData($data, $configuration);
|
||||
}
|
||||
}
|
|
@ -43,17 +43,17 @@ abstract class AbstractIndexer implements IndexerInterface
|
|||
*/
|
||||
protected $identifier = '';
|
||||
|
||||
/**
|
||||
* @var \Codappix\SearchCore\DataProcessing\Service
|
||||
* @inject
|
||||
*/
|
||||
protected $dataProcessorService;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Core\Log\Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
|
||||
* @inject
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* Inject log manager to get concrete logger from it.
|
||||
*
|
||||
|
@ -140,18 +140,7 @@ abstract class AbstractIndexer implements IndexerInterface
|
|||
{
|
||||
try {
|
||||
foreach ($this->configuration->get('indexing.' . $this->identifier . '.dataProcessing') as $configuration) {
|
||||
$className = '';
|
||||
if (is_string($configuration)) {
|
||||
$className = $configuration;
|
||||
$configuration = [];
|
||||
} else {
|
||||
$className = $configuration['_typoScriptNodeValue'];
|
||||
}
|
||||
|
||||
$dataProcessor = $this->objectManager->get($className);
|
||||
if ($dataProcessor instanceof ProcessorInterface) {
|
||||
$record = $dataProcessor->processRecord($record, $configuration);
|
||||
}
|
||||
$record = $this->dataProcessorService->executeDataProcessor($configuration, $record);
|
||||
}
|
||||
} catch (InvalidArgumentException $e) {
|
||||
// Nothing to do.
|
||||
|
|
61
Classes/Domain/Model/QueryResultInterfaceStub.php
Normal file
61
Classes/Domain/Model/QueryResultInterfaceStub.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Domain\Model;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* As we have to stay compatible with QueryResultInterface
|
||||
* of extbase but can and need not to provide all methods,
|
||||
* this stub will provde the non implemented methods to
|
||||
* keep real implementations clean.
|
||||
*/
|
||||
trait QueryResultInterfaceStub
|
||||
{
|
||||
public function getFirst()
|
||||
{
|
||||
throw new \BadMethodCallException('Method is not implemented yet.', 1502195121);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
throw new \BadMethodCallException('Method is not implemented yet.', 1502195135);
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
// Return false to allow Fluid to use appropriate getter methods.
|
||||
return false;
|
||||
}
|
||||
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
throw new \BadMethodCallException('Use getter to fetch properties.', 1502196933);
|
||||
}
|
||||
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
throw new \BadMethodCallException('You are not allowed to modify the result.', 1502196934);
|
||||
}
|
||||
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
throw new \BadMethodCallException('You are not allowed to modify the result.', 1502196936);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Connection\Elasticsearch;
|
||||
namespace Codappix\SearchCore\Domain\Model;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
|
@ -29,9 +29,14 @@ class ResultItem implements ResultItemInterface
|
|||
*/
|
||||
protected $data = [];
|
||||
|
||||
public function __construct(\Elastica\Result $result)
|
||||
public function __construct(array $result)
|
||||
{
|
||||
$this->data = $result->getData();
|
||||
$this->data = $result;
|
||||
}
|
||||
|
||||
public function getPlainData() : array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function offsetExists($offset)
|
|
@ -23,6 +23,7 @@ namespace Codappix\SearchCore\Domain\Model;
|
|||
use Codappix\SearchCore\Connection\ConnectionInterface;
|
||||
use Codappix\SearchCore\Connection\FacetRequestInterface;
|
||||
use Codappix\SearchCore\Connection\SearchRequestInterface;
|
||||
use Codappix\SearchCore\Domain\Search\SearchService;
|
||||
|
||||
/**
|
||||
* Represents a search request used to process an actual search.
|
||||
|
@ -63,6 +64,11 @@ class SearchRequest implements SearchRequestInterface
|
|||
*/
|
||||
protected $connection = null;
|
||||
|
||||
/**
|
||||
* @var SearchService
|
||||
*/
|
||||
protected $searchService = null;
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
*/
|
||||
|
@ -143,19 +149,30 @@ class SearchRequest implements SearchRequestInterface
|
|||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function setSearchService(SearchService $searchService)
|
||||
{
|
||||
$this->searchService = $searchService;
|
||||
}
|
||||
|
||||
// Extbase QueryInterface
|
||||
// Current implementation covers only paginate widget support.
|
||||
public function execute($returnRawQueryResult = false)
|
||||
{
|
||||
if ($this->connection instanceof ConnectionInterface) {
|
||||
return $this->connection->search($this);
|
||||
}
|
||||
|
||||
if (! ($this->connection instanceof ConnectionInterface)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Connection was not set before, therefore execute can not work. Use `setConnection` before.',
|
||||
1502197732
|
||||
);
|
||||
}
|
||||
if (! ($this->searchService instanceof SearchService)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'SearchService was not set before, therefore execute can not work. Use `setSearchService` before.',
|
||||
1520325175
|
||||
);
|
||||
}
|
||||
|
||||
return $this->searchService->processResult($this->connection->search($this));
|
||||
}
|
||||
|
||||
public function setLimit($limit)
|
||||
{
|
||||
|
|
129
Classes/Domain/Model/SearchResult.php
Normal file
129
Classes/Domain/Model/SearchResult.php
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Domain\Model;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\Connection\ResultItemInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\Domain\Model\QueryResultInterfaceStub;
|
||||
|
||||
/**
|
||||
* Generic model for mapping a concrete search result from a connection.
|
||||
*/
|
||||
class SearchResult implements SearchResultInterface
|
||||
{
|
||||
use QueryResultInterfaceStub;
|
||||
|
||||
/**
|
||||
* @var SearchResultInterface
|
||||
*/
|
||||
protected $originalSearchResult;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $resultItems = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $results = [];
|
||||
|
||||
/**
|
||||
* For Iterator interface.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $position = 0;
|
||||
|
||||
public function __construct(SearchResultInterface $originalSearchResult, array $resultItems)
|
||||
{
|
||||
$this->originalSearchResult = $originalSearchResult;
|
||||
$this->resultItems = $resultItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<ResultItemInterface>
|
||||
*/
|
||||
public function getResults()
|
||||
{
|
||||
$this->initResults();
|
||||
|
||||
return $this->results;
|
||||
}
|
||||
|
||||
protected function initResults()
|
||||
{
|
||||
if ($this->results !== []) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->resultItems as $item) {
|
||||
$this->results[] = new ResultItem($item);
|
||||
}
|
||||
}
|
||||
|
||||
public function getFacets()
|
||||
{
|
||||
return $this->originalSearchResult->getFacets();
|
||||
}
|
||||
|
||||
public function getCurrentCount()
|
||||
{
|
||||
return $this->originalSearchResult->getCurrentCount();
|
||||
}
|
||||
|
||||
public function count()
|
||||
{
|
||||
return $this->originalSearchResult->count();
|
||||
}
|
||||
|
||||
public function current()
|
||||
{
|
||||
return $this->getResults()[$this->position];
|
||||
}
|
||||
|
||||
public function next()
|
||||
{
|
||||
++$this->position;
|
||||
|
||||
return $this->current();
|
||||
}
|
||||
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function valid()
|
||||
{
|
||||
return isset($this->getResults()[$this->position]);
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->position = 0;
|
||||
}
|
||||
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->originalSearchResult->getQuery();
|
||||
}
|
||||
}
|
|
@ -25,7 +25,9 @@ use Codappix\SearchCore\Configuration\InvalidArgumentException;
|
|||
use Codappix\SearchCore\Connection\ConnectionInterface;
|
||||
use Codappix\SearchCore\Connection\SearchRequestInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\DataProcessing\Service as DataProcessorService;
|
||||
use Codappix\SearchCore\Domain\Model\FacetRequest;
|
||||
use Codappix\SearchCore\Domain\Model\SearchResult;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
|
||||
|
@ -49,19 +51,27 @@ class SearchService
|
|||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @var DataProcessorService
|
||||
*/
|
||||
protected $dataProcessorService;
|
||||
|
||||
/**
|
||||
* @param ConnectionInterface $connection
|
||||
* @param ConfigurationContainerInterface $configuration
|
||||
* @param ObjectManagerInterface $objectManager
|
||||
* @param DataProcessorService $dataProcessorService
|
||||
*/
|
||||
public function __construct(
|
||||
ConnectionInterface $connection,
|
||||
ConfigurationContainerInterface $configuration,
|
||||
ObjectManagerInterface $objectManager
|
||||
ObjectManagerInterface $objectManager,
|
||||
DataProcessorService $dataProcessorService
|
||||
) {
|
||||
$this->connection = $connection;
|
||||
$this->configuration = $configuration;
|
||||
$this->objectManager = $objectManager;
|
||||
$this->dataProcessorService = $dataProcessorService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,12 +80,15 @@ class SearchService
|
|||
*/
|
||||
public function search(SearchRequestInterface $searchRequest)
|
||||
{
|
||||
$searchRequest->setConnection($this->connection);
|
||||
$this->addSize($searchRequest);
|
||||
$this->addConfiguredFacets($searchRequest);
|
||||
$this->addConfiguredFilters($searchRequest);
|
||||
|
||||
return $this->connection->search($searchRequest);
|
||||
// Add connection to request to enable paginate widget support
|
||||
$searchRequest->setConnection($this->connection);
|
||||
$searchRequest->setSearchService($this);
|
||||
|
||||
return $this->processResult($this->connection->search($searchRequest));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,4 +146,31 @@ class SearchService
|
|||
// Nothing todo, no filter configured.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the result, e.g. applies configured data processing to result.
|
||||
*/
|
||||
public function processResult(SearchResultInterface $searchResult) : SearchResultInterface
|
||||
{
|
||||
try {
|
||||
$newSearchResultItems = [];
|
||||
foreach ($this->configuration->get('searching.dataProcessing') as $configuration) {
|
||||
foreach ($searchResult as $resultItem) {
|
||||
$newSearchResultItems[] = $this->dataProcessorService->executeDataProcessor(
|
||||
$configuration,
|
||||
$resultItem->getPlainData()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->objectManager->get(
|
||||
SearchResult::class,
|
||||
$searchResult,
|
||||
$newSearchResultItems
|
||||
);
|
||||
}
|
||||
catch (InvalidArgumentException $e) {
|
||||
return $searchResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,5 +36,7 @@ DataProcessing
|
|||
|
||||
Before data is transfered to search service, it can be processed by "DataProcessors" like already
|
||||
known by :ref:`t3tsref:cobj-fluidtemplate-properties-dataprocessing` of :ref:`t3tsref:cobj-fluidtemplate`.
|
||||
The same is true for retrieved search results. They can be processed again by "DataProcessors" to
|
||||
prepare data for display in Templates or further usage.
|
||||
|
||||
Configuration is done through TypoScript, see :ref:`dataProcessing`.
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
The following Processor are available:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
/configuration/dataProcessing/CopyToProcessor
|
||||
/configuration/dataProcessing/RemoveProcessor
|
||||
/configuration/dataProcessing/GeoPointProcessor
|
||||
|
||||
The following Processor are planned:
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\ReplaceProcessor``
|
||||
Will execute a search and replace on configured fields.
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\RootLevelProcessor``
|
||||
Will attach the root level to the record.
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\ChannelProcessor``
|
||||
Will add a configurable channel to the record, e.g. if you have different areas in your
|
||||
website like "products" and "infos".
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\RelationResolverProcessor``
|
||||
Resolves all relations using the TCA.
|
||||
|
||||
Of course you are able to provide further processors. Just implement
|
||||
``Codappix\SearchCore\DataProcessing\ProcessorInterface`` and use the FQCN (=Fully qualified
|
||||
class name) as done in the examples above.
|
||||
|
||||
By implementing also the same interface as necessary for TYPO3
|
||||
:ref:`t3tsref:cobj-fluidtemplate-properties-dataprocessing`, you are able to reuse the same code
|
||||
also for Fluid to prepare the same record fetched from DB for your fluid.
|
||||
|
||||
Dependency injection is possible inside of processors, as we instantiate through extbase
|
||||
``ObjectManager``.
|
|
@ -146,7 +146,7 @@ Example::
|
|||
dataProcessing
|
||||
--------------
|
||||
|
||||
Used by: All connections while indexing.
|
||||
Used by: All connections while indexing, due to implementation inside ``AbstractIndexer``.
|
||||
|
||||
Configure modifications on each document before sending it to the configured connection. Same as
|
||||
provided by TYPO3 for :ref:`t3tsref:cobj-fluidtemplate` through
|
||||
|
@ -170,41 +170,7 @@ Example::
|
|||
|
||||
The above example will copy all existing fields to the field ``search_spellcheck``. Afterwards
|
||||
all fields, including ``search_spellcheck`` will be copied to ``search_all``.
|
||||
E.g. used to index all information into a field for :ref:`spellchecking` or searching with
|
||||
different :ref:`mapping`.
|
||||
|
||||
The following Processor are available:
|
||||
.. include:: /configuration/dataProcessing/availableAndPlanned.rst
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:glob:
|
||||
|
||||
dataProcessing/CopyToProcessor
|
||||
dataProcessing/RemoveProcessor
|
||||
dataProcessing/GeoPointProcessor
|
||||
|
||||
The following Processor are planned:
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\ReplaceProcessor``
|
||||
Will execute a search and replace on configured fields.
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\RootLevelProcessor``
|
||||
Will attach the root level to the record.
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\ChannelProcessor``
|
||||
Will add a configurable channel to the record, e.g. if you have different areas in your
|
||||
website like "products" and "infos".
|
||||
|
||||
``Codappix\SearchCore\DataProcessing\RelationResolverProcessor``
|
||||
Resolves all relations using the TCA.
|
||||
|
||||
Of course you are able to provide further processors. Just implement
|
||||
``Codappix\SearchCore\DataProcessing\ProcessorInterface`` and use the FQCN (=Fully qualified
|
||||
class name) as done in the examples above.
|
||||
|
||||
By implementing also the same interface as necessary for TYPO3
|
||||
:ref:`t3tsref:cobj-fluidtemplate-properties-dataprocessing`, you are able to reuse the same code
|
||||
also for Fluid to prepare the same record fetched from DB for your fluid.
|
||||
|
||||
Dependency injection is possible inside of processors, as we instantiate through extbase
|
||||
``ObjectManager``.
|
||||
Also data processors are available for search results too, see :ref:`searching_dataProcessing`.
|
||||
|
|
|
@ -216,3 +216,37 @@ Example::
|
|||
}
|
||||
|
||||
Only ``filter`` is allowed as value. Will submit an empty query to switch to filter mode.
|
||||
|
||||
.. _searching_dataProcessing:
|
||||
|
||||
dataProcessing
|
||||
--------------
|
||||
|
||||
Used by: All connections while indexing, due to implementation inside ``SearchService``.
|
||||
|
||||
Configure modifications on each document before returning search result. Same as provided by TYPO3
|
||||
for :ref:`t3tsref:cobj-fluidtemplate` through
|
||||
:ref:`t3tsref:cobj-fluidtemplate-properties-dataprocessing`.
|
||||
|
||||
All processors are applied in configured order. Allowing to work with already processed data.
|
||||
|
||||
Example::
|
||||
|
||||
plugin.tx_searchcore.settings.searching.dataProcessing {
|
||||
1 = Codappix\SearchCore\DataProcessing\CopyToProcessor
|
||||
1 {
|
||||
to = search_spellcheck
|
||||
}
|
||||
|
||||
2 = Codappix\SearchCore\DataProcessing\CopyToProcessor
|
||||
2 {
|
||||
to = search_all
|
||||
}
|
||||
}
|
||||
|
||||
The above example will copy all existing fields to the field ``search_spellcheck``. Afterwards
|
||||
all fields, including ``search_spellcheck`` will be copied to ``search_all``.
|
||||
|
||||
.. include:: /configuration/dataProcessing/availableAndPlanned.rst
|
||||
|
||||
Also data processors are available while indexing too, see :ref:`dataProcessing`.
|
||||
|
|
|
@ -29,6 +29,16 @@ Also multiple filter are supported. Filtering results by fields for string conte
|
|||
Facets / aggregates are also possible. Therefore a mapping has to be defined in TypoScript for
|
||||
indexing, and the facets itself while searching.
|
||||
|
||||
.. _features_dataProcessing:
|
||||
|
||||
DataProcessing
|
||||
==============
|
||||
|
||||
DataProcessing, as known from ``FLUIDTEMPLATE`` is available while indexing and for search results.
|
||||
Each item can be processed by multiple processor to prepare data for indexing and output.
|
||||
|
||||
See :ref:`concepts_indexing_dataprocessing` in :ref:`concepts` section.
|
||||
|
||||
.. _features_planned:
|
||||
|
||||
Planned
|
||||
|
|
|
@ -27,15 +27,15 @@ class CopyToProcessorTest extends AbstractUnitTestCase
|
|||
{
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider getPossibleRecordConfigurationCombinations
|
||||
* @dataProvider getPossibleDataConfigurationCombinations
|
||||
*/
|
||||
public function fieldsAreCopiedAsConfigured(array $record, array $configuration, array $expectedRecord)
|
||||
public function fieldsAreCopiedAsConfigured(array $record, array $configuration, array $expectedData)
|
||||
{
|
||||
$subject = new CopyToProcessor();
|
||||
$processedRecord = $subject->processRecord($record, $configuration);
|
||||
$processedData = $subject->processData($record, $configuration);
|
||||
$this->assertSame(
|
||||
$expectedRecord,
|
||||
$processedRecord,
|
||||
$expectedData,
|
||||
$processedData,
|
||||
'The processor did not return the expected processed record.'
|
||||
);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class CopyToProcessorTest extends AbstractUnitTestCase
|
|||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPossibleRecordConfigurationCombinations()
|
||||
public function getPossibleDataConfigurationCombinations()
|
||||
{
|
||||
return [
|
||||
'Copy all fields to new field' => [
|
||||
|
@ -54,7 +54,7 @@ class CopyToProcessorTest extends AbstractUnitTestCase
|
|||
'configuration' => [
|
||||
'to' => 'new_field',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
'field 2' => 'Some more content like ipsum',
|
||||
'new_field' => 'Some content like lorem' . PHP_EOL . 'Some more content like ipsum',
|
||||
|
@ -71,7 +71,7 @@ class CopyToProcessorTest extends AbstractUnitTestCase
|
|||
'configuration' => [
|
||||
'to' => 'new_field',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
'field with sub2' => [
|
||||
'Tag 1',
|
||||
|
|
|
@ -27,15 +27,15 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
{
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider getPossibleRecordConfigurationCombinations
|
||||
* @dataProvider getPossibleDataConfigurationCombinations
|
||||
*/
|
||||
public function geoPointsAreAddedAsConfigured(array $record, array $configuration, array $expectedRecord)
|
||||
public function geoPointsAreAddedAsConfigured(array $record, array $configuration, array $expectedData)
|
||||
{
|
||||
$subject = new GeoPointProcessor();
|
||||
$processedRecord = $subject->processRecord($record, $configuration);
|
||||
$processedData = $subject->processData($record, $configuration);
|
||||
$this->assertSame(
|
||||
$expectedRecord,
|
||||
$processedRecord,
|
||||
$expectedData,
|
||||
$processedData,
|
||||
'The processor did not return the expected processed record.'
|
||||
);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPossibleRecordConfigurationCombinations()
|
||||
public function getPossibleDataConfigurationCombinations()
|
||||
{
|
||||
return [
|
||||
'Create new field with existing lat and lng' => [
|
||||
|
@ -56,7 +56,7 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
'lat' => 'lat',
|
||||
'lon' => 'lng',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'lat' => 23.232,
|
||||
'lng' => 45.43,
|
||||
'location' => [
|
||||
|
@ -73,7 +73,7 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
'configuration' => [
|
||||
'to' => 'location',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'lat' => 23.232,
|
||||
'lng' => 45.43,
|
||||
],
|
||||
|
@ -88,7 +88,7 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
'lat' => 'lat',
|
||||
'lon' => 'lng',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'lat' => '',
|
||||
'lng' => '',
|
||||
],
|
||||
|
@ -103,7 +103,7 @@ class GeoPointProcessorTest extends AbstractUnitTestCase
|
|||
'lat' => 'lat',
|
||||
'lon' => 'lng',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'lat' => 'av',
|
||||
'lng' => 'dsf',
|
||||
],
|
||||
|
|
|
@ -27,15 +27,15 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
{
|
||||
/**
|
||||
* @test
|
||||
* @dataProvider getPossibleRecordConfigurationCombinations
|
||||
* @dataProvider getPossibleDataConfigurationCombinations
|
||||
*/
|
||||
public function fieldsAreCopiedAsConfigured(array $record, array $configuration, array $expectedRecord)
|
||||
public function fieldsAreCopiedAsConfigured(array $record, array $configuration, array $expectedData)
|
||||
{
|
||||
$subject = new RemoveProcessor();
|
||||
$processedRecord = $subject->processRecord($record, $configuration);
|
||||
$processedData = $subject->processData($record, $configuration);
|
||||
$this->assertSame(
|
||||
$expectedRecord,
|
||||
$processedRecord,
|
||||
$expectedData,
|
||||
$processedData,
|
||||
'The processor did not return the expected processed record.'
|
||||
);
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPossibleRecordConfigurationCombinations()
|
||||
public function getPossibleDataConfigurationCombinations()
|
||||
{
|
||||
return [
|
||||
'Nothing configured' => [
|
||||
|
@ -56,7 +56,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
],
|
||||
'configuration' => [
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
'field with sub2' => [
|
||||
'Tag 1',
|
||||
|
@ -76,7 +76,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
'fields' => 'field with sub2',
|
||||
'_typoScriptNodeValue' => 'Codappix\SearchCore\DataProcessing\RemoveProcessor',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
],
|
||||
],
|
||||
|
@ -92,7 +92,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
'fields' => 'non existing',
|
||||
'_typoScriptNodeValue' => 'Codappix\SearchCore\DataProcessing\RemoveProcessor',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
'field with sub2' => [
|
||||
'Tag 1',
|
||||
|
@ -113,7 +113,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
'fields' => 'field 3, field with sub2',
|
||||
'_typoScriptNodeValue' => 'Codappix\SearchCore\DataProcessing\RemoveProcessor',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
'field 1' => 'Some content like lorem',
|
||||
],
|
||||
],
|
||||
|
@ -125,7 +125,7 @@ class RemoveProcessorTest extends AbstractUnitTestCase
|
|||
'fields' => 'field 1',
|
||||
'_typoScriptNodeValue' => 'Codappix\SearchCore\DataProcessing\RemoveProcessor',
|
||||
],
|
||||
'expectedRecord' => [
|
||||
'expectedData' => [
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
@ -24,9 +24,9 @@ use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
|
|||
use Codappix\SearchCore\Configuration\InvalidArgumentException;
|
||||
use Codappix\SearchCore\Connection\ConnectionInterface;
|
||||
use Codappix\SearchCore\DataProcessing\CopyToProcessor;
|
||||
use Codappix\SearchCore\DataProcessing\Service as DataProcessorService;
|
||||
use Codappix\SearchCore\Domain\Index\AbstractIndexer;
|
||||
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
|
||||
class AbstractIndexerTest extends AbstractUnitTestCase
|
||||
{
|
||||
|
@ -46,9 +46,9 @@ class AbstractIndexerTest extends AbstractUnitTestCase
|
|||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var ObjectManagerInterface
|
||||
* @var DataProcessorService
|
||||
*/
|
||||
protected $objectManager;
|
||||
protected $dataProcessorService;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
|
@ -56,14 +56,15 @@ class AbstractIndexerTest extends AbstractUnitTestCase
|
|||
|
||||
$this->configuration = $this->getMockBuilder(ConfigurationContainerInterface::class)->getMock();
|
||||
$this->connection = $this->getMockBuilder(ConnectionInterface::class)->getMock();
|
||||
$this->objectManager = $this->getMockBuilder(ObjectManagerInterface::class)->getMock();
|
||||
|
||||
$this->dataProcessorService = $this->getMockBuilder(DataProcessorService::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->subject = $this->getMockForAbstractClass(AbstractIndexer::class, [
|
||||
$this->connection,
|
||||
$this->configuration
|
||||
]);
|
||||
$this->inject($this->subject, 'objectManager', $this->objectManager);
|
||||
$this->inject($this->subject, 'dataProcessorService', $this->dataProcessorService);
|
||||
$this->subject->injectLogger($this->getMockedLogger());
|
||||
$this->subject->setIdentifier('testTable');
|
||||
$this->subject->expects($this->any())
|
||||
|
@ -82,12 +83,30 @@ class AbstractIndexerTest extends AbstractUnitTestCase
|
|||
$expectedRecord['new_test_field2'] = 'test' . PHP_EOL . 'test';
|
||||
$expectedRecord['search_abstract'] = '';
|
||||
|
||||
$this->objectManager->expects($this->any())
|
||||
->method('get')
|
||||
->with(CopyToProcessor::class)
|
||||
->willReturn(new CopyToProcessor());
|
||||
$this->dataProcessorService->expects($this->any())
|
||||
->method('executeDataProcessor')
|
||||
->withConsecutive(
|
||||
[
|
||||
[
|
||||
'_typoScriptNodeValue' => CopyToProcessor::class,
|
||||
'to' => 'new_test_field',
|
||||
],
|
||||
$record,
|
||||
],
|
||||
[
|
||||
[
|
||||
'_typoScriptNodeValue' => CopyToProcessor::class,
|
||||
'to' => 'new_test_field2',
|
||||
],
|
||||
array_merge($record, ['new_test_field' => 'test']),
|
||||
]
|
||||
)
|
||||
->will($this->onConsecutiveCalls(
|
||||
array_merge($record, ['new_test_field' => 'test']),
|
||||
$expectedRecord
|
||||
));
|
||||
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->withConsecutive(['indexing.testTable.dataProcessing'], ['indexing.testTable.abstractFields'])
|
||||
->will($this->onConsecutiveCalls([
|
||||
|
|
110
Tests/Unit/Domain/Model/ResultItemTest.php
Normal file
110
Tests/Unit/Domain/Model/ResultItemTest.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Tests\Unit\Domain\Model;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 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\Domain\Model\ResultItem;
|
||||
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
|
||||
|
||||
class ResultItemTest extends AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function plainDataCanBeRetrieved()
|
||||
{
|
||||
$originalData = [
|
||||
'uid' => 10,
|
||||
'title' => 'Some title',
|
||||
];
|
||||
$expectedData = $originalData;
|
||||
|
||||
$subject = new ResultItem($originalData);
|
||||
$this->assertSame(
|
||||
$expectedData,
|
||||
$subject->getPlainData(),
|
||||
'Could not retrieve plain data from result item.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function dataCanBeRetrievedInArrayNotation()
|
||||
{
|
||||
$originalData = [
|
||||
'uid' => 10,
|
||||
'title' => 'Some title',
|
||||
];
|
||||
$expectedData = $originalData;
|
||||
|
||||
$subject = new ResultItem($originalData);
|
||||
$this->assertSame(
|
||||
$originalData['title'],
|
||||
$subject['title'],
|
||||
'Could not retrieve title in array notation.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function existenceOfDataCanBeChecked()
|
||||
{
|
||||
$originalData = [
|
||||
'uid' => 10,
|
||||
'title' => 'Some title',
|
||||
];
|
||||
|
||||
$subject = new ResultItem($originalData);
|
||||
$this->assertTrue(isset($subject['title']), 'Could not determine that title exists.');
|
||||
$this->assertFalse(isset($subject['title2']), 'Could not determine that title2 does not exists.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function dataCanNotBeChanged()
|
||||
{
|
||||
$originalData = [
|
||||
'uid' => 10,
|
||||
'title' => 'Some title',
|
||||
];
|
||||
|
||||
$subject = new ResultItem($originalData);
|
||||
$this->expectException(\BadMethodCallException::class);
|
||||
$subject['title'] = 'New Title';
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function dataCanNotBeRemoved()
|
||||
{
|
||||
$originalData = [
|
||||
'uid' => 10,
|
||||
'title' => 'Some title',
|
||||
];
|
||||
|
||||
$subject = new ResultItem($originalData);
|
||||
$this->expectException(\BadMethodCallException::class);
|
||||
unset($subject['title']);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,10 @@ namespace Codappix\SearchCore\Tests\Unit\Domain\Model;
|
|||
* 02110-1301, USA.
|
||||
*/
|
||||
|
||||
use Codappix\SearchCore\Connection\ConnectionInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\Domain\Model\SearchRequest;
|
||||
use Codappix\SearchCore\Domain\Search\SearchService;
|
||||
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
|
||||
|
||||
class SearchRequestTest extends AbstractUnitTestCase
|
||||
|
@ -31,12 +34,12 @@ class SearchRequestTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function emptyFilterWillNotBeSet(array $filter)
|
||||
{
|
||||
$searchRequest = new SearchRequest();
|
||||
$searchRequest->setFilter($filter);
|
||||
$subject = new SearchRequest();
|
||||
$subject->setFilter($filter);
|
||||
|
||||
$this->assertSame(
|
||||
[],
|
||||
$searchRequest->getFilter(),
|
||||
$subject->getFilter(),
|
||||
'Empty filter were set, even if they should not.'
|
||||
);
|
||||
}
|
||||
|
@ -68,13 +71,67 @@ class SearchRequestTest extends AbstractUnitTestCase
|
|||
public function filterIsSet()
|
||||
{
|
||||
$filter = ['someField' => 'someValue'];
|
||||
$searchRequest = new SearchRequest();
|
||||
$searchRequest->setFilter($filter);
|
||||
$subject = new SearchRequest();
|
||||
$subject->setFilter($filter);
|
||||
|
||||
$this->assertSame(
|
||||
$filter,
|
||||
$searchRequest->getFilter(),
|
||||
$subject->getFilter(),
|
||||
'Filter was not set.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function exceptionIsThrownIfSearchServiceWasNotSet()
|
||||
{
|
||||
$subject = new SearchRequest();
|
||||
$subject->setConnection($this->getMockBuilder(ConnectionInterface::class)->getMock());
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$subject->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function exceptionIsThrownIfConnectionWasNotSet()
|
||||
{
|
||||
$subject = new SearchRequest();
|
||||
$subject->setSearchService(
|
||||
$this->getMockBuilder(SearchService::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
);
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$subject->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function executionMakesUseOfProvidedConnectionAndSearchService()
|
||||
{
|
||||
$searchServiceMock = $this->getMockBuilder(SearchService::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$connectionMock = $this->getMockBuilder(ConnectionInterface::class)
|
||||
->getMock();
|
||||
$searchResultMock = $this->getMockBuilder(SearchResultInterface::class)
|
||||
->getMock();
|
||||
|
||||
$subject = new SearchRequest();
|
||||
$subject->setSearchService($searchServiceMock);
|
||||
$subject->setConnection($connectionMock);
|
||||
|
||||
$connectionMock->expects($this->once())
|
||||
->method('search')
|
||||
->with($subject)
|
||||
->willReturn($searchResultMock);
|
||||
$searchServiceMock->expects($this->once())
|
||||
->method('processResult')
|
||||
->with($searchResultMock);
|
||||
|
||||
$subject->execute();
|
||||
}
|
||||
}
|
||||
|
|
100
Tests/Unit/Domain/Model/SearchResultTest.php
Normal file
100
Tests/Unit/Domain/Model/SearchResultTest.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
namespace Codappix\SearchCore\Tests\Unit\Domain\Model;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 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\ResultItemInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\Domain\Model\SearchResult;
|
||||
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
|
||||
|
||||
class SearchResultTest extends AbstractUnitTestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function countIsRetrievedFromOriginalResult()
|
||||
{
|
||||
$originalSearchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
$originalSearchResultMock->expects($this->once())->method('count');
|
||||
|
||||
$subject = new SearchResult($originalSearchResultMock, []);
|
||||
$subject->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function currentCountIsRetrievedFromOriginalResult()
|
||||
{
|
||||
$originalSearchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
$originalSearchResultMock->expects($this->once())->method('getCurrentCount');
|
||||
|
||||
$subject = new SearchResult($originalSearchResultMock, []);
|
||||
$subject->getCurrentCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function facetsAreRetrievedFromOriginalResult()
|
||||
{
|
||||
$originalSearchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
$originalSearchResultMock->expects($this->once())->method('getFacets');
|
||||
|
||||
$subject = new SearchResult($originalSearchResultMock, []);
|
||||
$subject->getFacets();
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function resultItemsCanBeRetrieved()
|
||||
{
|
||||
$originalSearchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
$data = [
|
||||
[
|
||||
'uid' => 10,
|
||||
'title' => 'Some Title',
|
||||
],
|
||||
[
|
||||
'uid' => 11,
|
||||
'title' => 'Some Title 2',
|
||||
],
|
||||
[
|
||||
'uid' => 12,
|
||||
'title' => 'Some Title 3',
|
||||
],
|
||||
];
|
||||
|
||||
$subject = new SearchResult($originalSearchResultMock, $data);
|
||||
$resultItems = $subject->getResults();
|
||||
|
||||
$this->assertCount(3, $resultItems);
|
||||
|
||||
$this->assertSame($data[0]['uid'], $resultItems[0]['uid']);
|
||||
$this->assertSame($data[1]['uid'], $resultItems[1]['uid']);
|
||||
$this->assertSame($data[2]['uid'], $resultItems[2]['uid']);
|
||||
|
||||
$this->assertInstanceOf(ResultItemInterface::class, $resultItems[0]);
|
||||
$this->assertInstanceOf(ResultItemInterface::class, $resultItems[1]);
|
||||
$this->assertInstanceOf(ResultItemInterface::class, $resultItems[2]);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
namespace Copyright\SearchCore\Tests\Unit\Domain\Search;
|
||||
namespace Codappix\SearchCore\Tests\Unit\Domain\Search;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2017 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
|
@ -23,7 +23,10 @@ namespace Copyright\SearchCore\Tests\Unit\Domain\Search;
|
|||
use Codappix\SearchCore\Configuration\ConfigurationContainerInterface;
|
||||
use Codappix\SearchCore\Configuration\InvalidArgumentException;
|
||||
use Codappix\SearchCore\Connection\ConnectionInterface;
|
||||
use Codappix\SearchCore\Connection\SearchResultInterface;
|
||||
use Codappix\SearchCore\DataProcessing\Service as DataProcessorService;
|
||||
use Codappix\SearchCore\Domain\Model\SearchRequest;
|
||||
use Codappix\SearchCore\Domain\Model\SearchResult;
|
||||
use Codappix\SearchCore\Domain\Search\SearchService;
|
||||
use Codappix\SearchCore\Tests\Unit\AbstractUnitTestCase;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManagerInterface;
|
||||
|
@ -35,10 +38,38 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
protected $subject;
|
||||
|
||||
/**
|
||||
* @var SearchResultInterface
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* @var ConnectionInterface
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* @var ConfigurationContainerInterface
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* @var ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @var DataProcessorService
|
||||
*/
|
||||
protected $dataProcessorService;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->result = $this->getMockBuilder(SearchResultInterface::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->connection = $this->getMockBuilder(ConnectionInterface::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
@ -48,11 +79,15 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
$this->objectManager = $this->getMockBuilder(ObjectManagerInterface::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->dataProcessorService = $this->getMockBuilder(DataProcessorService::class)
|
||||
->setConstructorArgs([$this->objectManager])
|
||||
->getMock();
|
||||
|
||||
$this->subject = new SearchService(
|
||||
$this->connection,
|
||||
$this->configuration,
|
||||
$this->objectManager
|
||||
$this->objectManager,
|
||||
$this->dataProcessorService
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -61,19 +96,19 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function sizeIsAddedFromConfiguration()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(45, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->will($this->throwException(new InvalidArgumentException));
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->with($this->callback(function ($searchRequest) {
|
||||
return $searchRequest->getLimit() === 45;
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$this->subject->search($searchRequest);
|
||||
|
@ -84,19 +119,19 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function defaultSizeIsAddedIfNothingIsConfigured()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->will($this->throwException(new InvalidArgumentException));
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->with($this->callback(function ($searchRequest) {
|
||||
return $searchRequest->getLimit() === 10;
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$this->subject->search($searchRequest);
|
||||
|
@ -107,20 +142,23 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function configuredFilterAreAddedToRequestWithoutAnyFilter()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->willReturn(['property' => 'something']);
|
||||
->will($this->onConsecutiveCalls(
|
||||
['property' => 'something'],
|
||||
$this->throwException(new InvalidArgumentException)
|
||||
));
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->with($this->callback(function ($searchRequest) {
|
||||
return $searchRequest->getFilter() === ['property' => 'something'];
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$this->subject->search($searchRequest);
|
||||
|
@ -131,14 +169,16 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function configuredFilterAreAddedToRequestWithExistingFilter()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->willReturn(['property' => 'something']);
|
||||
->will($this->onConsecutiveCalls(
|
||||
['property' => 'something'],
|
||||
$this->throwException(new InvalidArgumentException)
|
||||
));
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
|
@ -147,7 +187,8 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
'anotherProperty' => 'anything',
|
||||
'property' => 'something',
|
||||
];
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$searchRequest->setFilter(['anotherProperty' => 'anything']);
|
||||
|
@ -159,20 +200,20 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function nonConfiguredFilterIsNotChangingRequestWithExistingFilter()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->will($this->throwException(new InvalidArgumentException));
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->with($this->callback(function ($searchRequest) {
|
||||
return $searchRequest->getFilter() === ['anotherProperty' => 'anything'];
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$searchRequest->setFilter(['anotherProperty' => 'anything']);
|
||||
|
@ -184,23 +225,93 @@ class SearchServiceTest extends AbstractUnitTestCase
|
|||
*/
|
||||
public function emptyConfiguredFilterIsNotChangingRequestWithExistingFilter()
|
||||
{
|
||||
$this->configuration->expects($this->exactly(2))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->exactly(1))
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->with('searching.filter')
|
||||
->willReturn(['anotherProperty' => '']);
|
||||
->will($this->onConsecutiveCalls(
|
||||
['anotherProperty' => ''],
|
||||
$this->throwException(new InvalidArgumentException)
|
||||
));
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->with($this->callback(function ($searchRequest) {
|
||||
return $searchRequest->getFilter() === ['anotherProperty' => 'anything'];
|
||||
}));
|
||||
}))
|
||||
->willReturn($this->getMockBuilder(SearchResultInterface::class)->getMock());
|
||||
|
||||
$searchRequest = new SearchRequest('SearchWord');
|
||||
$searchRequest->setFilter(['anotherProperty' => 'anything']);
|
||||
$this->subject->search($searchRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function originalSearchResultIsReturnedIfNoDataProcessorIsConfigured()
|
||||
{
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->will($this->throwException(new InvalidArgumentException));
|
||||
|
||||
$searchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->willReturn($searchResultMock);
|
||||
|
||||
$this->dataProcessorService->expects($this->never())->method('executeDataProcessor');
|
||||
|
||||
$searchRequest = new SearchRequest('');
|
||||
$this->assertSame($searchResultMock, $this->subject->search($searchRequest), 'Did not get created result without applied data processing');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function configuredDataProcessorsAreExecutedOnSearchResult()
|
||||
{
|
||||
$this->configuration->expects($this->any())
|
||||
->method('getIfExists')
|
||||
->withConsecutive(['searching.size'], ['searching.facets'])
|
||||
->will($this->onConsecutiveCalls(null, null));
|
||||
$this->configuration->expects($this->any())
|
||||
->method('get')
|
||||
->will($this->onConsecutiveCalls(
|
||||
$this->throwException(new InvalidArgumentException),
|
||||
['SomeProcessorClass']
|
||||
));
|
||||
|
||||
$searchResultMock = $this->getMockBuilder(SearchResultInterface::class)->getMock();
|
||||
$searchResult = new SearchResult($searchResultMock, [['field 1' => 'value 1']]);
|
||||
|
||||
$this->connection->expects($this->once())
|
||||
->method('search')
|
||||
->willReturn($searchResult);
|
||||
|
||||
$this->dataProcessorService->expects($this->once())
|
||||
->method('executeDataProcessor')
|
||||
->with('SomeProcessorClass', ['field 1' => 'value 1'])
|
||||
->willReturn([
|
||||
'field 1' => 'value 1',
|
||||
'field 2' => 'value 2',
|
||||
]);
|
||||
|
||||
$this->objectManager->expects($this->once())
|
||||
->method('get')
|
||||
->with(SearchResult::class, $searchResult, [
|
||||
['field 1' => 'value 1', 'field 2' => 'value 2']
|
||||
])
|
||||
->willReturn($searchResultMock);
|
||||
|
||||
$searchRequest = new SearchRequest('');
|
||||
$this->assertSame($searchResultMock, $this->subject->search($searchRequest), 'Did not get created result with applied data processing');
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue