Completed
Push — master ( bd326e...3b0d07 )
by Paweł
12:10 queued 06:38
created

src/Superdesk/ContentApiSdk/Api/Response.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * This file is part of the PHP SDK library for the Superdesk Content API.
5
 *
6
 * Copyright 2015 Sourcefabric z.u. and contributors.
7
 *
8
 * For the full copyright and license information, please see the
9
 * AUTHORS and LICENSE files distributed with this source code.
10
 *
11
 * @copyright 2015 Sourcefabric z.ú.
12
 * @license http://www.superdesk.org/license
13
 */
14
15
namespace Superdesk\ContentApiSdk\Api;
16
17
use Superdesk\ContentApiSdk\ContentApiSdk;
18
use Superdesk\ContentApiSdk\Exception\ResponseException;
19
use Superdesk\ContentApiSdk\Exception\InvalidDataException;
20
use stdClass;
21
use SimpleXMLElement;
22
use Exception;
23
24
/**
25
 * API Response object.
26
 */
27
class Response
28
{
29
    const TYPE_ITEMS = 'items';
30
    const TYPE_PACKAGES = 'packages';
31
32
    const CONTENT_TYPE_JSON = 'application/json';
33
    const CONTENT_TYPE_XML = 'application/xml';
34
35
    /**
36
     * Unprocessed body as returned by the api.
37
     *
38
     * @var string
39
     */
40
    protected $rawBody;
41
42
    /**
43
     * Content type of the response, we should support xml and json.
44
     *
45
     * @var string
46
     */
47
    protected $contentType;
48
49
    /**
50
     * Type of response.
51
     *
52
     * @var string
53
     */
54
    protected $type;
55
56
    /**
57
     * Request URI.
58
     *
59
     * @var string
60
     */
61
    protected $href;
62
63
    /**
64
     * List of response headers.
65
     *
66
     * @var array|null
67
     */
68
    protected $headers;
69
70
    /**
71
     * Current page.
72
     *
73
     * @var int
74
     */
75
    protected $page;
76
77
    /**
78
     * Next page.
79
     *
80
     * @var int
81
     */
82
    protected $nextPage;
83
84
    /**
85
     * Previous page.
86
     *
87
     * @var int
88
     */
89
    protected $prevPage;
90
91
    /**
92
     * Last page.
93
     *
94
     * @var int
95
     */
96
    protected $lastPage;
97
98
    /**
99
     * Maximum of results on 1 page.
100
     *
101
     * @var int
102
     */
103
    protected $maxResults;
104
105
    /**
106
     * Total amount of results for current parameters.
107
     *
108
     * @var int
109
     */
110
    protected $total;
111
112
    /**
113
     * Array container resources or stdClass with properties.
114
     *
115
     * @var array|stdClass
116
     */
117
    protected $resources;
118
119
    /**
120
     * Array of keys which are not part of the actual single data (item or
121
     * package).
122
     *
123
     * @var array
124
     */
125
    protected $metaKeys = array(
126
        '_links'
127
    );
128
129
    /**
130
     * Constructs Response.
131
     *
132
     * @param string     $body    Body of a response as string
133
     * @param array|null $headers List of headers
134
     */
135
    public function __construct($body, array $headers = null)
136
    {
137
        $this->rawBody = $body;
138
        $this->headers = $headers;
139
140
        $this->determineContentType();
141
        $this->processRawBody();
142
    }
143
144
    /**
145
     * Determine the content type of the response via header. If header is not
146
     * set, then content will be analyzed. Throws exception on failure.
147
     *
148
     * @return string Content type of the response
149
     *
150
     * @throws ResponseException
151
     */
152
    private function determineContentType()
153
    {
154
        if (isset($this->headers['Content-Type']) && in_array($this->headers['Content-Type'], $this->getSupportedContenTypes())) {
155
            $this->contentType = $this->headers['Content-Type'];
156
157
            return;
158
        }
159
160
        // Try to determine content type based on body
161
        try {
162
            new SimpleXMLElement($this->rawBody);
163
            $this->contentType = self::CONTENT_TYPE_XML;
164
165
            return;
166
        } catch (Exception $e) {
167
            // Not valid XML
168
        }
169
170
        $json = json_decode($this->rawBody);
171
172
        if (!is_null($json) && json_last_error() === JSON_ERROR_NONE) {
173
            $this->contentType = self::CONTENT_TYPE_JSON;
174
175
            return;
176
        }
177
178
        throw new ResponseException('Could not determine response content-type.');
179
    }
180
181
    /**
182
     * Return valid response content types.
183
     *
184
     * @return string[]
185
     */
186
    public static function getSupportedContenTypes()
187
    {
188
        return array(
189
            self::CONTENT_TYPE_JSON,
190
            self::CONTENT_TYPE_XML,
191
        );
192
    }
193
194
    /**
195
     * Sets properties based on response body.
196
     *
197
     * @throws ResponseException
198
     */
199
    private function processRawBody()
200
    {
201
        switch ($this->contentType) {
202
            case self::CONTENT_TYPE_JSON:
203
204
                try {
205
                    $responseJson = ContentApiSdk::getValidJsonObj($this->rawBody);
206
                } catch (InvalidDataException $e) {
207
                    throw new ResponseException($e->getMessage(), $e->getCode(), $e);
208
                }
209
210
                $this->type = $responseJson->_links->self->title;
211
                $this->href = $responseJson->_links->self->href;
212
213
                if (property_exists($responseJson, '_meta')) {
214
                    $this->page = $responseJson->_meta->page;
215
                    $this->maxResults = $responseJson->_meta->max_results;
216
                    $this->total = $responseJson->_meta->total;
217
218
                    $this->nextPage = (property_exists($responseJson->_links, 'next')) ? $this->page + 1 : $this->page;
219
                    $this->prevPage = (property_exists($responseJson->_links, 'prev')) ? $this->page - 1 : $this->page;
220
                    $this->lastPage = (property_exists($responseJson->_links, 'last')) ? (int) ceil($this->total / $this->maxResults) : $this->page;
221
222
                    $this->resources = $responseJson->_items;
223
                } else {
224
                    $resourceObj = new stdClass();
225
226
                    foreach ($responseJson as $key => $value) {
0 ignored issues
show
The expression $responseJson of type object|integer|double|string|array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
227
                        if (in_array($key, $this->metaKeys)) {
228
                            continue;
229
                        }
230
231
                        $resourceObj->$key = $value;
232
                    }
233
234
                    $this->resources = $resourceObj;
235
                }
236
237
                break;
238
            case self::CONTENT_TYPE_XML:
239
                break;
240
        }
241
242
        return;
243
    }
244
245
    /**
246
     * Returns content type.
247
     *
248
     * @return string
249
     */
250
    public function getContentType()
251
    {
252
        return $this->contentType;
253
    }
254
255
    /**
256
     * Returns response type. Package or item.
257
     *
258
     * @return string
259
     */
260
    public function getType()
261
    {
262
        return $this->type;
263
    }
264
265
    /**
266
     * Returns the response href. In most cases this is the request uri.
267
     *
268
     * @return string
269
     */
270
    public function getHref()
271
    {
272
        return $this->href;
273
    }
274
275
    /**
276
     * Returns current page number.
277
     *
278
     * @return int
279
     */
280
    public function getCurrentPage()
281
    {
282
        return $this->page;
283
    }
284
285
    /**
286
     * Returns next page number.
287
     *
288
     * @return int
289
     */
290
    public function getNextPage()
291
    {
292
        return $this->nextPage;
293
    }
294
295
    /**
296
     * Returns previous page number.
297
     *
298
     * @return int
299
     */
300
    public function getPreviousPage()
301
    {
302
        return $this->prevPage;
303
    }
304
305
    /**
306
     * Returns last page number.
307
     *
308
     * @return int
309
     */
310
    public function getLastPage()
311
    {
312
        return $this->lastPage;
313
    }
314
315
    /**
316
     * Returns first page number.
317
     *
318
     * @return int
319
     */
320
    public function getFirstPage()
321
    {
322
        return 1;
323
    }
324
325
    /**
326
     * Returns whether current page is last page.
327
     *
328
     * @return bool
329
     */
330
    public function isLastPage()
331
    {
332
        return $this->getCurrentPage() === $this->getLastPage();
333
    }
334
335
    /**
336
     * Returns whether current page is first page.
337
     *
338
     * @return bool
339
     */
340
    public function isFirstPage()
341
    {
342
        return $this->getCurrentPage() === $this->getFirstPage();
343
    }
344
345
    /**
346
     * Returns maximum results displayed per page.
347
     *
348
     * @return int
349
     */
350
    public function getMaxResults()
351
    {
352
        return $this->maxResults;
353
    }
354
355
    /**
356
     * Returns total amount of results.
357
     *
358
     * @return int
359
     */
360
    public function getTotalResults()
361
    {
362
        return $this->total;
363
    }
364
365
    /**
366
     * Returns the response resources, items or packages.
367
     *
368
     * @return array
369
     */
370
    public function getResources()
371
    {
372
        return $this->resources;
373
    }
374
}
375