Passed
Branch account (d43697)
by vincent
02:17
created

Tmdb::decodeRequest()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 9
cts 9
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 4
crap 3
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 359
    public function __construct(string $api_key, int $version = 3, LoggerInterface $logger, HttpRequestInterface $http_request)
111
    {
112 359
        $this->api_key      = $api_key;
113 359
        $this->logger       = $logger;
114 359
        $this->version      = $version;
115 359
        $this->http_request = $http_request;
116 359
        $this->request      = new \stdClass;
117 359
    }
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 6
        $res = new \stdClass();
169
        switch ($method) {
170 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...
171 4
                  $res = $this->http_request->getResponse($url);
172 4
                  break;
173 2
              case "POST":
174 1
                  $res = $this->http_request->postResponse($url, [], $form_params);
175 1
                  break;
176 1
              case "DELETE":
177 1
                  $res = $this->http_request->deleteResponse($url);
178 1
                  break;
179
        }
180 6
        $response = $this->decodeRequest($res, $method, $url, $form_params);
181 3
        return $response;
182
    }
183
184
    /**
185
     * Decode request response
186
     * @param  mixed $res
187
     * @param  string $method
188
     * @param  string $url
189
     * @param  array $form_params
190
     * @return string
191
     */
192 6
    private function decodeRequest($res, $method, $url, $form_params)
193
    {
194 6
        if (empty($res->getBody())) {
195 2
            $this->logger->error('Request Body empty', array('method' => $method, 'url' => $url, 'form_params' => $form_params));
196 2
            throw new ServerErrorException();
197
        }
198 4
        $response = json_decode($res->getBody());
199 4
        if (empty($response)) {
200 1
            $this->logger->error('Request Body can not be decode', array('method' => $method, 'url' => $url, 'form_params' => $form_params));
201 1
            throw new ServerErrorException();
202
        }
203 3
        return $response;
204
    }
205
206
    /**
207
     * Build URL for HTTP Call
208
     * @param string $action API action to request
209
     * @param array $options Array of options of the request (optional)
210
     * @return string
211
     */
212 319
    private function buildHTTPUrl(string $action, array $options) : string
213
    {
214
        // Url construction
215 319
        $url = $this->base_api_url . $this->version . '/' . $action;
216
217
        // Parameters
218 319
        $params            = [];
219 319
        $params['api_key'] = $this->api_key;
220
221 319
        $params = array_merge($params, $options);
222
223
        // URL with paramters construction
224 319
        $url = $url . '?' . http_build_query($params);
225
226 319
        return $url;
227
    }
228
229
    /**
230
     * Get API Configuration
231
     * @return \stdClass
232
     * @throws TmdbException
233
     */
234 50
    public function getConfiguration() : \stdClass
235
    {
236
        try {
237 50
            $this->logger->debug('Start getting configuration');
238 50
            if (is_null($this->configuration)) {
239 50
                $this->logger->debug('No configuration found, sending HTTP request to get it');
240 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...
241
            }
242 49
            return $this->configuration;
243 1
        } catch (TmdbException $ex) {
244 1
            throw $ex;
245
        }
246
    }
247
248
    /**
249
     * Check options rules before send request
250
     * @param array $options Array of options to validate
251
     * @return array
252
     * @throws IncorrectParamException
253
     */
254 308
    public function checkOptions(array $options) : array
255
    {
256 308
        $params                  = [];
257
        // Check options
258 308
        foreach ($options as $key => $value) {
259
            switch ($key) {
260 73
                case 'year':
261 2
                    $params[$key] = $this->checkYear($value);
262 2
                    break;
263 72
                case 'language':
264 37
                    $params[$key] = $this->checkLanguage($value);
265 36
                    break;
266 64
                case 'include_adult':
267 1
                    $params[$key] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
268 1
                    break;
269 64
                case 'page':
270 1
                    $params[$key] = (int) $value;
271 1
                    break;
272 63
                case 'sort_by':
273 2
                    $params[$key] = $this->checkSort($value);
274 1
                    break;
275 61
                case 'query':
276 33
                case 'session_id':
277 56
                    $params[$key] = trim($value);
278 56
                    break;
279
                default:
280 5
                    $this->logger->error('Unknown param options', array('options', $options));
281 5
                    throw new IncorrectParamException;
282
            }
283
        }
284 301
        return $params;
285
    }
286
287
    /**
288
     * Check year format
289
     * @param mixed $year year to validate
290
     * @return int year validated
291
     */
292 2
    private function checkYear($year) : int
293
    {
294 2
        $year = (int) $year;
295 2
        return $year;
296
    }
297
298
    /**
299
     * Check language
300
     * @param string $language Language string with format ISO 639-1
301
     * @return string Language string validated
302
     * @throws IncorrectParamException
303
     */
304 37
    private function checkLanguage(string $language) : string
305
    {
306 37
        $check = preg_match("#([a-z]{2})-([A-Z]{2})#", $language);
307 37
        if ($check === 0 || $check === false) {
308 1
            $this->logger->error('Incorrect language param option', array('language' => $language));
309 1
            throw new IncorrectParamException;
310
        }
311 36
        return $language;
312
    }
313
314
    /**
315
     * Check sort direction
316
     * @param  string $direction direction of sorting
317
     * @return string            Sort string validated
318
     * @throws IncorrectParamException
319
     */
320 2
    private function checkSort(string $direction) : string
321
    {
322
        switch ($direction) {
323 2
            case 'asc':
324 1
            case 'desc':
325 1
                break;
326
            default:
327 1
                throw new IncorrectParamException;
328
        }
329 1
        return 'created_at.'.$direction;
330
    }
331
332
    /**
333
     * Get logger
334
     * @return LoggerInterface
335
     */
336 329
    public function getLogger() : LoggerInterface
337
    {
338 329
        return $this->logger;
339
    }
340
341
    /**
342
     * Magical property getter
343
     * @param  string $name Name of the property
344
     * @return string       Value of the property
345
     */
346 73
    public function __get(string $name) : string
347
    {
348
        switch ($name) {
349 73
            case 'url':
350 72
                return $this->$name;
351
            default:
352 1
                throw new IncorrectParamException;
353
        }
354
    }
355
}
356