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