Imdb::searchByTitleNative()   F
last analyzed

Complexity

Conditions 21
Paths 14471

Size

Total Lines 120
Code Lines 84

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 462

Importance

Changes 0
Metric Value
dl 0
loc 120
ccs 0
cts 105
cp 0
rs 2
c 0
b 0
f 0
cc 21
eloc 84
nc 14471
nop 1
crap 462

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
    /**
59
     * Imdb constructor.
60
     *
61
     * @param \mrcnpdlk\Xmdb\Client $oClient
62
     *
63
     * @throws \mrcnpdlk\Xmdb\Exception
64
     */
65
    public function __construct(Client $oClient)
66
    {
67
        try {
68
            $this->oClient                = $oClient;
69
            $this->oLog                   = $oClient->getLogger();
70
            $this->oCache                 = $oClient->getAdapter()->getCache();
71
            $this->oConfig                = new Config();
72
            $this->oConfig->usecache      = null !== $this->oCache;
73
            $this->oConfig->language      = $oClient->getLang();
74
            $this->oConfig->default_agent = UserAgent::random();
75
76
77
        } catch (\Exception $e) {
78
            throw new Exception(sprintf('Cannot create Imdb Client'), 1, $e);
79
        }
80
    }
81
82
    /**
83
     * @param string $imdbId
84
     *
85
     * @return \Imdb\Title
86
     */
87
    protected function getApiTitle(string $imdbId): ApiTitle
88
    {
89
        return new ApiTitle($imdbId, $this->oConfig, $this->oLog, $this->oCache);
90
    }
91
92
    /**
93
     * @return \Imdb\TitleSearch
94
     */
95
    protected function getApiTitleSearch(): TitleSearch
96
    {
97
        return new ApiTitleSearch($this->oConfig, $this->oLog, $this->oCache);
98
    }
99
100
    /**
101
     * @param string $imdbId
102
     *
103
     * @return \mrcnpdlk\Xmdb\Model\Imdb\Rating
104
     * @throws \mrcnpdlk\Xmdb\Exception
105
     */
106
    public function getRating(string $imdbId): Rating
107
    {
108
        try {
109
            $searchUrl = 'http://p.media-imdb.com/static-content/documents/v1/title/'
110
                . $imdbId
111
                . '/ratings%3Fjsonp=imdb.rating.run:imdb.api.title.ratings/data.json?u='
112
                . $this->oClient->getImdbUser();
113
114
            $oResp = $this
115
                ->oClient
116
                ->getAdapter()
117
                ->useCache(
118
                    function () use ($searchUrl) {
119
                        $oCurl = new Curl();
120
                        $oCurl->setOpt(\CURLOPT_ENCODING, 'gzip');
121
                        $oCurl->setUserAgent(UserAgent::random());
122
                        $oCurl->setHeader('Accept-Language', $this->oClient->getLang());
123
                        $oCurl->get($searchUrl);
124
125
                        if ($oCurl->error) {
126
                            throw new \RuntimeException('Curl Error! ' . Http::message($oCurl->httpStatusCode), $oCurl->error_code);
127
                        }
128
129
                        preg_match("/^[\w\.]*\((.*)\)$/", $oCurl->response, $output_array);
130
                        $json = new JSON();
131
132
                        return $json->decode($output_array[1]);
133
                    },
134
                    [$searchUrl, $this->oClient->getLang()],
135
                    180)
136
            ;
137
138
            if (!isset($oResp->resource)) {
139
                throw new \RuntimeException('Resource is empty');
140
            }
141
142
            $oData = $oResp->resource;
143
144
            $oRating         = new Rating();
145
            $oRating->id     = $imdbId;
146
            $oRating->title  = $oData->title;
147
            $oRating->year   = $oData->year;
148
            $oRating->rating = $oData->rating;
149
            $oRating->votes  = $oData->ratingCount;
150
            $oRating->type   = $oData->titleType;
151
152
            return $oRating;
153
154
        } catch (\Exception $e) {
155
            throw new Exception(sprintf('Item [%s] not found, reason: %s', $imdbId, $e->getMessage()));
156
        }
157
    }
