1 <?php
2 
3 namespace Elastica;
4 
5 use Elastica\Exception\NotFoundException;
6 
7 /**
8  * Elastica Response object.
9  *
10  * Stores query time, and result array -> is given to result set, returned by ...
11  *
12  * @author Nicolas Ruflin <spam@ruflin.com>
13  */
14 class Response
15 {
16     /**
17      * Query time.
18      *
19      * @var float Query time
20      */
21     protected $_queryTime;
22 
23     /**
24      * Response string (json).
25      *
26      * @var string Response
27      */
28     protected $_responseString = '';
29 
30     /**
31      * Transfer info.
32      *
33      * @var array transfer info
34      */
35     protected $_transferInfo = [];
36 
37     /**
38      * Response.
39      *
40      * @var array|null
41      */
42     protected $_response;
43 
44     /**
45      * HTTP response status code.
46      *
47      * @var int
48      */
49     protected $_status;
50 
51     /**
52      * Whether or not to convert bigint results to string (see issue #717).
53      *
54      * @var bool
55      */
56     protected $_jsonBigintConversion = false;
57 
58     /**
59      * Construct.
60      *
61      * @param array|string $responseString Response string (json)
62      * @param int          $responseStatus http status code
63      */
64     public function __construct($responseString, $responseStatus = null)
65     {
66         if (\is_array($responseString)) {
67             $this->_response = $responseString;
68         } else {
69             $this->_responseString = $responseString;
70         }
71         $this->_status = $responseStatus;
72     }
73 
74     /**
75      * Error message.
76      *
77      * @return string Error message
78      */
79     public function getError()
80     {
81         $error = $this->getFullError();
82 
83         if (!$error) {
84             return '';
85         }
86 
87         if (\is_string($error)) {
88             return $error;
89         }
90 
91         $rootError = $error['root_cause'][0] ?? $error;
92 
93         $message = $rootError['reason'];
94         if (isset($rootError['index'])) {
95             $message .= ' [index: '.$rootError['index'].']';
96         }
97 
98         if (isset($error['reason']) && $rootError['reason'] !== $error['reason']) {
99             $message .= ' [reason: '.$error['reason'].']';
100         }
101 
102         return $message;
103     }
104 
105     /**
106      * A keyed array representing any errors that occurred.
107      *
108      * In case of http://localhost:9200/_alias/test the error is a string
109      *
110      * @return array|string|null Error data or null if there is no error
111      */
112     public function getFullError()
113     {
114         $response = $this->getData();
115 
116         return $response['error'] ?? null;
117     }
118 
119     /**
120      * @return string Error string based on the error object
121      */
122     public function getErrorMessage()
123     {
124         return $this->getError();
125     }
126 
127     /**
128      * True if response has error.
129      *
130      * @return bool True if response has error
131      */
132     public function hasError()
133     {
134         $response = $this->getData();
135 
136         return isset($response['error']);
137     }
138 
139     /**
140      * True if response has failed shards.
141      *
142      * @return bool True if response has failed shards
143      */
144     public function hasFailedShards()
145     {
146         try {
147             $shardsStatistics = $this->getShardsStatistics();
148         } catch (NotFoundException $e) {
149             return false;
150         }
151 
152         return \array_key_exists('failures', $shardsStatistics);
153     }
154 
155     /**
156      * Checks if the query returned ok.
157      *
158      * @return bool True if ok
159      */
160     public function isOk()
161     {
162         $data = $this->getData();
163 
164         // Bulk insert checks. Check every item
165         if (isset($data['status'])) {
166             return $data['status'] >= 200 && $data['status'] <= 300;
167         }
168 
169         if (isset($data['items'])) {
170             if (isset($data['errors']) && true === $data['errors']) {
171                 return false;
172             }
173 
174             foreach ($data['items'] as $item) {
175                 if (isset($item['index']['ok']) && false == $item['index']['ok']) {
176                     return false;
177                 }
178 
179                 if (isset($item['index']['status']) && ($item['index']['status'] < 200 || $item['index']['status'] >= 300)) {
180                     return false;
181                 }
182             }
183 
184             return true;
185         }
186 
187         if ($this->_status >= 200 && $this->_status <= 300) {
188             // http status is ok
189             return true;
190         }
191 
192         return isset($data['ok']) && $data['ok'];
193     }
194 
195     /**
196      * @return int
197      */
198     public function getStatus()
199     {
200         return $this->_status;
201     }
202 
203     /**
204      * Response data array.
205      *
206      * @return array<string, mixed> Response data array
207      */
208     public function getData()
209     {
210         if (null == $this->_response) {
211             $response = $this->getResponseString();
212             $this->_response = $response;
213             $this->_responseString = '';
214         }
215 
216         return $this->_response;
217     }
218 
219     /**
220      * Gets the transfer information.
221      *
222      * @return array information about the curl request
223      */
224     public function getTransferInfo()
225     {
226         return $this->_transferInfo;
227     }
228 
229     /**
230      * Sets the transfer info of the curl request. This function is called
231      * from the \Elastica\Client::_callService .
232      *
233      * @param array $transferInfo the curl transfer information
234      *
235      * @return $this
236      */
237     public function setTransferInfo(array $transferInfo)
238     {
239         $this->_transferInfo = $transferInfo;
240 
241         return $this;
242     }
243 
244     /**
245      * Returns query execution time.
246      *
247      * @return float Query time
248      */
249     public function getQueryTime()
250     {
251         return $this->_queryTime;
252     }
253 
254     /**
255      * Sets the query time.
256      *
257      * @param float $queryTime Query time
258      *
259      * @return $this
260      */
261     public function setQueryTime($queryTime)
262     {
263         $this->_queryTime = $queryTime;
264 
265         return $this;
266     }
267 
268     /**
269      * Time request took.
270      *
271      * @throws NotFoundException
272      *
273      * @return int Time request took
274      */
275     public function getEngineTime()
276     {
277         $data = $this->getData();
278 
279         if (!isset($data['took'])) {
280             throw new NotFoundException('Unable to find the field [took]from the response');
281         }
282 
283         return $data['took'];
284     }
285 
286     /**
287      * Get the _shard statistics for the response.
288      *
289      * @throws NotFoundException
290      *
291      * @return array
292      */
293     public function getShardsStatistics()
294     {
295         $data = $this->getData();
296 
297         if (!isset($data['_shards'])) {
298             throw new NotFoundException('Unable to find the field [_shards] from the response');
299         }
300 
301         return $data['_shards'];
302     }
303 
304     /**
305      * Get the _scroll value for the response.
306      *
307      * @throws NotFoundException
308      *
309      * @return string
310      */
311     public function getScrollId()
312     {
313         $data = $this->getData();
314 
315         if (!isset($data['_scroll_id'])) {
316             throw new NotFoundException('Unable to find the field [_scroll_id] from the response');
317         }
318 
319         return $data['_scroll_id'];
320     }
321 
322     /**
323      * Sets whether or not to apply bigint conversion on the JSON result.
324      *
325      * @param bool $jsonBigintConversion
326      */
327     public function setJsonBigintConversion($jsonBigintConversion): void
328     {
329         $this->_jsonBigintConversion = $jsonBigintConversion;
330     }
331 
332     /**
333      * Gets whether or not to apply bigint conversion on the JSON result.
334      *
335      * @return bool
336      */
337     public function getJsonBigintConversion()
338     {
339         return $this->_jsonBigintConversion;
340     }
341 
342     /**
343      * @return array|string[]
344      */
345     private function getResponseString()
346     {
347         $response = $this->_responseString;
348         if (empty($response)) {
349             return [];
350         }
351         try {
352             if ($this->getJsonBigintConversion()) {
353                 $response = JSON::parse($response, true, 512, \JSON_BIGINT_AS_STRING);
354             } else {
355                 $response = JSON::parse($response);
356             }
357         } catch (\JsonException $e) {
358             // leave response as is if parse fails
359         }
360 
361         if (\is_string($response)) {
362             $response = ['message' => $response];
363         }
364 
365         return $response;
366     }
367 }
368