Passed
Branch account (1cb1c3)
by vincent
02:52
created

Tmdb::getRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
crap 1
1
<?php declare(strict_types=1);
2
3
/**
4
 * This file is part of the Tmdb package.
5
 *
6
 * (c) Vincent Faliès <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * @author Vincent Faliès <[email protected]>
12
 * @copyright Copyright (c) 2017
13
 */
14
15
namespace VfacTmdb;
16
17
use VfacTmdb\Interfaces\TmdbInterface;
18
use VfacTmdb\Interfaces\HttpRequestInterface;
19
use Psr\Log\LoggerInterface;
20
use VfacTmdb\Exceptions\TmdbException;
21
use VfacTmdb\Exceptions\IncorrectParamException;
22
use VfacTmdb\Exceptions\ServerErrorException;
23
24
/**
25
 * Tmdb wrapper core class
26
 * @package Tmdb
27
 * @author Vincent Faliès <[email protected]>
28
 * @copyright Copyright (c) 2017
29
 */
30
class Tmdb implements TmdbInterface
31
{
32
33
    /**
34
     * API Key
35
     * @var string
36
     */
37
    private $api_key = null;
38
39
    /**
40
     * Default language for API response
41
     * @var string
42
     */
43
    private $language = 'fr-FR';
44
45
    /**
46
     * Include adult content in search result
47
     * @var bool
48
     */
49
    private $include_adult = false;
50
51
    /**
52
     * API Page result
53
     * @var int
54
     */
55
    private $page = 1;
56
57
    /**
58
     * API configuration
59
     * @var \stdClass
60
     */
61
    protected $configuration = null;
62
63
    /**
64
     * API Genres
65
     * @var \stdClass
66
     */
67
    protected $genres = null;
68
69
    /**
70
     * Base URL of the API
71
     * @var string
72
     */
73
    public $base_api_url = 'https://api.themoviedb.org/';
74
75
    /**
76
     * Logger
77
     * @var LoggerInterface
78
     */
79
    protected $logger = null;
80
81
    /**
82
     * API Version
83
     * @var int
84
     */
85
    protected $version = 3;
86
87
    /**
88
     * Http request object
89
     * @var HttpRequestInterface
90
     */
91
    protected $http_request = null;
92
    /**
93
     * Request object
94
     * @var \stdClass
95
     */
96
    protected $request;
97
    /**
98
     * Last request url
99
     * @var string
100
     */
101
    protected $url = null;
102
103
    /**
104
     * Constructor
105
     * @param string $api_key TMDB API Key
106
     * @param int $version Version of API (Not yet used)
107
     * @param LoggerInterface $logger Logger used in the class
108
     * @param HttpRequestInterface $http_request
109
     */
110 356
    public function __construct(string $api_key, int $version = 3, LoggerInterface $logger, HttpRequestInterface $http_request)
111
    {
112 356
        $this->api_key      = $api_key;
113 356
        $this->logger       = $logger;
114 356
        $this->version      = $version;
115 356
        $this->http_request = $http_request;
116 356
        $this->request      = new \stdClass;
117 356
    }
118
119
    /**
120
     * Send request to TMDB API with GET method
121
     * @param string $action API action to request
122
     * @param array $options Array of options of the request (optional)
123
     * @return \stdClass|null
124
     */
125 317
    public function getRequest(string $action, array $options = array()) : ?\stdClass
126
    {
127 317
        $this->logger->debug('Start sending HTTP request with GET method', array('action' => $action, 'options' => $options));
128 317
        $this->url = $this->buildHTTPUrl($action, $options);
129 317
        return $this->sendRequest('GET', $this->url);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sendRequest('GET', $this->url); (string) is incompatible with the return type declared by the interface VfacTmdb\Interfaces\TmdbInterface::getRequest of type stdClass|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
130
    }
131
132
    /**
133
     * Send request to TMDB API with POST method
134
     * @param string $action API action to request
135
     * @param array $options Array of options of the request (optional)
136
     * @param array $form_params form_params for request options
137
     * @return \stdClass|null
138
     */
139 15
    public function postRequest(string $action, array $options = array(), array $form_params = array()) : ?\stdClass
140
    {
141 15
        $this->logger->debug('Start sending HTTP request with POST method', array('action' => $action, 'options' => $options, 'form_params' => $form_params));
142 15
        $this->url = $this->buildHTTPUrl($action, $options);
143 15
        return $this->sendRequest('POST', $this->url, $form_params);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sendReques...is->url, $form_params); (string) is incompatible with the return type declared by the interface VfacTmdb\Interfaces\TmdbInterface::postRequest of type stdClass|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
144
    }