158
159
    /**
160
     * Combined search by title
161
     *
162
     * @param string                          $title
163
     * @param int|null                        $limit
164
     * @param \mrcnpdlk\Xmdb\Model\Ratio|null $oRatio
165
     *
166
     * @return Title[]
167
     */
168
    public function searchByTitle(string $title, int $limit = null, Ratio $oRatio = null): array
169
    {
170
        /**
171
         * @var Title[] $answer
172
         * @var Title[] $tmpList
173
         */
174
        $answer      = [];
175
        $tmpList     = [];
176
        $tNativeList = $this->searchByTitleNative($title);
177
        foreach ($tNativeList as $item) {
178
            $tmpList[$item->imdbId] = $item;
179
        }
180
181
        $tApiList = $this->searchByTitleApi($title);
182
        foreach ($tApiList as $item) {
183
            if (!\array_key_exists($item->imdbId, $tmpList)) {
184
                $tmpList[$item->imdbId] = $item;
185
            } else {
186
                $tmpList[$item->imdbId]->isMovie = $tmpList[$item->imdbId]->isMovie ?? $item->isMovie;
187
                $tmpList[$item->imdbId]->type    = $tmpList[$item->imdbId]->type ?? $item->type;
188
            }
189
        }
190
191
        foreach ($tmpList as $item) {
192
            $answer[] = $item;
193
        }
194
195
        if ($oRatio) {
196
            $oRatio->calculateRatio($answer);
197
            $answer = [];
198
            foreach ($oRatio->items as $oRatioElement) {
199
                $answer[] = $oRatioElement->item;
200
            }
201
202
        }
203
204
        return $limit === null ? $answer : \array_slice($answer, 0, $limit);
205
    }
206
207
    /**
208
     * @param string $title
209
     *
210
     * @return Title[]
211
     */
212
    public function searchByTitleApi(string $title): array
213
    {
214
        try {
215
            $answer = [];
216
            $tList  = $this->getApiTitleSearch()->search($title, [
217
                TitleSearch::MOVIE,
218
                TitleSearch::TV_SERIES,
219
                TitleSearch::VIDEO,
220
                TitleSearch::TV_MOVIE,
221
            ])
222
            ;
223
224
            foreach ($tList as $element) {
225
                $oTitle                   = new Title();
226
                $oTitle->imdbId           = 'tt' . $element->imdbid();
227
                $oTitle->title            = $element->title();
228
                $oTitle->titleOrg         = $element->title();
229
                $oTitle->rating           = null; //set null for speedy
230
                $oTitle->episode          = null;
231
                $oTitle->releaseYear      = empty($element->year()) ? null : $element->year();
232
                $oTitle->type             = $element->movietype();
233
                $oTitle->isMovie          = \in_array($element->movietype(),
234
                    [TitleSearch::MOVIE, TitleSearch::TV_MOVIE, TitleSearch::VIDEO],
235
                    true);
236
                $oTitle->directors        = [];
237
                $oTitle->directorsDisplay = implode(', ', $oTitle->directors);
238
                $oTitle->actors           = [];
239
                $oTitle->actorsDisplay    = implode(', ', $oTitle->actors);
240
                $answer[]                 = $oTitle;
241
            }
242
243
            return $answer;
244
        } catch (\Exception $e) {
245
            $this->oLog->warning(sprintf('Item [%s] not found, reason: %s', $title, $e->getMessage()));
246
247
            return [];
248
        }
249
    }
250
251
    /**
252
     * @param string $title
253
     *
254
     * @return Title[]
255
     */
256
    public function searchByTitleNative(string $title): array
