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