1 <?php
2 
3 namespace Elastica;
4 
5 use Elastica\Bulk\ResponseSet;
6 use Elastica\Exception\Bulk\ResponseException as BulkResponseException;
7 use Elastica\Exception\ClientException;
8 use Elastica\Exception\ConnectionException;
9 use Elastica\Exception\InvalidException;
10 use Elastica\Exception\NotFoundException;
11 use Elastica\Exception\ResponseException;
12 use Elastica\Index\Recovery as IndexRecovery;
13 use Elastica\Index\Settings as IndexSettings;
14 use Elastica\Index\Stats as IndexStats;
15 use Elastica\Query\AbstractQuery;
16 use Elastica\ResultSet\BuilderInterface;
17 use Elastica\Script\AbstractScript;
18 use Elasticsearch\Endpoints\AbstractEndpoint;
19 use Elasticsearch\Endpoints\DeleteByQuery;
20 use Elasticsearch\Endpoints\Get as DocumentGet;
21 use Elasticsearch\Endpoints\Index as IndexEndpoint;
22 use Elasticsearch\Endpoints\Indices\Alias;
23 use Elasticsearch\Endpoints\Indices\Aliases\Update;
24 use Elasticsearch\Endpoints\Indices\Analyze;
25 use Elasticsearch\Endpoints\Indices\Cache\Clear;
26 use Elasticsearch\Endpoints\Indices\ClearCache;
27 use Elasticsearch\Endpoints\Indices\Close;
28 use Elasticsearch\Endpoints\Indices\Create;
29 use Elasticsearch\Endpoints\Indices\Delete;
30 use Elasticsearch\Endpoints\Indices\DeleteAlias;
31 use Elasticsearch\Endpoints\Indices\Exists;
32 use Elasticsearch\Endpoints\Indices\Flush;
33 use Elasticsearch\Endpoints\Indices\ForceMerge;
34 use Elasticsearch\Endpoints\Indices\GetAlias;
35 use Elasticsearch\Endpoints\Indices\GetMapping;
36 use Elasticsearch\Endpoints\Indices\Mapping\Get as MappingGet;
37 use Elasticsearch\Endpoints\Indices\Open;
38 use Elasticsearch\Endpoints\Indices\PutSettings;
39 use Elasticsearch\Endpoints\Indices\Refresh;
40 use Elasticsearch\Endpoints\Indices\Settings\Put;
41 use Elasticsearch\Endpoints\Indices\UpdateAliases;
42 use Elasticsearch\Endpoints\OpenPointInTime;
43 use Elasticsearch\Endpoints\UpdateByQuery;
44 
45 /**
46  * Elastica index object.
47  *
48  * Handles reads, deletes and configurations of an index
49  *
50  * @author   Nicolas Ruflin <spam@ruflin.com>
51  * @phpstan-import-type TCreateQueryArgsMatching from Query
52  */
53 class Index implements SearchableInterface
54 {
55     /**
56      * Index name.
57      *
58      * @var string Index name
59      */
60     protected $_name;
61 
62     /**
63      * Client object.
64      *
65      * @var Client Client object
66      */
67     protected $_client;
68 
69     /**
70      * Creates a new index object.
71      *
72      * All the communication to and from an index goes of this object
73      *
74      * @param Client $client Client object
75      * @param string $name   Index name
76      */
77     public function __construct(Client $client, string $name)
78     {
79         $this->_client = $client;
80         $this->_name = $name;
81     }
82 
83     /**
84      * Return Index Stats.
85      *
86      * @return IndexStats
87      */
88     public function getStats()
89     {
90         return new IndexStats($this);
91     }
92 
93     /**
94      * Return Index Recovery.
95      *
96      * @return IndexRecovery
97      */
98     public function getRecovery()
99     {
100         return new IndexRecovery($this);
101     }
102 
103     /**
104      * Sets the mappings for the current index.
105      *
106      * @param Mapping $mapping MappingType object
107      * @param array   $query   querystring when put mapping (for example update_all_types)
108      */
109     public function setMapping(Mapping $mapping, array $query = []): Response
110     {
111         return $mapping->send($this, $query);
112     }
113 
114     /**
115      * Gets all mappings for the current index.
116      *
117      * @throws ClientException
118      * @throws ConnectionException
119      * @throws ResponseException
120      */
121     public function getMapping(): array
122     {
123         // TODO: Use only GetMapping when dropping support for elasticsearch/elasticsearch 7.x
124         $endpoint = \class_exists(GetMapping::class) ? new GetMapping() : new MappingGet();
125 
126         $response = $this->requestEndpoint($endpoint);
127         $data = $response->getData();
128 
129         // Get first entry as if index is an Alias, the name of the mapping is the real name and not alias name
130         $mapping = \array_shift($data);
131 
132         return $mapping['mappings'] ?? [];
133     }
134 
135     /**
136      * Returns the index settings object.
137      *
138      * @return IndexSettings
139      */
140     public function getSettings()
141     {
142         return new IndexSettings($this);
143     }
144 
145     /**
146      * @param array|string $data
147      *
148      * @return Document
149      */
150     public function createDocument(string $id = '', $data = [])
151     {
152         return new Document($id, $data, $this);
153     }
154 
155     /**
156      * Uses _bulk to send documents to the server.
157      *
158      * @param Document[] $docs    Array of Elastica\Document
159      * @param array      $options Array of query params to use for query. For possible options check es api
160      *
161      * @throws ClientException
162      * @throws ConnectionException
163      * @throws ResponseException
164      * @throws BulkResponseException
165      * @throws InvalidException
166      *
167      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
168      */
169     public function updateDocuments(array $docs, array $options = []): ResponseSet
170     {
171         foreach ($docs as $doc) {
172             $doc->setIndex($this->getName());
173         }
174 
175         return $this->getClient()->updateDocuments($docs, $options);
176     }
177 
178     /**
179      * Update entries in the db based on a query.
180      *
181      * @param AbstractQuery|array|Query|string|null $query Query object or array
182      * @phpstan-param TCreateQueryArgsMatching $query
183      *
184      * @param AbstractScript $script  Script
185      * @param array          $options Optional params
186      *
187      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html
188      *
189      * @throws ClientException
190      * @throws ConnectionException
191      * @throws ResponseException
192      */
193     public function updateByQuery($query, AbstractScript $script, array $options = []): Response
194     {
195         $endpoint = new UpdateByQuery();
196         $q = Query::create($query)->getQuery();
197         $body = [
198             'query' => \is_array($q) ? $q : $q->toArray(),
199             'script' => $script->toArray()['script'],
200         ];
201 
202         $endpoint->setBody($body);
203         $endpoint->setParams($options);
204 
205         return $this->requestEndpoint($endpoint);
206     }
207 
208     /**
209      * Adds the given document to the search index.
210      *
211      * @throws ClientException
212      * @throws ConnectionException
213      * @throws ResponseException
214      */
215     public function addDocument(Document $doc): Response
216     {
217         $endpoint = new IndexEndpoint();
218 
219         if (null !== $doc->getId() && '' !== $doc->getId()) {
220             $endpoint->setId($doc->getId());
221         }
222 
223         $options = $doc->getOptions(
224             [
225                 'consistency',
226                 'op_type',
227                 'parent',
228                 'percolate',
229                 'pipeline',
230                 'refresh',
231                 'replication',
232                 'retry_on_conflict',
233                 'routing',
234                 'timeout',
235             ]
236         );
237 
238         $endpoint->setBody($doc->getData());
239         $endpoint->setParams($options);
240 
241         $response = $this->requestEndpoint($endpoint);
242 
243         $data = $response->getData();
244         // set autogenerated id to document
245         if ($response->isOk() && (
246             $doc->isAutoPopulate() || $this->getClient()->getConfigValue(['document', 'autoPopulate'], false)
247         )) {
248             if (isset($data['_id']) && !$doc->hasId()) {
249                 $doc->setId($data['_id']);
250             }
251             $doc->setVersionParams($data);
252         }
253 
254         return $response;
255     }
256 
257     /**
258      * Uses _bulk to send documents to the server.
259      *
260      * @param array|Document[] $docs    Array of Elastica\Document
261      * @param array            $options Array of query params to use for query. For possible options check es api
262      *
263      * @throws ClientException
264      * @throws ConnectionException
265      * @throws ResponseException
266      * @throws BulkResponseException
267      * @throws InvalidException
268      *
269      * @return ResponseSet
270      *
271      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
272      */
273     public function addDocuments(array $docs, array $options = [])
274     {
275         foreach ($docs as $doc) {
276             $doc->setIndex($this->getName());
277         }
278 
279         return $this->getClient()->addDocuments($docs, $options);
280     }
281 
282     /**
283      * Get the document from search index.
284      *
285      * @param int|string $id      Document id
286      * @param array      $options options for the get request
287      *
288      * @throws NotFoundException
289      * @throws ClientException
290      * @throws ConnectionException
291      * @throws ResponseException
292      */
293     public function getDocument($id, array $options = []): Document
294     {
295         $endpoint = new DocumentGet();
296         $endpoint->setId($id);
297         $endpoint->setParams($options);
298 
299         $response = $this->requestEndpoint($endpoint);
300         $result = $response->getData();
301 
302         if (!isset($result['found']) || false === $result['found']) {
303             throw new NotFoundException('doc id '.$id.' not found');
304         }
305 
306         if (isset($result['fields'])) {
307             $data = $result['fields'];
308         } elseif (isset($result['_source'])) {
309             $data = $result['_source'];
310         } else {
311             $data = [];
312         }
313 
314         $doc = new Document($id, $data, $this->getName());
315         $doc->setVersionParams($result);
316 
317         return $doc;
318     }
319 
320     /**
321      * Deletes a document by its unique identifier.
322      *
323      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html
324      *
325      * @throws ClientException
326      * @throws ConnectionException
327      * @throws ResponseException
328      */
329     public function deleteById(string $id, array $options = []): Response
330     {
331         if (!\trim($id)) {
332             throw new NotFoundException('Doc id "'.$id.'" not found and can not be deleted');
333         }
334 
335         $endpoint = new \Elasticsearch\Endpoints\Delete();
336         $endpoint->setId(\trim($id));
337         $endpoint->setParams($options);
338 
339         return $this->requestEndpoint($endpoint);
340     }
341 
342     /**
343      * Deletes documents matching the given query.
344      *
345      * @param AbstractQuery|array|Query|string|null $query Query object or array
346      * @phpstan-param TCreateQueryArgsMatching $query
347      *
348      * @param array $options Optional params
349      *
350      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html
351      *
352      * @throws ClientException
353      * @throws ConnectionException
354      * @throws ResponseException
355      */
356     public function deleteByQuery($query, array $options = []): Response
357     {
358         $query = Query::create($query)->getQuery();
359 
360         $endpoint = new DeleteByQuery();
361         $endpoint->setBody(['query' => \is_array($query) ? $query : $query->toArray()]);
362         $endpoint->setParams($options);
363 
364         return $this->requestEndpoint($endpoint);
365     }
366 
367     /**
368      * Opens a Point-in-Time on the index.
369      *
370      * @see: https://www.elastic.co/guide/en/elasticsearch/reference/current/point-in-time-api.html
371      *
372      * @throws ClientException
373      * @throws ConnectionException
374      * @throws ResponseException
375      */
376     public function openPointInTime(string $keepAlive): Response
377     {
378         $endpoint = new OpenPointInTime();
379         $endpoint->setParams(['keep_alive' => $keepAlive]);
380 
381         return $this->requestEndpoint($endpoint);
382     }
383 
384     /**
385      * Deletes the index.
386      *
387      * @throws ClientException
388      * @throws ConnectionException
389      * @throws ResponseException
390      */
391     public function delete(): Response
392     {
393         return $this->requestEndpoint(new Delete());
394     }
395 
396     /**
397      * Uses the "_bulk" endpoint to delete documents from the server.
398      *
399      * @param Document[] $docs Array of documents
400      *
401      * @throws ClientException
402      * @throws ConnectionException
403      * @throws ResponseException
404      * @throws BulkResponseException
405      * @throws InvalidException
406      *
407      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
408      */
409     public function deleteDocuments(array $docs): ResponseSet
410     {
411         foreach ($docs as $doc) {
412             $doc->setIndex($this->getName());
413         }
414 
415         return $this->getClient()->deleteDocuments($docs);
416     }
417 
418     /**
419      * Force merges index.
420      *
421      * Detailed arguments can be found here in the ES documentation.
422      *
423      * @param array $args Additional arguments
424      *
425      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html
426      *
427      * @throws ClientException
428      * @throws ConnectionException
429      * @throws ResponseException
430      */
431     public function forcemerge($args = []): Response
432     {
433         $endpoint = new ForceMerge();
434         $endpoint->setParams($args);
435 
436         return $this->requestEndpoint($endpoint);
437     }
438 
439     /**
440      * Refreshes the index.
441      *
442      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html
443      *
444      * @throws ClientException
445      * @throws ConnectionException
446      * @throws ResponseException
447      */
448     public function refresh(): Response
449     {
450         return $this->requestEndpoint(new Refresh());
451     }
452 
453     /**
454      * Creates a new index with the given arguments.
455      *
456      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html
457      *
458      * @param array      $args    Additional arguments to pass to the Create endpoint
459      * @param array|bool $options OPTIONAL
460      *                            bool=> Deletes index first if already exists (default = false).
461      *                            array => Associative array of options (option=>value)
462      *
463      * @throws InvalidException
464      * @throws ClientException
465      * @throws ConnectionException
466      * @throws ResponseException
467      *
468      * @return Response Server response
469      */
470     public function create(array $args = [], $options = null): Response
471     {
472         if (null === $options) {
473             if (\func_num_args() >= 2) {
474                 \trigger_deprecation('ruflin/elastica', '7.1.0', 'Passing null as 2nd argument to "%s()" is deprecated, avoid passing this argument or pass an array instead. It will be removed in 8.0.', __METHOD__);
475             }
476             $options = [];
477         } elseif (\is_bool($options)) {
478             \trigger_deprecation('ruflin/elastica', '7.1.0', 'Passing a bool as 2nd argument to "%s()" is deprecated, pass an array with the key "recreate" instead. It will be removed in 8.0.', __METHOD__);
479             $options = ['recreate' => $options];
480         } elseif (!\is_array($options)) {
481             throw new \TypeError(\sprintf('Argument 2 passed to "%s()" must be of type array|bool|null, %s given.', __METHOD__, \is_object($options) ? \get_class($options) : \gettype($options)));
482         }
483 
484         $endpoint = new Create();
485         $invalidOptions = \array_diff(\array_keys($options), $allowedOptions = \array_merge($endpoint->getParamWhitelist(), [
486             'recreate',
487         ]));
488 
489         if (1 === $invalidOptionCount = \count($invalidOptions)) {
490             throw new InvalidException(\sprintf('"%s" is not a valid option. Allowed options are "%s".', \implode('", "', $invalidOptions), \implode('", "', $allowedOptions)));
491         }
492 
493         if ($invalidOptionCount > 1) {
494             throw new InvalidException(\sprintf('"%s" are not valid options. Allowed options are "%s".', \implode('", "', $invalidOptions), \implode('", "', $allowedOptions)));
495         }
496 
497         if ($options['recreate'] ?? false) {
498             try {
499                 $this->delete();
500             } catch (ResponseException $e) {
501                 // Index can't be deleted, because it doesn't exist
502             }
503         }
504 
505         unset($options['recreate']);
506 
507         $endpoint->setParams($options);
508         $endpoint->setBody($args);
509 
510         return $this->requestEndpoint($endpoint);
511     }
512 
513     /**
514      * Checks if the given index exists ans is created.
515      *
516      * @throws ClientException
517      * @throws ConnectionException
518      * @throws ResponseException
519      */
520     public function exists(): bool
521     {
522         $response = $this->requestEndpoint(new Exists());
523 
524         return 200 === $response->getStatus();
525     }
526 
527     /**
528      * {@inheritdoc}
529      */
530     public function createSearch($query = '', $options = null, ?BuilderInterface $builder = null): Search
531     {
532         $search = new Search($this->getClient(), $builder);
533         $search->addIndex($this);
534         $search->setOptionsAndQuery($options, $query);
535 
536         return $search;
537     }
538 
539     /**
540      * {@inheritdoc}
541      */
542     public function search($query = '', $options = null, string $method = Request::POST): ResultSet
543     {
544         $search = $this->createSearch($query, $options);
545 
546         return $search->search('', null, $method);
547     }
548 
549     /**
550      * {@inheritdoc}
551      */
552     public function count($query = '', string $method = Request::POST): int
553     {
554         $search = $this->createSearch($query);
555 
556         return $search->count('', false, $method);
557     }
558 
559     /**
560      * Opens an index.
561      *
562      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html
563      *
564      * @throws ClientException
565      * @throws ConnectionException
566      * @throws ResponseException
567      */
568     public function open(): Response
569     {
570         return $this->requestEndpoint(new Open());
571     }
572 
573     /**
574      * Closes the index.
575      *
576      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html
577      *
578      * @throws ClientException
579      * @throws ConnectionException
580      * @throws ResponseException
581      */
582     public function close(): Response
583     {
584         return $this->requestEndpoint(new Close());
585     }
586 
587     /**
588      * Returns the index name.
589      */
590     public function getName(): string
591     {
592         return $this->_name;
593     }
594 
595     /**
596      * Returns index client.
597      */
598     public function getClient(): Client
599     {
600         return $this->_client;
601     }
602 
603     /**
604      * Adds an alias to the current index.
605      *
606      * @param bool $replace If set, an existing alias will be replaced
607      *
608      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html
609      *
610      * @throws ClientException
611      * @throws ConnectionException
612      * @throws ResponseException
613      */
614     public function addAlias(string $name, bool $replace = false): Response
615     {
616         $data = ['actions' => []];
617 
618         if ($replace) {
619             $status = new Status($this->getClient());
620             foreach ($status->getIndicesWithAlias($name) as $index) {
621                 $data['actions'][] = ['remove' => ['index' => $index->getName(), 'alias' => $name]];
622             }
623         }
624 
625         $data['actions'][] = ['add' => ['index' => $this->getName(), 'alias' => $name]];
626 
627         // TODO: Use only UpdateAliases when dropping support for elasticsearch/elasticsearch 7.x
628         $endpoint = \class_exists(UpdateAliases::class) ? new UpdateAliases() : new Update();
629         $endpoint->setBody($data);
630 
631         return $this->getClient()->requestEndpoint($endpoint);
632     }
633 
634     /**
635      * Removes an alias pointing to the current index.
636      *
637      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-aliases.html
638      *
639      * @throws ClientException
640      * @throws ConnectionException
641      * @throws ResponseException
642      */
643     public function removeAlias(string $name): Response
644     {
645         // TODO: Use only DeleteAlias when dropping support for elasticsearch/elasticsearch 7.x
646         $endpoint = \class_exists(DeleteAlias::class) ? new DeleteAlias() : new Alias\Delete();
647         $endpoint->setName($name);
648 
649         return $this->requestEndpoint($endpoint);
650     }
651 
652     /**
653      * Returns all index aliases.
654      *
655      * @throws ClientException
656      * @throws ConnectionException
657      * @throws ResponseException
658      *
659      * @return string[]
660      */
661     public function getAliases(): array
662     {
663         // TODO: Use only GetAlias when dropping support for elasticsearch/elasticsearch 7.x
664         $endpoint = \class_exists(GetAlias::class) ? new GetAlias() : new Alias\Get();
665         $endpoint->setName('*');
666 
667         $responseData = $this->requestEndpoint($endpoint)->getData();
668 
669         if (!isset($responseData[$this->getName()])) {
670             return [];
671         }
672 
673         $data = $responseData[$this->getName()];
674         if (!empty($data['aliases'])) {
675             return \array_keys($data['aliases']);
676         }
677 
678         return [];
679     }
680 
681     /**
682      * Checks if the index has the given alias.
683      */
684     public function hasAlias(string $name): bool
685     {
686         return \in_array($name, $this->getAliases(), true);
687     }
688 
689     /**
690      * Clears the cache of an index.
691      *
692      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-clearcache.html
693      *
694      * @throws ClientException
695      * @throws ConnectionException
696      * @throws ResponseException
697      */
698     public function clearCache(): Response
699     {
700         // TODO: Use only ClearCache when dropping support for elasticsearch/elasticsearch 7.x
701         $endpoint = \class_exists(ClearCache::class) ? new ClearCache() : new Clear();
702 
703         // TODO: add additional cache clean arguments
704         return $this->requestEndpoint($endpoint);
705     }
706 
707     /**
708      * Flushes the index to storage.
709      *
710      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-flush.html
711      *
712      * @throws ClientException
713      * @throws ConnectionException
714      * @throws ResponseException
715      */
716     public function flush(array $options = []): Response
717     {
718         $endpoint = new Flush();
719         $endpoint->setParams($options);
720 
721         return $this->requestEndpoint($endpoint);
722     }
723 
724     /**
725      * Can be used to change settings during runtime. One example is to use it for bulk updating.
726      *
727      * @param array $data Data array
728      *
729      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html
730      *
731      * @throws ClientException
732      * @throws ConnectionException
733      * @throws ResponseException
734      */
735     public function setSettings(array $data): Response
736     {
737         // TODO: Use only PutSettings when dropping support for elasticsearch/elasticsearch 7.x
738         $endpoint = \class_exists(PutSettings::class) ? new PutSettings() : new Put();
739         $endpoint->setBody($data);
740 
741         return $this->requestEndpoint($endpoint);
742     }
743 
744     /**
745      * Makes calls to the elasticsearch server based on this index.
746      *
747      * @param string       $path   Path to call
748      * @param string       $method Rest method to use (GET, POST, DELETE, PUT)
749      * @param array|string $data   Arguments as array or encoded string
750      *
751      * @throws ClientException
752      * @throws ConnectionException
753      * @throws ResponseException
754      */
755     public function request(string $path, string $method, $data = [], array $queryParameters = []): Response
756     {
757         $path = $this->getName().'/'.$path;
758 
759         return $this->getClient()->request($path, $method, $data, $queryParameters);
760     }
761 
762     /**
763      * Makes calls to the elasticsearch server with usage official client Endpoint based on this index.
764      *
765      * @throws ClientException
766      * @throws ConnectionException
767      * @throws ResponseException
768      */
769     public function requestEndpoint(AbstractEndpoint $endpoint): Response
770     {
771         $cloned = clone $endpoint;
772         $cloned->setIndex($this->getName());
773 
774         return $this->getClient()->requestEndpoint($cloned);
775     }
776 
777     /**
778      * Run the analysis on the index.
779      *
780      * @param array $body request body for the `_analyze` API, see API documentation for the required properties
781      * @param array $args Additional arguments
782      *
783      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-analyze.html
784      *
785      * @throws ClientException
786      * @throws ConnectionException
787      * @throws ResponseException
788      */
789     public function analyze(array $body, $args = []): array
790     {
791         $endpoint = new Analyze();
792         $endpoint->setBody($body);
793         $endpoint->setParams($args);
794 
795         $data = $this->requestEndpoint($endpoint)->getData();
796 
797         // Support for "Explain" parameter, that returns a different response structure from Elastic
798         // @see: https://www.elastic.co/guide/en/elasticsearch/reference/current/_explain_analyze.html
799         if (isset($body['explain']) && $body['explain']) {
800             return $data['detail'];
801         }
802 
803         return $data['tokens'];
804     }
805 
806     /**
807      * Update document, using update script.
808      *
809      * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html
810      *
811      * @param AbstractScript|Document $data    Document or Script with update data
812      * @param array                   $options array of query params to use for query
813      */
814     public function updateDocument($data, array $options = []): Response
815     {
816         if (!($data instanceof Document) && !($data instanceof AbstractScript)) {
817             throw new \InvalidArgumentException('Data should be a Document or Script');
818         }
819 
820         if (!$data->hasId()) {
821             throw new InvalidException('Document or Script id is not set');
822         }
823 
824         return $this->getClient()->updateDocument($data->getId(), $data, $this->getName(), $options);
825     }
826 }
827