257
    {
258
        try {
259
            /**
260
             * @var Title[] $answer
261
             */
262
            $answer    = [];
263
            $params    = [
264
                'sort'  => 'moviemeter,asc',
265
                'view'  => 'advanced',
266
                'title' => $title,
267
            ];
268
            $searchUrl = 'http://www.imdb.com/search/title?' . http_build_query($params);
269
270
            $htmlContent = $this->oClient->getAdapter()->useCache(
271
                function () use ($searchUrl) {
272
                    $oCurl = new Curl();
273
                    $oCurl->setHeader('Accept-Language', $this->oClient->getLang());
274
275
                    return $oCurl->get($searchUrl);
276
                },
277
                [$searchUrl, $this->oClient->getLang()],
278
                3600 * 2)
279
            ;
280
281
            $html = HtmlDomParser::str_get_html($htmlContent);
282
            if (!$html) {
283
                throw new \RuntimeException('Response from IMDB malformed!');
284
            }
285
            $listerItemNode = $html->find('div.lister-item');
286
287
            foreach ($listerItemNode as $element) {
288
                $content = $element->find('div.lister-item-content', 0);
289
                if (!$content) {
290
                    throw new \RuntimeException('Empty search result!');
291
                }
292
                $titleNode = $content->find('h3.lister-item-header a', 0);
293
                if ($titleNode) {
294
                    $foundTitle = trim($titleNode->text());
295
                } else {
296
                    throw new \RuntimeException('Title not found!');
297
                }
298
                $yearNode = $content->find('h3.lister-item-header .lister-item-year', 0);
299
                if ($yearNode) {
300
                    $foundYear = trim($yearNode->text());
301
                    preg_match("/^\(?([0-9\-\–\s]+)([\w\s]*)?\)?$/u", $foundYear, $yearOut);
302
                    $foundYear = isset($yearOut[1]) ? trim($yearOut[1]) : $foundYear;
303
                    $foundType = isset($yearOut[2]) && trim($yearOut[2]) ? trim($yearOut[2]) : null;
304
                } else {
305
                    $foundYear = null;
306
                    $foundType = null;
307
                }
308
                $episodeNode  = $content->find('h3.lister-item-header a', 1);
309
                $foundEpisode = null;
310
                if ($episodeNode) {
311
                    $foundEpisode = trim($episodeNode->text());
312
                }
313
                $ratingNode  = $content->find('div.ratings-bar div.ratings-imdb-rating', 0);
314
                $foundRating = null;
315
                if ($ratingNode) {
316
                    $foundRating = trim($ratingNode->text());
317
                }
318
319
                $metascoreNode  = $content->find('div.ratings-bar div.ratings-metascore', 0);
320
                $foundMetascore = null;
321
                if ($metascoreNode) {
322
                    $oSpanNode      = $metascoreNode->find('span', 0);
323
                    $foundMetascore = $oSpanNode ? (int)$oSpanNode->text() : null;
324
                }
325
326
                $idNode = $element->find('div.lister-top-right div.ribbonize', 0);
327
                $imdbId = null;
328
                if ($idNode) {
329
                    $imdbId = $idNode->getAttribute('data-tconst');
330
                }
331
332
                $directors   = [];
333
                $stars       = [];
334
                $personsNode = $content->find('p', 2);
335
                if ($personsNode) {
336
                    $persons = $personsNode->find('a');
337
                    if (\is_array($persons)) {
338
                        foreach ($persons as $person) {
339
                            if (strpos($person->getAttribute('href'), 'adv_li_dr_') !== false) {
340
                                $directors[] = $person->text();
341
                            }
342
                            if (strpos($person->getAttribute('href'), 'adv_li_st_') !== false) {
343
                                $stars[] = $person->text();
344
                            }
345
                        }
346
                    }
347
                }
348
349
                if ($imdbId) {
350
                    $oTitle                   = new Title();
351
                    $oTitle->title            = $foundTitle;
352
                    $oTitle->titleOrg         = $foundTitle;
353
                    $oTitle->imdbId           = $imdbId;
354
                    $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...
355
                    $oTitle->metascore        = $foundMetascore;
356
                    $oTitle->episode          = $foundEpisode;
357
                    $oTitle->releaseYear      = $foundYear;
358
                    $oTitle->type             = $foundType;
359
                    $oTitle->directors        = $directors;
360
                    $oTitle->directorsDisplay = implode(', ', $oTitle->directors);
361
                    $oTitle->actors           = $stars;
362
                    $oTitle->actorsDisplay    = implode(', ', $oTitle->actors);
363
364
                    $answer[] = $oTitle;
365
                }
366
367
            }
368
369
            return $answer;
370
        } catch (\Exception $e) {
371
            $this->oLog->warning(sprintf('Item [%s] not found, reason: %s', $title, $e->getMessage()));
372
373
            return [];
374
        }
375
    }
376
}
377