Type.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. <?php
  2. namespace Elastica;
  3. use Elastica\Exception\InvalidException;
  4. use Elastica\Exception\NotFoundException;
  5. use Elastica\Exception\RuntimeException;
  6. use Elastica\ResultSet\BuilderInterface;
  7. use Elastica\Script\AbstractScript;
  8. use Elastica\Type\Mapping;
  9. use Elasticsearch\Endpoints\AbstractEndpoint;
  10. use Elasticsearch\Endpoints\Delete;
  11. use Elasticsearch\Endpoints\DeleteByQuery;
  12. use Elasticsearch\Endpoints\Indices\Mapping\Get;
  13. use Elasticsearch\Endpoints\Indices\Type\Exists;
  14. /**
  15. * Elastica type object.
  16. *
  17. * elasticsearch has for every types as a substructure. This object
  18. * represents a type inside a context
  19. * The hierarchy is as following: client -> index -> type -> document
  20. *
  21. * @author Nicolas Ruflin <spam@ruflin.com>
  22. */
  23. class Type implements SearchableInterface
  24. {
  25. /**
  26. * Index.
  27. *
  28. * @var \Elastica\Index Index object
  29. */
  30. protected $_index;
  31. /**
  32. * Type name.
  33. *
  34. * @var string Type name
  35. */
  36. protected $_name;
  37. /**
  38. * @var array|string A callable that serializes an object passed to it
  39. */
  40. protected $_serializer;
  41. /**
  42. * Creates a new type object inside the given index.
  43. *
  44. * @param \Elastica\Index $index Index Object
  45. * @param string $name Type name
  46. */
  47. public function __construct(Index $index, $name)
  48. {
  49. $this->_index = $index;
  50. $this->_name = $name;
  51. }
  52. /**
  53. * Adds the given document to the search index.
  54. *
  55. * @param \Elastica\Document $doc Document with data
  56. *
  57. * @return \Elastica\Response
  58. */
  59. public function addDocument(Document $doc)
  60. {
  61. $endpoint = new \Elasticsearch\Endpoints\Index();
  62. if (null !== $doc->getId() && '' !== $doc->getId()) {
  63. $endpoint->setID($doc->getId());
  64. }
  65. $options = $doc->getOptions(
  66. [
  67. 'version',
  68. 'version_type',
  69. 'routing',
  70. 'percolate',
  71. 'parent',
  72. 'op_type',
  73. 'consistency',
  74. 'replication',
  75. 'refresh',
  76. 'timeout',
  77. 'pipeline',
  78. ]
  79. );
  80. $endpoint->setBody($doc->getData());
  81. $endpoint->setParams($options);
  82. $response = $this->requestEndpoint($endpoint);
  83. $data = $response->getData();
  84. // set autogenerated id to document
  85. if (($doc->isAutoPopulate()
  86. || $this->getIndex()->getClient()->getConfigValue(['document', 'autoPopulate'], false))
  87. && $response->isOk()
  88. ) {
  89. if (!$doc->hasId()) {
  90. if (isset($data['_id'])) {
  91. $doc->setId($data['_id']);
  92. }
  93. }
  94. if (isset($data['_version'])) {
  95. $doc->setVersion($data['_version']);
  96. }
  97. }
  98. return $response;
  99. }
  100. /**
  101. * @param $object
  102. * @param Document $doc
  103. *
  104. * @throws Exception\RuntimeException
  105. *
  106. * @return Response
  107. */
  108. public function addObject($object, Document $doc = null)
  109. {
  110. if (!isset($this->_serializer)) {
  111. throw new RuntimeException('No serializer defined');
  112. }
  113. $data = call_user_func($this->_serializer, $object);
  114. if (!$doc) {
  115. $doc = new Document();
  116. }
  117. $doc->setData($data);
  118. return $this->addDocument($doc);
  119. }
  120. /**
  121. * Update document, using update script. Requires elasticsearch >= 0.19.0.
  122. *
  123. * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html
  124. *
  125. * @param \Elastica\Document|\Elastica\Script\AbstractScript $data Document with update data
  126. * @param array $options array of query params to use for query. For possible options check es api
  127. *
  128. * @throws \Elastica\Exception\InvalidException
  129. *
  130. * @return \Elastica\Response
  131. */
  132. public function updateDocument($data, array $options = [])
  133. {
  134. if (!($data instanceof Document) && !($data instanceof AbstractScript)) {
  135. throw new \InvalidArgumentException('Data should be a Document or Script');
  136. }
  137. if (!$data->hasId()) {
  138. throw new InvalidException('Document or Script id is not set');
  139. }
  140. return $this->getIndex()->getClient()->updateDocument(
  141. $data->getId(),
  142. $data,
  143. $this->getIndex()->getName(),
  144. $this->getName(),
  145. $options
  146. );
  147. }
  148. /**
  149. * Uses _bulk to send documents to the server.
  150. *
  151. * @param array|\Elastica\Document[] $docs Array of Elastica\Document
  152. * @param array $options Array of query params to use for query. For possible options check es api
  153. *
  154. * @return \Elastica\Bulk\ResponseSet
  155. *
  156. * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
  157. */
  158. public function updateDocuments(array $docs, array $options = [])
  159. {
  160. foreach ($docs as $doc) {
  161. $doc->setType($this->getName());
  162. }
  163. return $this->getIndex()->updateDocuments($docs, $options);
  164. }
  165. /**
  166. * Uses _bulk to send documents to the server.
  167. *
  168. * @param array|\Elastica\Document[] $docs Array of Elastica\Document
  169. * @param array $options Array of query params to use for query. For possible options check es api
  170. *
  171. * @return \Elastica\Bulk\ResponseSet
  172. *
  173. * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
  174. */
  175. public function addDocuments(array $docs, array $options = [])
  176. {
  177. foreach ($docs as $doc) {
  178. $doc->setType($this->getName());
  179. }
  180. return $this->getIndex()->addDocuments($docs, $options);
  181. }
  182. /**
  183. * Uses _bulk to send documents to the server.
  184. *
  185. * @param object[] $objects
  186. * @param array $options Array of query params to use for query. For possible options check es api
  187. *
  188. * @return Bulk\ResponseSet
  189. *
  190. * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
  191. */
  192. public function addObjects(array $objects, array $options = [])
  193. {
  194. if (!isset($this->_serializer)) {
  195. throw new RuntimeException('No serializer defined');
  196. }
  197. $docs = [];
  198. foreach ($objects as $object) {
  199. $data = call_user_func($this->_serializer, $object);
  200. $doc = new Document();
  201. $doc->setData($data);
  202. $doc->setType($this->getName());
  203. $docs[] = $doc;
  204. }
  205. return $this->getIndex()->addDocuments($docs, $options);
  206. }
  207. /**
  208. * Get the document from search index.
  209. *
  210. * @param string $id Document id
  211. * @param array $options options for the get request
  212. *
  213. * @throws \Elastica\Exception\NotFoundException
  214. * @throws \Elastica\Exception\ResponseException
  215. *
  216. * @return \Elastica\Document
  217. */
  218. public function getDocument($id, $options = [])
  219. {
  220. $endpoint = new \Elasticsearch\Endpoints\Get();
  221. $endpoint->setID($id);
  222. $endpoint->setParams($options);
  223. $response = $this->requestEndpoint($endpoint);
  224. $result = $response->getData();
  225. if (!isset($result['found']) || false === $result['found']) {
  226. throw new NotFoundException('doc id '.$id.' not found');
  227. }
  228. if (isset($result['fields'])) {
  229. $data = $result['fields'];
  230. } elseif (isset($result['_source'])) {
  231. $data = $result['_source'];
  232. } else {
  233. $data = [];
  234. }
  235. $document = new Document($id, $data, $this->getName(), $this->getIndex());
  236. $document->setVersion($result['_version']);
  237. return $document;
  238. }
  239. /**
  240. * @param string $id
  241. * @param array|string $data
  242. *
  243. * @return Document
  244. */
  245. public function createDocument($id = '', $data = [])
  246. {
  247. $document = new Document($id, $data);
  248. $document->setType($this);
  249. return $document;
  250. }
  251. /**
  252. * Returns the type name.
  253. *
  254. * @return string Type name
  255. */
  256. public function getName()
  257. {
  258. return $this->_name;
  259. }
  260. /**
  261. * Sets value type mapping for this type.
  262. *
  263. * @param \Elastica\Type\Mapping|array $mapping Elastica\Type\MappingType object or property array with all mappings
  264. * @param array $query querystring when put mapping (for example update_all_types)
  265. *
  266. * @return \Elastica\Response
  267. */
  268. public function setMapping($mapping, array $query = [])
  269. {
  270. $mapping = Mapping::create($mapping);
  271. $mapping->setType($this);
  272. return $mapping->send($query);
  273. }
  274. /**
  275. * Returns current mapping for the given type.
  276. *
  277. * @return array Current mapping
  278. */
  279. public function getMapping()
  280. {
  281. $response = $this->requestEndpoint(new Get());
  282. $data = $response->getData();
  283. $mapping = array_shift($data);
  284. if (isset($mapping['mappings'])) {
  285. return $mapping['mappings'];
  286. }
  287. return [];
  288. }
  289. /**
  290. * Create search object.
  291. *
  292. * @param string|array|\Elastica\Query $query Array with all query data inside or a Elastica\Query object
  293. * @param int|array $options OPTIONAL Limit or associative array of options (option=>value)
  294. * @param BuilderInterface $builder
  295. *
  296. * @return Search
  297. */
  298. public function createSearch($query = '', $options = null, BuilderInterface $builder = null)
  299. {
  300. $search = $this->getIndex()->createSearch($query, $options, $builder);
  301. $search->addType($this);
  302. return $search;
  303. }
  304. /**
  305. * Do a search on this type.
  306. *
  307. * @param string|array|\Elastica\Query $query Array with all query data inside or a Elastica\Query object
  308. * @param int|array $options OPTIONAL Limit or associative array of options (option=>value)
  309. *
  310. * @return \Elastica\ResultSet with all results inside
  311. *
  312. * @see \Elastica\SearchableInterface::search
  313. */
  314. public function search($query = '', $options = null)
  315. {
  316. $search = $this->createSearch($query, $options);
  317. return $search->search();
  318. }
  319. /**
  320. * Count docs by query.
  321. *
  322. * @param string|array|\Elastica\Query $query Array with all query data inside or a Elastica\Query object
  323. *
  324. * @return int number of documents matching the query
  325. *
  326. * @see \Elastica\SearchableInterface::count
  327. */
  328. public function count($query = '')
  329. {
  330. $search = $this->createSearch($query);
  331. return $search->count();
  332. }
  333. /**
  334. * Returns index client.
  335. *
  336. * @return \Elastica\Index Index object
  337. */
  338. public function getIndex()
  339. {
  340. return $this->_index;
  341. }
  342. /**
  343. * @param \Elastica\Document $document
  344. *
  345. * @return \Elastica\Response
  346. */
  347. public function deleteDocument(Document $document)
  348. {
  349. $options = $document->getOptions(
  350. [
  351. 'version',
  352. 'version_type',
  353. 'routing',
  354. 'parent',
  355. 'replication',
  356. 'consistency',
  357. 'refresh',
  358. 'timeout',
  359. ]
  360. );
  361. return $this->deleteById($document->getId(), $options);
  362. }
  363. /**
  364. * Uses _bulk to delete documents from the server.
  365. *
  366. * @param array|\Elastica\Document[] $docs Array of Elastica\Document
  367. *
  368. * @return \Elastica\Bulk\ResponseSet
  369. *
  370. * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
  371. */
  372. public function deleteDocuments(array $docs)
  373. {
  374. foreach ($docs as $doc) {
  375. $doc->setType($this->getName());
  376. }
  377. return $this->getIndex()->deleteDocuments($docs);
  378. }
  379. /**
  380. * Deletes an entry by its unique identifier.
  381. *
  382. * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html
  383. *
  384. * @param int|string $id Document id
  385. * @param array $options
  386. *
  387. * @throws \InvalidArgumentException
  388. * @throws \Elastica\Exception\NotFoundException
  389. *
  390. * @return \Elastica\Response Response object
  391. */
  392. public function deleteById($id, array $options = [])
  393. {
  394. if (empty($id) || !trim($id)) {
  395. throw new \InvalidArgumentException();
  396. }
  397. $endpoint = new Delete();
  398. $endpoint->setID($id);
  399. $endpoint->setParams($options);
  400. $response = $this->requestEndpoint($endpoint);
  401. $responseData = $response->getData();
  402. if (isset($responseData['result']) && 'not_found' == $responseData['result']) {
  403. throw new NotFoundException('Doc id '.$id.' not found and can not be deleted');
  404. }
  405. return $response;
  406. }
  407. /**
  408. * Deletes the given list of ids from this type.
  409. *
  410. * @param array $ids
  411. * @param string|bool $routing Optional routing key for all ids
  412. *
  413. * @return \Elastica\Response Response object
  414. */
  415. public function deleteIds(array $ids, $routing = false)
  416. {
  417. return $this->getIndex()->getClient()->deleteIds($ids, $this->getIndex(), $this, $routing);
  418. }
  419. /**
  420. * Deletes entries in the db based on a query.
  421. *
  422. * @param \Elastica\Query|\Elastica\Query\AbstractQuery|string|array $query Query object
  423. * @param array $options Optional params
  424. *
  425. * @return \Elastica\Response
  426. *
  427. * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
  428. */
  429. public function deleteByQuery($query, array $options = [])
  430. {
  431. $query = Query::create($query);
  432. $endpoint = new DeleteByQuery();
  433. $endpoint->setBody($query->toArray());
  434. $endpoint->setParams($options);
  435. return $this->requestEndpoint($endpoint);
  436. }
  437. /**
  438. * Makes calls to the elasticsearch server based on this type.
  439. *
  440. * @param string $path Path to call
  441. * @param string $method Rest method to use (GET, POST, DELETE, PUT)
  442. * @param array $data OPTIONAL Arguments as array
  443. * @param array $query OPTIONAL Query params
  444. *
  445. * @return \Elastica\Response Response object
  446. */
  447. public function request($path, $method, $data = [], array $query = [])
  448. {
  449. $path = $this->getName().'/'.$path;
  450. return $this->getIndex()->request($path, $method, $data, $query);
  451. }
  452. /**
  453. * Makes calls to the elasticsearch server with usage official client Endpoint based on this type.
  454. *
  455. * @param AbstractEndpoint $endpoint
  456. *
  457. * @return Response
  458. */
  459. public function requestEndpoint(AbstractEndpoint $endpoint)
  460. {
  461. $cloned = clone $endpoint;
  462. $cloned->setType($this->getName());
  463. return $this->getIndex()->requestEndpoint($cloned);
  464. }
  465. /**
  466. * Sets the serializer callable used in addObject.
  467. *
  468. * @see \Elastica\Type::addObject
  469. *
  470. * @param array|string $serializer @see \Elastica\Type::_serializer
  471. *
  472. * @return $this
  473. */
  474. public function setSerializer($serializer)
  475. {
  476. $this->_serializer = $serializer;
  477. return $this;
  478. }
  479. /**
  480. * Checks if the given type exists in Index.
  481. *
  482. * @return bool True if type exists
  483. */
  484. public function exists()
  485. {
  486. $response = $this->requestEndpoint(new Exists());
  487. return 200 === $response->getStatus();
  488. }
  489. }