vendor/ruflin/elastica/src/Search.php line 326

Open in your IDE?
  1. <?php
  2. namespace Elastica;
  3. use Elastica\Exception\InvalidException;
  4. use Elastica\Exception\ResponseException;
  5. use Elastica\Query\AbstractQuery;
  6. use Elastica\ResultSet\BuilderInterface;
  7. use Elastica\ResultSet\DefaultBuilder;
  8. use Elastica\Suggest\AbstractSuggest;
  9. /**
  10.  * Elastica search object.
  11.  *
  12.  * @author   Nicolas Ruflin <spam@ruflin.com>
  13.  */
  14. class Search
  15. {
  16.     /*
  17.      * Options
  18.      */
  19.     public const OPTION_SEARCH_TYPE 'search_type';
  20.     public const OPTION_ROUTING 'routing';
  21.     public const OPTION_PREFERENCE 'preference';
  22.     public const OPTION_VERSION 'version';
  23.     public const OPTION_TIMEOUT 'timeout';
  24.     public const OPTION_FROM 'from';
  25.     public const OPTION_SIZE 'size';
  26.     public const OPTION_SCROLL 'scroll';
  27.     public const OPTION_SCROLL_ID 'scroll_id';
  28.     public const OPTION_QUERY_CACHE 'query_cache';
  29.     public const OPTION_TERMINATE_AFTER 'terminate_after';
  30.     public const OPTION_SHARD_REQUEST_CACHE 'request_cache';
  31.     public const OPTION_FILTER_PATH 'filter_path';
  32.     public const OPTION_TYPED_KEYS 'typed_keys';
  33.     /*
  34.      * Search types
  35.      */
  36.     public const OPTION_SEARCH_TYPE_DFS_QUERY_THEN_FETCH 'dfs_query_then_fetch';
  37.     public const OPTION_SEARCH_TYPE_QUERY_THEN_FETCH 'query_then_fetch';
  38.     public const OPTION_SEARCH_TYPE_SUGGEST 'suggest';
  39.     public const OPTION_SEARCH_IGNORE_UNAVAILABLE 'ignore_unavailable';
  40.     /**
  41.      * Array of indices names.
  42.      *
  43.      * @var string[]
  44.      */
  45.     protected $_indices = [];
  46.     /**
  47.      * @var Query
  48.      */
  49.     protected $_query;
  50.     /**
  51.      * @var array
  52.      */
  53.     protected $_options = [];
  54.     /**
  55.      * Client object.
  56.      *
  57.      * @var Client
  58.      */
  59.     protected $_client;
  60.     /**
  61.      * @var BuilderInterface|null
  62.      */
  63.     private $builder;
  64.     public function __construct(Client $client, ?BuilderInterface $builder null)
  65.     {
  66.         $this->_client $client;
  67.         $this->builder $builder ?: new DefaultBuilder();
  68.     }
  69.     /**
  70.      * Adds a index to the list.
  71.      *
  72.      * @param Index $index Index object or string
  73.      *
  74.      * @throws InvalidException
  75.      */
  76.     public function addIndex($index): self
  77.     {
  78.         if ($index instanceof Index) {
  79.             $index $index->getName();
  80.         } else {
  81.             \trigger_deprecation(
  82.                 'ruflin/elastica',
  83.                 '7.2.0',
  84.                 'Passing a string as 1st argument to "%s()" is deprecated, pass an Index instance or use "addIndexByName" instead. It will throw a %s in 8.0.',
  85.                 __METHOD__,
  86.                 \TypeError::class
  87.             );
  88.         }
  89.         if (!\is_scalar($index)) {
  90.             throw new InvalidException('Invalid param type');
  91.         }
  92.         return $this->addIndexByName((string) $index);
  93.     }
  94.     /**
  95.      * Adds an index to the list.
  96.      */
  97.     public function addIndexByName(string $index): self
  98.     {
  99.         $this->_indices[] = $index;
  100.         return $this;
  101.     }
  102.     /**
  103.      * Add array of indices at once.
  104.      *
  105.      * @param Index[] $indices
  106.      */
  107.     public function addIndices(array $indices = []): self
  108.     {
  109.         foreach ($indices as $index) {
  110.             if (\is_string($index)) {
  111.                 \trigger_deprecation(
  112.                     'ruflin/elastica',
  113.                     '7.2.0',
  114.                     'Passing a array of strings as 1st argument to "%s()" is deprecated, pass an array of Indexes or use "addIndicesByName" instead. It will throw a %s in 8.0.',
  115.                     __METHOD__,
  116.                     \TypeError::class
  117.                 );
  118.                 $this->addIndexByName($index);
  119.                 continue;
  120.             }
  121.             if (!$index instanceof Index) {
  122.                 throw new InvalidException('Invalid param type for addIndices(), expected Index[]');
  123.             }
  124.             $this->addIndex($index);
  125.         }
  126.         return $this;
  127.     }
  128.     /**
  129.      * @param string[] $indices
  130.      */
  131.     public function addIndicesByName(array $indices = []): self
  132.     {
  133.         foreach ($indices as $index) {
  134.             if (!\is_string($index)) {
  135.                 throw new InvalidException('Invalid param type for addIndicesByName(), expected string[]');
  136.             }
  137.             $this->addIndexByName($index);
  138.         }
  139.         return $this;
  140.     }
  141.     /**
  142.      * @param AbstractQuery|AbstractSuggest|array|Collapse|Query|string|Suggest $query
  143.      */
  144.     public function setQuery($query): self
  145.     {
  146.         $this->_query Query::create($query);
  147.         return $this;
  148.     }
  149.     /**
  150.      * @param mixed $value
  151.      */
  152.     public function setOption(string $key$value): self
  153.     {
  154.         $this->validateOption($key);
  155.         $this->_options[$key] = $value;
  156.         return $this;
  157.     }
  158.     public function setOptions(array $options): self
  159.     {
  160.         $this->clearOptions();
  161.         foreach ($options as $key => $value) {
  162.             $this->setOption($key$value);
  163.         }
  164.         return $this;
  165.     }
  166.     public function clearOptions(): self
  167.     {
  168.         $this->_options = [];
  169.         return $this;
  170.     }
  171.     /**
  172.      * @param mixed $value
  173.      */
  174.     public function addOption(string $key$value): self
  175.     {
  176.         $this->validateOption($key);
  177.         $this->_options[$key][] = $value;
  178.         return $this;
  179.     }
  180.     public function hasOption(string $key): bool
  181.     {
  182.         return isset($this->_options[$key]);
  183.     }
  184.     /**
  185.      * @throws InvalidException if the given key does not exists as an option
  186.      *
  187.      * @return mixed
  188.      */
  189.     public function getOption(string $key)
  190.     {
  191.         if (!$this->hasOption($key)) {
  192.             throw new InvalidException('Option '.$key.' does not exist');
  193.         }
  194.         return $this->_options[$key];
  195.     }
  196.     public function getOptions(): array
  197.     {
  198.         return $this->_options;
  199.     }
  200.     /**
  201.      * Return client object.
  202.      */
  203.     public function getClient(): Client
  204.     {
  205.         return $this->_client;
  206.     }
  207.     /**
  208.      * Return array of indices names.
  209.      *
  210.      * @return string[]
  211.      */
  212.     public function getIndices(): array
  213.     {
  214.         return $this->_indices;
  215.     }
  216.     public function hasIndices(): bool
  217.     {
  218.         return \count($this->_indices) > 0;
  219.     }
  220.     /**
  221.      * @param Index $index
  222.      */
  223.     public function hasIndex($index): bool
  224.     {
  225.         if ($index instanceof Index) {
  226.             $index $index->getName();
  227.         } else {
  228.             \trigger_deprecation(
  229.                 'ruflin/elastica',
  230.                 '7.2.0',
  231.                 'Passing a string as 1st argument to "%s()" is deprecated, pass an Index instance or use "hasIndexByName" instead. It will throw a %s in 8.0.',
  232.                 __METHOD__,
  233.                 \TypeError::class
  234.             );
  235.         }
  236.         return $this->hasIndexByName($index);
  237.     }
  238.     public function hasIndexByName(string $index): bool
  239.     {
  240.         return \in_array($index$this->_indicestrue);
  241.     }
  242.     public function getQuery(): Query
  243.     {
  244.         if (null === $this->_query) {
  245.             $this->_query Query::create('');
  246.         }
  247.         return $this->_query;
  248.     }
  249.     /**
  250.      * Creates new search object.
  251.      */
  252.     public static function create(SearchableInterface $searchObject): Search
  253.     {
  254.         return $searchObject->createSearch();
  255.     }
  256.     /**
  257.      * Combines indices to the search request path.
  258.      */
  259.     public function getPath(): string
  260.     {
  261.         if (isset($this->_options[self::OPTION_SCROLL_ID])) {
  262.             return '_search/scroll';
  263.         }
  264.         return \implode(','$this->getIndices()).'/_search';
  265.     }
  266.     /**
  267.      * Search in the set indices.
  268.      *
  269.      * @param AbstractQuery|AbstractSuggest|array|Collapse|Query|string|Suggest $query
  270.      * @param array|int                                                         $options Limit or associative array of options (option=>value)
  271.      *
  272.      * @throws InvalidException
  273.      * @throws ResponseException
  274.      */
  275.     public function search($query ''$options nullstring $method Request::POST): ResultSet
  276.     {
  277.         $this->setOptionsAndQuery($options$query);
  278.         $query $this->getQuery();
  279.         $path $this->getPath();
  280.         $params $this->getOptions();
  281.         // Send scroll_id via raw HTTP body to handle cases of very large (> 4kb) ids.
  282.         if ('_search/scroll' === $path) {
  283.             $data = [self::OPTION_SCROLL_ID => $params[self::OPTION_SCROLL_ID]];
  284.             unset($params[self::OPTION_SCROLL_ID]);
  285.         } else {
  286.             $data $query->toArray();
  287.         }
  288.         $response $this->getClient()->request($path$method$data$params);
  289.         return $this->builder->buildResultSet($response$query);
  290.     }
  291.     /**
  292.      * @param array|Query|Query\AbstractQuery|string $query
  293.      * @param bool                                   $fullResult By default only the total hit count is returned. If set to true, the full ResultSet including aggregations is returned
  294.      *
  295.      * @return int|ResultSet
  296.      */
  297.     public function count($query ''bool $fullResult falsestring $method Request::POST)
  298.     {
  299.         $this->setOptionsAndQuery(null$query);
  300.         // Clone the object as we do not want to modify the original query.
  301.         $query = clone $this->getQuery();
  302.         $query->setSize(0);
  303.         $query->setTrackTotalHits(true);
  304.         $path $this->getPath();
  305.         $response $this->getClient()->request(
  306.             $path,
  307.             $method,
  308.             $query->toArray(),
  309.             [self::OPTION_SEARCH_TYPE => self::OPTION_SEARCH_TYPE_QUERY_THEN_FETCH]
  310.         );
  311.         $resultSet $this->builder->buildResultSet($response$query);
  312.         return $fullResult $resultSet $resultSet->getTotalHits();
  313.     }
  314.     /**
  315.      * @param array|int                                                         $options
  316.      * @param AbstractQuery|AbstractSuggest|array|Collapse|Query|string|Suggest $query
  317.      */
  318.     public function setOptionsAndQuery($options null$query ''): self
  319.     {
  320.         if ('' !== $query) {
  321.             $this->setQuery($query);
  322.         }
  323.         if (\is_int($options)) {
  324.             \trigger_deprecation('ruflin/elastica''7.1.3''Passing an int as 1st argument to "%s()" is deprecated, pass an array with the key "size" instead. It will be removed in 8.0.'__METHOD__);
  325.             $this->getQuery()->setSize($options);
  326.         } elseif (\is_array($options)) {
  327.             if (isset($options['limit'])) {
  328.                 $this->getQuery()->setSize($options['limit']);
  329.                 unset($options['limit']);
  330.             }
  331.             if (isset($options['explain'])) {
  332.                 $this->getQuery()->setExplain($options['explain']);
  333.                 unset($options['explain']);
  334.             }
  335.             $this->setOptions($options);
  336.         }
  337.         return $this;
  338.     }
  339.     public function setSuggest(Suggest $suggest): self
  340.     {
  341.         return $this->setOptionsAndQuery([self::OPTION_SEARCH_TYPE_SUGGEST => 'suggest'], $suggest);
  342.     }
  343.     /**
  344.      * Returns the Scroll Iterator.
  345.      *
  346.      * @see Scroll
  347.      */
  348.     public function scroll(string $expiryTime '1m'): Scroll
  349.     {
  350.         return new Scroll($this$expiryTime);
  351.     }
  352.     public function getResultSetBuilder(): BuilderInterface
  353.     {
  354.         return $this->builder;
  355.     }
  356.     /**
  357.      * @throws InvalidException If the given key is not a valid option
  358.      */
  359.     protected function validateOption(string $key): void
  360.     {
  361.         switch ($key) {
  362.             case self::OPTION_SEARCH_TYPE:
  363.             case self::OPTION_ROUTING:
  364.             case self::OPTION_PREFERENCE:
  365.             case self::OPTION_VERSION:
  366.             case self::OPTION_TIMEOUT:
  367.             case self::OPTION_FROM:
  368.             case self::OPTION_SIZE:
  369.             case self::OPTION_SCROLL:
  370.             case self::OPTION_SCROLL_ID:
  371.             case self::OPTION_SEARCH_TYPE_SUGGEST:
  372.             case self::OPTION_SEARCH_IGNORE_UNAVAILABLE:
  373.             case self::OPTION_QUERY_CACHE:
  374.             case self::OPTION_TERMINATE_AFTER:
  375.             case self::OPTION_SHARD_REQUEST_CACHE:
  376.             case self::OPTION_FILTER_PATH:
  377.             case self::OPTION_TYPED_KEYS:
  378.                 return;
  379.         }
  380.         throw new InvalidException('Invalid option '.$key);
  381.     }
  382. }