145
146
    /**
147
     * Send request to TMDB API with DELETE method
148
     * @param  string $action  API action to request
149
     * @param  array  $options Array of options of the request (optional)
150
     * @return \stdClass|null
151
     */
152 5
    public function deleteRequest(string $action, array $options = array()) : ?\stdClass
153
    {
154 5
        $this->logger->debug('Start sending HTTP request with DELETE method', array('action' => $action, 'options' => $options));
155 5
        $this->url = $this->buildHTTPUrl($action, $options);
156 5
        return $this->sendRequest('DELETE', $this->url);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->sendRequest('DELETE', $this->url); (string) is incompatible with the return type declared by the interface VfacTmdb\Interfaces\TmdbInterface::deleteRequest of type stdClass|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
157
    }
158
159
    /**
160
     * Send request to TMDB API with GET method
161
     * @param string $method HTTP method (GET, POST)
162
     * @param string $url API url to request
163
     * @param array $form_params form params request options
164
     * @return \stdClass|null
165
     */
166 6
    protected function sendRequest(string $method, string $url, array $form_params = array()) : ?\stdClass
167
    {
168
        try {
169 6
            $res = new \stdClass();
170
            switch ($method) {
171 6
                  case "GET":
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
172 4
                      $res = $this->http_request->getResponse($url);
173 4
                      break;
174 2
                  case "POST":
175 1
                      $res = $this->http_request->postResponse($url, [], $form_params);
176 1
                      break;
177 1
                  case "DELETE":
178 1
                      $res = $this->http_request->deleteResponse($url);
179 1
                      break;
180
            }
181 6
            $response = $this->decodeRequest($res, $method, $url, $form_params);
182 3
            return $response;
183 3
        } catch (TmdbException $e) {
184 3
            $this->logger->error('sendRequest failed : '.$e->getMessage(), array('method' => $method, 'url' => $url, 'form_params' => $form_params));
185 3
            throw $e;
186
        }
187
    }
188
189
    /**
190
     * Decode request response
191
     * @param  mixed $res
192
     * @param  string $method
193
     * @param  string $url
194
     * @param  array $form_params
195
     * @return string
196
     */
197 6
    private function decodeRequest($res, $method, $url, $form_params)
198
    {
199 6
        if (empty($res->getBody())) {
200 2
            $this->logger->error('Request Body empty', array('method' => $method, 'url' => $url, 'form_params' => $form_params));
201 2
            throw new ServerErrorException();
202
        }
203 4
        $response = json_decode($res->getBody());
204 4
        if (empty($response)) {
205 1
            $this->logger->error('Request Body can not be decode', array('method' => $method, 'url' => $url, 'form_params' => $form_params));
206 1
            throw new ServerErrorException();
207
        }
208 3
        return $response;
209
    }
210
211
    /**
212
     * Build URL for HTTP Call
213
     * @param string $action API action to request
214
     * @param array $options Array of options of the request (optional)
215
     * @return string
216
     */
217 319
    private function buildHTTPUrl(string $action, array $options) : string
218
    {
219
        // Url construction
220 319
        $url = $this->base_api_url . $this->version . '/' . $action;
221
222
        // Parameters
223 319
        $params            = [];
224 319
        $params['api_key'] = $this->api_key;
225
226 319
        $params = array_merge($params, $options);
227
228
        // URL with paramters construction
229 319
        $url = $url . '?' . http_build_query($params);
230
231 319
        return $url;
232
    }
233
234
    /**
235
     * Get API Configuration
236
     * @return \stdClass
237
     * @throws TmdbException
238
     */
239 50
    public function getConfiguration() : \stdClass
