Completed
Push — master ( 04fddd...f4fff3 )
by Marcin
06:19
created

Imdb   C

Complexity

Total Complexity 41

Size/Duplication

Total Lines 342
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 41
lcom 1
cbo 17
dl 0
loc 342
ccs 0
cts 232
cp 0
rs 6.6122
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getApiTitle() 0 4 1
A getApiTitleSearch() 0 4 1
A __construct() 0 17 2
B getRating() 0 49 4
C searchByTitle() 0 38 8
B searchByTitleApi() 0 38 4
F searchByTitleNative() 0 120 21

How to fix   Complexity   

Complex Class

Complex classes like Imdb often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Imdb, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * xMDB-API
4
 *
5
 * Copyright © 2017 pudelek.org.pl
6
 *
7
 * @license MIT License (MIT)
8
 *
9
 * For the full copyright and license information, please view source file
10
 * that is bundled with this package in the file LICENSE
11
 *
12
 * @author  Marcin Pudełek <[email protected]>
13
 */
14
15
/**
16
 * Created by Marcin.
17
 * Date: 01.12.2017
18
 * Time: 22:52
19
 */
20
21
namespace mrcnpdlk\Xmdb;
22
23
24
use Campo\UserAgent;
25
use Curl\Curl;
26
use HttpLib\Http;
27
use Imdb\Config;
28
use Imdb\Title as ApiTitle;
29
use Imdb\TitleSearch;
30
use Imdb\TitleSearch as ApiTitleSearch;
31
use KHerGe\JSON\JSON;
32
use mrcnpdlk\Xmdb\Model\Imdb\Rating;
33
use mrcnpdlk\Xmdb\Model\Imdb\Title;
34
use mrcnpdlk\Xmdb\Model\Ratio;
35
use Sunra\PhpSimple\HtmlDomParser;
36
37
class Imdb
38
{
39
    /**
40
     * @var \mrcnpdlk\Xmdb\Client
41
     */
42
    private $oClient;
43
    /**
44
     * @var \Imdb\Config
45
     */
46
    private $oConfig;
47
    /** @noinspection PhpUndefinedClassInspection */
48
49
    /**
50
     * @var \Psr\Log\LoggerInterface
51
     */
52
    private $oLog;
53
    /**
54
     * @var \Psr\SimpleCache\CacheInterface
55
     */
56
    private $oCache;
57
    /**
58
     * @var \mrcnpdlk\Xmdb\ImdbCache
59
     */
60
    private $oImdbCache;
61
62
    /**
63
     * Imdb constructor.
64
     *
65
     * @param \mrcnpdlk\Xmdb\Client $oClient
66
     *
67
     * @throws \mrcnpdlk\Xmdb\Exception
68
     */
69
    public function __construct(Client $oClient)
70
    {
71
        try {
72
            $this->oClient                = $oClient;
73
            $this->oLog                   = $oClient->getLogger();
74
            $this->oCache                 = $oClient->getAdapter()->getCache();
75
            $this->oImdbCache             = new ImdbCache($this->oCache);
76
            $this->oConfig                = new Config();
77
            $this->oConfig->usecache      = null !== $this->oImdbCache;
78
            $this->oConfig->language      = $oClient->getLang();
79
            $this->oConfig->default_agent = UserAgent::random();
80
81
82
        } catch (\Exception $e) {
83
            throw new Exception(sprintf('Cannot create Tmdb Client'), 1, $e);
84
        }
85
    }
86
87
    /**
88
     * @param string $imdbId
89
     *
90
     * @return \Imdb\Title
91
     */
92
    protected function getApiTitle(string $imdbId): ApiTitle
93
    {
94
        return new ApiTitle($imdbId, $this->oConfig, $this->oLog, $this->oImdbCache);
95
    }
96
97
    /**
98
     * @return \Imdb\TitleSearch
99
     */
100
    protected function getApiTitleSearch(): TitleSearch
101
    {
102
        return new ApiTitleSearch($this->oConfig, $this->oLog, $this->oImdbCache);
103
    }
104
105
    /**
106
     * @param string $imdbId
107
     *
108
     * @return \mrcnpdlk\Xmdb\Rating
109
     * @throws \mrcnpdlk\Xmdb\Exception
110
     */
111
    public function getRating(string $imdbId): Rating
112
    {
113
        try {
114
            $searchUrl = 'http://p.media-imdb.com/static-content/documents/v1/title/'
115
                . $imdbId
116
                . '/ratings%3Fjsonp=imdb.rating.run:imdb.api.title.ratings/data.json?u='
117
                . $this->oClient->getImdbUser();
118
119
            $oResp = $this->oClient->getAdapter()->useCache(
120
                function () use ($searchUrl) {
121
                    $oCurl = new Curl();
122
                    $oCurl->setOpt(\CURLOPT_ENCODING, 'gzip');
123
                    $oCurl->setUserAgent(UserAgent::random());
124
                    $oCurl->setHeader('Accept-Language', $this->oClient->getLang());
125
                    $oCurl->get($searchUrl);
126
127
                    if ($oCurl->error) {
128
                        throw new \RuntimeException('Curl Error! ' . Http::message($oCurl->httpStatusCode), $oCurl->error_code);
129
                    }
130
131
                    preg_match("/^[\w\.]*\((.*)\)$/", $oCurl->response, $output_array);
132
                    $json = new JSON();
133
134
                    return $json->decode($output_array[1]);
135
                },
136
                [$searchUrl, $this->oClient->getLang()],
137
                180)
138
            ;
139
140
            if (!isset($oResp->resource)) {
141
                throw new \RuntimeException('Resource is empty');
142
            }
143
144
            $oData = $oResp->resource;
145
146
            $oRating         = new Rating();
147
            $oRating->id     = $imdbId;
148
            $oRating->title  = $oData->title;
149
            $oRating->year   = $oData->year;
150
            $oRating->rating = $oData->rating;
151
            $oRating->votes  = $oData->ratingCount;
152
            $oRating->type   = $oData->titleType;
153
154
            return $oRating;
155
156
        } catch (\Exception $e) {
157
            throw new Exception(sprintf('Item [%s] not found, reason: %s', $imdbId, $e->getMessage()));
158
        }
159
    }
160
161
    /**
162
     * Combined search by title
163
     *
164
     * @param string                          $title
165
     * @param int|null                        $limit
166
     * @param \mrcnpdlk\Xmdb\Model\Ratio|null $oRatio
167
     *
168
     * @return Title[]
169
     */
170
    public function searchByTitle(string $title, int $limit = null, Ratio $oRatio = null): array
171
    {
172
        /**
173
         * @var Title[] $answer
174
         * @var Title[] $tmpList
175
         */
176
        $answer      = [];
177
        $tmpList     = [];
178
        $tNativeList = $this->searchByTitleNative($title);
179
        foreach ($tNativeList as $item) {
180
            $tmpList[$item->imdbId] = $item;
181
        }
182
183
        $tApiList = $this->searchByTitleApi($title);
184
        foreach ($tApiList as $item) {
185
            if (!\array_key_exists($item->imdbId, $tmpList)) {
186
                $tmpList[$item->imdbId] = $item;
187
            } else {
188
                $tmpList[$item->imdbId]->isMovie = $tmpList[$item->imdbId]->isMovie ?? $item->isMovie;
189
                $tmpList[$item->imdbId]->type    = $tmpList[$item->imdbId]->type ?? $item->type;
190
            }
191
        }
192
193
        foreach ($tmpList as $item) {
194
            $answer[] = $item;
195
        }
196
197
        if ($oRatio) {
198
            $oRatio->calculateRatio($answer);
199
            $answer = [];
200
            foreach ($oRatio->items as $oRatioElement) {
201
                $answer[] = $oRatioElement->item;
202
            }
203
204
        }
205
206
        return $limit === null ? $answer : \array_slice($answer, 0, $limit);
207
    }
208
209
    /**
210
     * @param string $title
211
     *
212
     * @return Title[]
213
     */
214
    public function searchByTitleApi(string $title): array
215
    {
216
        try {
217
            $answer = [];
218
            $tList  = $this->getApiTitleSearch()->search($title, [
219
                TitleSearch::MOVIE,
220
                TitleSearch::TV_SERIES,
221
                TitleSearch::VIDEO,
222
                TitleSearch::TV_MOVIE,
223
            ])
224
            ;
225
226
            foreach ($tList as $element) {
227
                $oTitle                   = new Title();
228
                $oTitle->imdbId           = 'tt' . $element->imdbid();
229
                $oTitle->title            = $element->title();
230
                $oTitle->titleOrg         = $element->title();
231
                $oTitle->rating           = null; //set null for speedy
232
                $oTitle->episode          = null;
233
                $oTitle->releaseYear      = empty($element->year()) ? null : $element->year();
234
                $oTitle->type             = $element->movietype();
235
                $oTitle->isMovie          = \in_array($element->movietype(),
236
                    [TitleSearch::MOVIE, TitleSearch::TV_MOVIE, TitleSearch::VIDEO],
237
                    true);
238
                $oTitle->directors        = [];
239
                $oTitle->directorsDisplay = implode(', ', $oTitle->directors);
240
                $oTitle->actors           = [];
241
                $oTitle->actorsDisplay    = implode(', ', $oTitle->actors);
242
                $answer[]                 = $oTitle;
243
            }
244
245
            return $answer;
246
        } catch (\Exception $e) {
247
            $this->oLog->warning(sprintf('Item [%s] not found, reason: %s', $title, $e->getMessage()));
248
249
            return [];
250
        }
251
    }
252
253
    /**
254
     * @param string $title
255
     *
256
     * @return Title[]
257
     */
258
    public function searchByTitleNative(string $title): array
259
    {
260
        try {
261
            /**
262
             * @var Title[] $answer
263
             */
264
            $answer    = [];
265
            $params    = [
266
                'sort'  => 'moviemeter,asc',
267
                'view'  => 'advanced',
268
                'title' => $title,
269
            ];
270
            $searchUrl = 'http://www.imdb.com/search/title?' . http_build_query($params);
271
272
            $htmlContent = $this->oClient->getAdapter()->useCache(
273
                function () use ($searchUrl) {
274
                    $oCurl = new Curl();
275
                    $oCurl->setHeader('Accept-Language', $this->oClient->getLang());
276
277
                    return $oCurl->get($searchUrl);
278
                },
279
                [$searchUrl, $this->oClient->getLang()],
280
                3600 * 2)
281
            ;
282
283
            $html = HtmlDomParser::str_get_html($htmlContent);
284
            if (!$html) {
285
                throw new \RuntimeException('Response from IMDB malformed!');
286
            }
287
            $listerItemNode = $html->find('div.lister-item');
288
289
            foreach ($listerItemNode as $element) {
290
                $content = $element->find('div.lister-item-content', 0);
291
                if (!$content) {
292
                    throw new \RuntimeException('Empty search result!');
293
                }
294
                $titleNode = $content->find('h3.lister-item-header a', 0);
295
                if ($titleNode) {
296
                    $foundTitle = trim($titleNode->text());
297
                } else {
298
                    throw new \RuntimeException('Title not found!');
299
                }
300
                $yearNode = $content->find('h3.lister-item-header .lister-item-year', 0);
301
                if ($yearNode) {
302
                    $foundYear = trim($yearNode->text());
303
                    preg_match("/^\(?([0-9\-\–\s]+)([\w\s]*)?\)?$/u", $foundYear, $yearOut);
304
                    $foundYear = isset($yearOut[1]) ? trim($yearOut[1]) : $foundYear;
305
                    $foundType = isset($yearOut[2]) && trim($yearOut[2]) ? trim($yearOut[2]) : null;
306
                } else {
307
                    $foundYear = null;
308
                    $foundType = null;
309
                }
310
                $episodeNode  = $content->find('h3.lister-item-header a', 1);
311
                $foundEpisode = null;
312
                if ($episodeNode) {
313
                    $foundEpisode = trim($episodeNode->text());
314
                }
315
                $ratingNode  = $content->find('div.ratings-bar div.ratings-imdb-rating', 0);
316
                $foundRating = null;
317
                if ($ratingNode) {
318
                    $foundRating = trim($ratingNode->text());
319
                }
320
321
                $metascoreNode  = $content->find('div.ratings-bar div.ratings-metascore', 0);
322
                $foundMetascore = null;
323
                if ($metascoreNode) {
324
                    $oSpanNode      = $metascoreNode->find('span', 0);
325
                    $foundMetascore = $oSpanNode ? (int)$oSpanNode->text() : null;
326
                }
327
328
                $idNode = $element->find('div.lister-top-right div.ribbonize', 0);
329
                $imdbId = null;
330
                if ($idNode) {
331
                    $imdbId = $idNode->getAttribute('data-tconst');
332
                }
333
334
                $directors   = [];
335
                $stars       = [];
336
                $personsNode = $content->find('p', 2);
337
                if ($personsNode) {
338
                    $persons = $personsNode->find('a');
339
                    if (\is_array($persons)) {
340
                        foreach ($persons as $person) {
341
                            if (strpos($person->getAttribute('href'), 'adv_li_dr_') !== false) {
342
                                $directors[] = $person->text();
343
                            }
344
                            if (strpos($person->getAttribute('href'), 'adv_li_st_') !== false) {
345
                                $stars[] = $person->text();
346
                            }
347
                        }
348
                    }
349
                }
350
351
                if ($imdbId) {
352
                    $oTitle                   = new Title();
353
                    $oTitle->title            = $foundTitle;
354
                    $oTitle->titleOrg         = $foundTitle;
355
                    $oTitle->imdbId           = $imdbId;
356
                    $oTitle->rating           = $foundRating;
0 ignored issues
show
Documentation Bug introduced by
It seems like $foundRating can also be of type string. However, the property $rating is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
357
                    $oTitle->metascore        = $foundMetascore;
358
                    $oTitle->episode          = $foundEpisode;
359
                    $oTitle->releaseYear      = $foundYear;
360
                    $oTitle->type             = $foundType;
361
                    $oTitle->directors        = $directors;
362
                    $oTitle->directorsDisplay = implode(', ', $oTitle->directors);
363
                    $oTitle->actors           = $stars;
364
                    $oTitle->actorsDisplay    = implode(', ', $oTitle->actors);
365
366
                    $answer[] = $oTitle;
367
                }
368
369
            }
370
371
            return $answer;
372
        } catch (\Exception $e) {
373
            $this->oLog->warning(sprintf('Item [%s] not found, reason: %s', $title, $e->getMessage()));
374
375
            return [];
376
        }
377
    }
378
}
379