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

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