240
    {
241
        try {
242 50
            $this->logger->debug('Start getting configuration');
243 50
            if (is_null($this->configuration)) {
244 50
                $this->logger->debug('No configuration found, sending HTTP request to get it');
245 50
                $this->configuration = $this->getRequest('configuration');
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getRequest('configuration') of type string is incompatible with the declared type object<stdClass> of property $configuration.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
246
            }
247 49
            return $this->configuration;
248 1
        } catch (TmdbException $ex) {
249 1
            throw $ex;
250
        }
251
    }
252
253
    /**
254
     * Get logger
255
     * @return LoggerInterface
256
     */
257 324
    public function getLogger() : LoggerInterface
258
    {
259 324
        return $this->logger;
260
    }
261
262
    /**
263
     * Magical property getter
264
     * @param  string $name Name of the property
265
     * @return string       Value of the property
266
     */
267 73
    public function __get(string $name) : string
268
    {
269
        switch ($name) {
270 73
            case 'url':
271 72
                return $this->$name;
272
            default:
273 1
                throw new IncorrectParamException;
274
        }
275
    }
276
277
    /**
278
     * Check year option and return correct value
279
     * @param array $options
280
     * @param array &$return Return array to save valid option
281
     * @return void
282
     */
283 29
    public function checkOptionYear(array $options, array &$return) : void
284
    {
285 29
        if (isset($options['year'])) {
286 1
            $return['year'] = (int) $options['year'];
287
        }
288 29
    }
289
290
    /**
291
     * Check Language string with format ISO 639-1
292
     * @param array $options
293
     * @param array &$return Return array to save valid option
294
     * @return void
295
     */
296 277
    public function checkOptionLanguage(array $options, array &$return) : void
297
    {
298 277
        if (isset($options['language'])) {
299 35
            $check = preg_match("#([a-z]{2})-([A-Z]{2})#", $options['language']);
300 35
            if ($check === 0 || $check === false) {
301 1
                $this->logger->error('Incorrect language param option', array('language' => $options['language']));
302 1
                throw new IncorrectParamException;
303
            }
304 34
            $return['language'] = $options['language'];
305
        }
306 276
    }
307
308
    /**
309
     * Check include adult option
310
     * @param  array $options
311
     * @param array &$return Return array to save valid option
312
     * @return void
313
     */
314 29
    public function checkOptionIncludeAdult(array $options, array &$return) : void
315
    {
316 29
        if (isset($options['include_adult'])) {
317 1
            $return['include_adult'] = filter_var($options['include_adult'], FILTER_VALIDATE_BOOLEAN);
318
        }
319 29
    }
320
321
    /**
322
     * Check page option
323
     * @param  array  $options
324
     * @param array &$return Return array to save valid option
325
     * @return void
326
     */
327 38
    public function checkOptionPage(array $options, array &$return) : void
328
    {
329 38
        if (isset($options['page'])) {
330 1
            $return['page'] = (int) $options['page'];
331
        }
332 38
    }
333
334
    /**
335
     * Check sort by option
336
     * @param  array  $options
337
     * @param array &$return Return array to save valid option
338
     * @return void
339
     */
340 9
    public function checkOptionSortBy(array $options, array &$return) : void
341
    {
342 9
        if (isset($options['sort_by'])) {
343 2
            switch ($options['sort_by']) {
344 2
                case 'asc':
345 1
                case 'desc':
346 1
                    break;
347
                default:
348 1
                    throw new IncorrectParamException;
349
            }
350 1
            $return['sort_by'] = 'created_at.'.$options['sort_by'];
351
        }
352 8
    }
353
354
    /**
355
     * Check query option
356
     * @param  array  $options
357
     * @param array &$return Return array to save valid option
358
     * @return void
359
     */
360 28
    public function checkOptionQuery(array $options, array &$return) : void
361
    {
362 28
        if (isset($options['query'])) {
363 28
            $return['query'] = trim($options['query']);
364
        }
365 28
    }
366
367
    /**
368
     * Check session_id option
369
     * @param array  $options
370
     * @param array &$return Return array to save valid option
371
     * @return void
372
     */
373 7
    public function checkOptionSessionId(array $options, array &$return) : void
374
    {
375 7
        if (isset($options['session_id'])) {
376 7
            $return['session_id'] = trim($options['session_id']);
377
        }
378 7
    }
379
}
380