Search.php 12 KB

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