diff --git a/Query/QueryAwareTrait.php b/Query/QueryAwareTrait.php deleted file mode 100644 index 262fdd694193f93565cd366245c043cf1ec07c72..0000000000000000000000000000000000000000 --- a/Query/QueryAwareTrait.php +++ /dev/null @@ -1,138 +0,0 @@ -<?php - -/* - * This file is part of the ONGR package. - * - * (c) NFQ Technologies UAB <info@nfq.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace ONGR\ElasticsearchBundle\DSL\Query; - -use ONGR\ElasticsearchBundle\DSL\Bool\Bool; -use ONGR\ElasticsearchBundle\DSL\BuilderInterface; - -/** - * Provides query container functionality to any class. - */ -trait QueryAwareTrait -{ - /** - * @var BuilderInterface[] - */ - private $queries = []; - - /** - * @var \ONGR\ElasticsearchBundle\DSL\Bool\Bool - */ - private $boolQuery; - - /** - * @param BuilderInterface $query - * @param string $boolType - * - * @return $this - */ - public function addQuery(BuilderInterface $query, $boolType = Bool::MUST) - { - if ($boolType !== Bool::MUST || $this->boolQuery !== null) { - $this->getBoolQuery()->addToBool($query, $boolType); - } else { - $this->queries[$query->getType()] = $query; - } - - return $this; - } - - /** - * Returns Bool query. Creates new instance if there is not initiated. - * - * @return \ONGR\ElasticsearchBundle\DSL\Bool\Bool - */ - public function getBoolQuery() - { - if (!$this->boolQuery) { - $this->boolQuery = new Bool(); - } - - return $this->boolQuery; - } - - /** - * @param array $params Example values: - * - minimum_should_match => 1 - * - boost => 1. - */ - public function setBoolQueryParameters(array $params) - { - $this->getBoolQuery()->setParameters($params); - } - - /** - * Checks if there is added specific query. - * - * @param string $type Query type. - * - * @return bool - */ - public function hasQuery($type) - { - return array_key_exists($type, $this->queries); - } - - /** - * Removes specific query. - * - * @param string $key - */ - public function removeQuery($key) - { - if ($this->hasQuery($key)) { - unset($this->queries[$key]); - } - } - - /** - * Completely resets query. - */ - public function destroyQuery() - { - $this->queries = []; - $this->boolQuery = null; - } - - /** - * Return all queries. - * - * @return array - */ - public function getQueries() - { - return $this->queries; - } - - /** - * Aggregates all queries to array. - * - * @return array - */ - public function processQueries() - { - if ($this->boolQuery || count($this->getQueries()) > 1) { - $bool = $this->getBoolQuery(); - foreach ($this->getQueries() as $query) { - $bool->addToBool($query); - } - - return ['query' => [$bool->getType() => $bool->toArray()]]; - } elseif (count($this->getQueries()) == 1) { - $query = array_values($this->getQueries())[0]; - - return ['query' => [$query->getType() => $query->toArray()]]; - } - - return []; - } -} diff --git a/Search.php b/Search.php index 5e9bd482cd94b80d724d0cbe93b0ee23b071c3e6..cd626aed5a27cf95f55f3128f910624129ff5126 100644 --- a/Search.php +++ b/Search.php @@ -18,39 +18,24 @@ use ONGR\ElasticsearchBundle\DSL\Highlight\Highlight; use ONGR\ElasticsearchBundle\DSL\Query\FilteredQuery; use ONGR\ElasticsearchBundle\DSL\Query\Query; use ONGR\ElasticsearchBundle\DSL\Query\QueryAwareTrait; +use ONGR\ElasticsearchBundle\DSL\SearchEndpoint\SearchEndpointFactory; +use ONGR\ElasticsearchBundle\DSL\SearchEndpoint\SearchEndpointInterface; use ONGR\ElasticsearchBundle\DSL\Sort\AbstractSort; use ONGR\ElasticsearchBundle\DSL\Sort\Sorts; use ONGR\ElasticsearchBundle\DSL\Suggester\AbstractSuggester; +use ONGR\ElasticsearchBundle\Serializer\Normalizer\CustomReferencedNormalizer; +use ONGR\ElasticsearchBundle\Serializer\Normalizer\ReferencedNormalizer; +use ONGR\ElasticsearchBundle\Serializer\OrderedSerializer; +use Symfony\Component\Serializer\Normalizer\CustomNormalizer; +use Symfony\Component\Serializer\Serializer; /** * Search object that can be executed by a manager. */ class Search { - use QueryAwareTrait; - const SCROLL_DURATION = '5m'; - /** - * @var array - */ - private $boolQueryParams; - - /** - * @var BuilderInterface - */ - private $filters; - - /** - * @var BuilderInterface Filters collection. - */ - private $postFilters; - - /** - * @var array - */ - private $boolFilterParams; - /** * @var int */ @@ -61,11 +46,6 @@ class Search */ private $from; - /** - * @var Sorts - */ - private $sorts; - /** * @var string|null */ @@ -86,16 +66,6 @@ class Search */ private $scriptFields; - /** - * @var NamedBuilderBag - */ - private $suggesters; - - /** - * @var Highlight - */ - private $highlight; - /** * @var string */ @@ -111,11 +81,6 @@ class Search */ private $stats; - /** - * @var NamedBuilderBag - */ - private $aggregations; - /** * @var string[] */ @@ -127,364 +92,414 @@ class Search private $minScore; /** - * @return float + * @var Serializer */ - public function getMinScore() + private $serializer; + + /** + * @var SearchEndpointInterface[] + */ + private $endpoints = []; + + /** + * Initializes serializer. + */ + public function __construct() { - return $this->minScore; + $this->serializer = new OrderedSerializer( + [ + new CustomReferencedNormalizer(), + new CustomNormalizer(), + ] + ); } /** - * Set min score. + * Adds query to search. * - * @param float $minScore + * @param BuilderInterface $query + * @param string $boolType * - * @return $this + * @return Search */ - public function setMinScore($minScore) + public function addQuery(BuilderInterface $query, $boolType = '') { - $this->minScore = $minScore; + $this + ->getEndpoint('query') + ->addBuilder($query, ['bool_type' => $boolType]); return $this; } /** - * Set offset. + * Sets parameters for bool query. * - * @param int $from + * @param array $params Example values: + * - minimum_should_match => 1 + * - boost => 1. * - * @return $this + * @return Search */ - public function setFrom($from) + public function setBoolQueryParameters(array $params) { - $this->from = $from; + $this + ->getEndpoint('query') + ->setParameters($params); return $this; } /** - * Set maximum number of results. + * Returns contained query. * - * @param int $size + * @return BuilderInterface + */ + public function getQuery() + { + return $this + ->getEndpoint('query') + ->getBuilder(); + } + + /** + * Destroys query part. * - * @return $this + * @return Search */ - public function setSize($size) + public function destroyQuery() { - $this->size = $size; + $this->destroyEndpoint('query'); return $this; } /** - * Add sort. + * Adds a filter to search. * - * @param AbstractSort $sort + * @param BuilderInterface $filter Filter. + * @param string $boolType Possible boolType values: + * - must + * - must_not + * - should. * - * @return $this + * @return Search */ - public function addSort($sort) + public function addFilter(BuilderInterface $filter, $boolType = '') { - if ($this->sorts === null) { - $this->sorts = new Sorts(); - } + $this->getEndpoint('query'); - $this->sorts->addSort($sort); + $this + ->getEndpoint('filter') + ->addBuilder($filter, ['bool_type' => $boolType]); return $this; } /** - * Set source. + * Returns currently contained filters. * - * @param array|bool|string $source - * - * @return $this + * @return BuilderInterface */ - public function setSource($source) + public function getFilters() { - $this->source = $source; - - return $this; + return $this + ->getEndpoint('filter') + ->getBuilder(); } /** - * Set fields. + * Sets bool filter parameters. * - * @param array $fields + * @param array $params Possible values: + * _cache => true + * false. * - * @return $this + * @return Search */ - public function setFields(array $fields) + public function setBoolFilterParameters($params) { - $this->fields = $fields; + $this + ->getEndpoint('filter') + ->setParameters($params); return $this; } /** - * Set script fields. - * - * @param array $scriptFields - * - * @return $this + * Destroys filter part. */ - public function setScriptFields($scriptFields) + public function destroyFilters() { - $this->scriptFields = $scriptFields; - - return $this; + $this->destroyEndpoint('filter'); } /** + * Adds a post filter to search. + * * @param BuilderInterface $postFilter Post filter. * @param string $boolType Possible boolType values: * - must * - must_not * - should. * - * @return $this + * @return Search */ - public function addPostFilter(BuilderInterface $postFilter, $boolType = 'must') + public function addPostFilter(BuilderInterface $postFilter, $boolType = '') { - if ($this->postFilters === null) { - $this->postFilters = new Bool(); - } - - $this->postFilters->addToBool($postFilter, $boolType); + $this + ->getEndpoint('post_filter') + ->addBuilder($postFilter, ['bool_type' => $boolType]); return $this; } /** - * Sets highlight. - * - * @param Highlight $highlight + * Returns all contained post filters. * - * @return $this + * @return BuilderInterface */ - public function setHighlight($highlight) + public function getPostFilters() { - $this->highlight = $highlight; - - return $this; + return $this + ->getEndpoint('post_filter') + ->getBuilder(); } /** - * Set search type. + * Sets bool post filter parameters. * - * @param string $searchType + * @param array $params Possible values: + * _cache => true + * false. * - * @return $this + * @return Search */ - public function setSearchType($searchType) + public function setBoolPostFilterParameters($params) { - $this->searchType = $searchType; + $this + ->getEndpoint('post_filter') + ->setParameters($params); return $this; } /** - * Set explain. + * Returns min score value. * - * @param bool $explain - * - * @return $this + * @return float */ - public function setExplain($explain) + public function getMinScore() { - $this->explain = $explain; - - return $this; + return $this->minScore; } /** - * Set stats. + * Exclude documents which have a _score less than the minimum specified. * - * @param array $stats + * @param float $minScore * - * @return $this + * @return Search */ - public function setStats($stats) + public function setMinScore($minScore) { - $this->stats = $stats; + $this->minScore = $minScore; return $this; } /** - * Setter for scroll duration, effectively setting if search is scrolled or not. + * Paginate results from. * - * @param string|null $duration + * @param int $from * * @return Search */ - public function setScroll($duration = self::SCROLL_DURATION) + public function setFrom($from) { - $this->scrollDuration = $duration; + $this->from = $from; return $this; } /** - * Setter for preference. + * Returns results offset value. * - * Controls which shard replicas to execute the search request on. + * @return int + */ + public function getFrom() + { + return $this->from; + } + + /** + * Set maximum number of results. * - * @param mixed $preferenceParams Possible values: - * _primary - * _primary_first - * _local - * _only_node:xyz (xyz - node id) - * _prefer_node:xyz (xyz - node id) - * _shards:2,3 (2 and 3 specified shards) - * custom value - * string[] combination of params. + * @param int $size * - * @return Search $this + * @return Search */ - public function setPreference($preferenceParams) + public function setSize($size) { - if (is_string($preferenceParams)) { - $this->preference[] = $preferenceParams; - } - - if (is_array($preferenceParams) && !empty($preferenceParams)) { - $this->preference = $preferenceParams; - } + $this->size = $size; return $this; } /** - * Returns preference params as string. + * Returns maximum number of results query can request. * - * @return string + * @return int */ - protected function getPreference() + public function getSize() { - return implode(';', $this->preference); + return $this->size; } /** - * Returns scroll duration. + * Adds sort to search. + * + * @param AbstractSort $sort * - * @return null|string + * @return Search */ - public function getScroll() + public function addSort(AbstractSort $sort) { - return $this->scrollDuration; + $this + ->getEndpoint('sort') + ->addBuilder($sort); + + return $this; } /** - * @param AbstractAggregation $agg + * Returns currectly contained sorts object. * - * @return $this + * @return Sorts */ - public function addAggregation($agg) + public function getSorts() { - if ($this->aggregations === null) { - $this->aggregations = new NamedBuilderBag(); - } - $this->aggregations->add($agg); - - return $this; + return $this + ->getEndpoint('sort') + ->getBuilder(); } /** - * @param BuilderInterface $filter Filter. - * @param string $boolType Possible boolType values: - * - must - * - must_not - * - should. + * Allows to control how the _source field is returned with every hit. * - * @return $this + * @param array|bool|string $source + * + * @return Search */ - public function addFilter(BuilderInterface $filter, $boolType = 'must') + public function setSource($source) { - if ($this->filters === null) { - $this->filters = new Bool(); - } - - $this->filters->addToBool($filter, $boolType); + $this->source = $source; return $this; } /** - * @param array $params Possible values: - * _cache => true - * false. + * Returns source value. + * + * @return array|bool|string */ - public function setBoolFilterParameters($params) + public function getSource() { - $this->boolFilterParams = $params; + return $this->source; } /** - * @param AbstractSuggester $suggester + * Allows to selectively load specific stored fields for each document represented by a search hit. + * + * @param array $fields * * @return Search */ - public function addSuggester(AbstractSuggester $suggester) + public function setFields(array $fields) { - if ($this->suggesters === null) { - $this->suggesters = new NamedBuilderBag(); - } - $this->suggesters->add($suggester); + $this->fields = $fields; return $this; } /** - * Returns query url parameters. + * Returns field value. * * @return array */ - public function getQueryParams() + public function getFields() { - $array = []; - - if ($this->scrollDuration !== null) { - $array['scroll'] = $this->scrollDuration; - } + return $this->fields; + } - if ($this->searchType !== null) { - $array['search_type'] = $this->searchType; - } + /** + * Allows to return a script evaluation (based on different fields) for each hit. + * + * @param array $scriptFields + * + * @return Search + */ + public function setScriptFields($scriptFields) + { + $this->scriptFields = $scriptFields; - if ($this->preference !== null) { - $array['preference'] = $this->getPreference(); - } + return $this; + } - return $array; + /** + * Returns containing script fields. + * + * @return array + */ + public function getScriptFields() + { + return $this->scriptFields; } /** - * @return NamedBuilderBag + * Allows to highlight search results on one or more fields. + * + * @param Highlight $highlight + * + * @return Search */ - public function getAggregations() + public function setHighlight($highlight) { - return $this->aggregations; + $this + ->getEndpoint('highlight') + ->addBuilder($highlight); + + return $this; } /** - * @return array + * Returns containing highlight object. + * + * @return Highlight */ - public function getBoolFilterParameters() + public function getHighlight() { - return $this->boolFilterParams; + return $this + ->getEndpoint('highlight') + ->getBuilder(); } /** - * @return array + * Sets explain property in request body search. + * + * @param bool $explain + * + * @return Search */ - public function getBoolQueryParameters() + public function setExplain($explain) { - return $this->boolQueryParams; + $this->explain = $explain; + + return $this; } /** + * Returns if explain property is set in request body search. + * * @return bool */ public function isExplain() @@ -493,107 +508,187 @@ class Search } /** - * @return array + * Sets a stats group. + * + * @param array $stats + * + * @return Search */ - public function getFields() + public function setStats($stats) { - return $this->fields; + $this->stats = $stats; + + return $this; } /** - * @return BuilderInterface[] + * Returns a stats group. + * + * @return array */ - public function getFilters() + public function getStats() { - return $this->filters; + return $this->stats; } /** - * @return int + * Adds aggregation into search. + * + * @param AbstractAggregation $aggregation + * + * @return Search */ - public function getFrom() + public function addAggregation(AbstractAggregation $aggregation) { - return $this->from; + $this + ->getEndpoint('aggregations') + ->addBuilder($aggregation); + + return $this; } /** - * @return Highlight + * Returns contained aggregations. + * + * @return AbstractAggregation[] */ - public function getHighlight() + public function getAggregations() { - return $this->highlight; + return $this + ->getEndpoint('aggregations') + ->getBuilder(); } /** - * @return BuilderInterface + * Adds suggester to search. + * + * @param AbstractSuggester $suggester + * + * @return Search */ - public function getPostFilters() + public function addSuggester(AbstractSuggester $suggester) { - return $this->postFilters; + $this + ->getEndpoint('suggest') + ->addBuilder($suggester); + + return $this; } /** - * @return array + * Returns all contained suggester's. + * + * @return AbstractSuggester[] */ - public function getScriptFields() + public function getSuggesters() { - return $this->scriptFields; + return $this + ->getEndpoint('suggest') + ->getBuilder(); } /** - * @return null|string + * Setter for scroll duration, effectively setting if search is scrolled or not. + * + * @param string|null $duration + * + * @return Search */ - public function getScrollDuration() + public function setScroll($duration = self::SCROLL_DURATION) { - return $this->scrollDuration; + $this->scrollDuration = $duration; + + return $this; } /** - * @return string + * Returns scroll duration. + * + * @return string|null */ - public function getSearchType() + public function getScroll() { - return $this->searchType; + return $this->scrollDuration; } /** - * @return int + * Set search type. + * + * @param string $searchType + * + * @return Search */ - public function getSize() + public function setSearchType($searchType) { - return $this->size; + $this->searchType = $searchType; + + return $this; } /** - * @return Sorts + * Returns search type used. + * + * @return string */ - public function getSorts() + public function getSearchType() { - return $this->sorts; + return $this->searchType; } /** - * @return array|bool|string + * Setter for preference. + * + * Controls which shard replicas to execute the search request on. + * + * @param mixed $preferenceParams Possible values: + * _primary + * _primary_first + * _local + * _only_node:xyz (xyz - node id) + * _prefer_node:xyz (xyz - node id) + * _shards:2,3 (2 and 3 specified shards) + * custom value + * string[] combination of params. + * + * @return Search */ - public function getSource() + public function setPreference($preferenceParams) { - return $this->source; + if (is_string($preferenceParams)) { + $this->preference[] = $preferenceParams; + } + + if (is_array($preferenceParams) && !empty($preferenceParams)) { + $this->preference = $preferenceParams; + } + + return $this; } /** - * @return array + * Returns preference params as string. + * + * @return string */ - public function getStats() + public function getPreference() { - return $this->stats; + return $this->preference ? implode(';', $this->preference) : null; } /** - * @return NamedBuilderBag + * Returns query url parameters. + * + * @return array */ - public function getSuggesters() + public function getQueryParams() { - return $this->suggesters; + return array_filter( + [ + 'scroll' => $this->getScroll(), + 'search_type' => $this->getSearchType(), + 'preference' => $this->getPreference(), + ] + ); } /** @@ -601,29 +696,7 @@ class Search */ public function toArray() { - /* - * First we check if there are some filter to add to filtered query. - * For now we use match all, but its gonna be changed. - */ - - if ($this->filters !== null) { - $filteredQuery = new FilteredQuery(); - $filteredQuery->setFilter($this->filters); - $this->addQuery($filteredQuery); - } - - $output = $this->processQueries(); - - if ($this->postFilters !== null) { - $postFilter = new PostFilter(); - $postFilter->setFilter($this->postFilters); - - $output[$postFilter->getType()] = $postFilter->toArray(); - } - - if ($this->highlight !== null) { - $output['highlight'] = $this->highlight->toArray(); - } + $output = array_filter($this->serializer->normalize($this->endpoints)); $params = [ 'from' => 'from', @@ -633,6 +706,7 @@ class Search 'explain' => 'explain', 'stats' => 'stats', 'minScore' => 'min_score', + 'source' => '_source', ]; foreach ($params as $field => $param) { @@ -641,36 +715,32 @@ class Search } } - if ($this->sorts && $this->sorts->isRelevant()) { - $output[$this->sorts->getType()] = $this->sorts->toArray(); - } - - if ($this->source !== null) { - $output['_source'] = $this->source; - } - - if ($this->aggregations !== null) { - $aggregationsOutput = []; - foreach ($this->aggregations->all() as $aggregation) { - $aggregationsOutput = array_merge($aggregationsOutput, $aggregation->toArray()); - } + return $output; + } - if (!empty($aggregationsOutput)) { - $output['aggregations'] = $aggregationsOutput; - } + /** + * Returns endpoint instance. + * + * @param string $type Endpoint type. + * + * @return SearchEndpointInterface + */ + private function getEndpoint($type) + { + if (!array_key_exists($type, $this->endpoints)) { + $this->endpoints[$type] = SearchEndpointFactory::get($type); } - if ($this->suggesters !== null) { - $suggestersOutput = []; - foreach ($this->suggesters->all() as $suggester) { - $suggestersOutput = array_merge($suggestersOutput, $suggester->toArray()); - } - - if (!empty($suggestersOutput)) { - $output['suggest'] = $suggestersOutput; - } - } + return $this->endpoints[$type]; + } - return $output; + /** + * Destroys search endpoint. + * + * @param string $type Endpoint type. + */ + private function destroyEndpoint($type) + { + unset($this->endpoints[$type]); } }