Passed
Push — master ( e7fa4f...1e0739 )
by Alexey
04:03
created

AppInfoScraper::__invoke()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 105
Code Lines 88

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 86
CRAP Score 5.0002

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 88
dl 0
loc 105
ccs 86
cts 88
cp 0.9773
rs 7.9507
c 1
b 1
f 0
cc 5
nc 9
nop 3
crap 5.0002

How to fix   Long Method   

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
declare(strict_types=1);
4
5
/*
6
 * Copyright (c) Ne-Lexa
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 *
11
 * @see https://github.com/Ne-Lexa/google-play-scraper
12
 */
13
14
namespace Nelexa\GPlay\Scraper;
15
16
use GuzzleHttp\Psr7\Query;
17
use Nelexa\GPlay\Exception\GooglePlayException;
18
use Nelexa\GPlay\GPlayApps;
19
use Nelexa\GPlay\HttpClient\ParseHandlerInterface;
20
use Nelexa\GPlay\Model\AppId;
21
use Nelexa\GPlay\Model\AppInfo;
22
use Nelexa\GPlay\Model\Category;
23
use Nelexa\GPlay\Model\Developer;
24
use Nelexa\GPlay\Model\GoogleImage;
25
use Nelexa\GPlay\Model\HistogramRating;
26
use Nelexa\GPlay\Model\Review;
27
use Nelexa\GPlay\Model\Video;
28
use Nelexa\GPlay\Scraper\Extractor\ReviewsExtractor;
29
use Nelexa\GPlay\Util\DateStringFormatter;
30
use Nelexa\GPlay\Util\ScraperUtil;
31
use Psr\Http\Message\RequestInterface;
32
use Psr\Http\Message\ResponseInterface;
33
34
/**
35
 * @internal
36
 */
37
class AppInfoScraper implements ParseHandlerInterface
38
{
39
    /**
40
     * @param RequestInterface  $request
41
     * @param ResponseInterface $response
42
     * @param array             $options
43
     *
44
     * @throws \Nelexa\GPlay\Exception\GooglePlayException
45
     *
46
     * @return AppInfo
47
     */
48 10
    public function __invoke(RequestInterface $request, ResponseInterface $response, array &$options = []): AppInfo
49
    {
50 10
        $scriptData = ScraperUtil::extractScriptData($response->getBody()->getContents());
51
52 10
        $appInfo = ScraperUtil::getValue($scriptData, 'ds:4.1.2');
53 10
        if (!\is_array($appInfo)) {
54
            throw (new GooglePlayException('Unable to get data for this application.'))->setUrl(
55
                $request->getUri()->__toString()
56
            );
57
        }
58
59 10
        $query = Query::parse($request->getUri()->getQuery());
60 10
        $id = $query[GPlayApps::REQ_PARAM_ID];
61 10
        $locale = $query[GPlayApps::REQ_PARAM_LOCALE] ?? GPlayApps::DEFAULT_LOCALE;
62 10
        $country = $query[GPlayApps::REQ_PARAM_COUNTRY] ?? GPlayApps::DEFAULT_COUNTRY;
63
64 10
        $name = $appInfo[0][0];
65 10
        $description = ScraperUtil::html2text($appInfo[72][0][1]);
66 10
        $developer = $this->extractDeveloper($appInfo);
67
68 10
        $category = $this->extractCategory($appInfo[79][0][0] ?? []);
69 10
        $summary = $this->extractSummary($appInfo);
70 10
        $installsText = $appInfo[13][0] ?? null;
71 10
        $installs = $appInfo[13][2] ?? 0;
72 10
        $score = (float) ($appInfo[51][0][1] ?? 0);
73 10
        $numberVoters = (int) ($appInfo[51][2][1] ?? 0);
74 10
        $numberReviews = (int) ($appInfo[51][3][1] ?? 0);
75 10
        $histogramRating = null;
76 10
        if (isset($appInfo[51][1])) {
77 10
            $histogramRating = $this->extractHistogramRating($appInfo[51][1]);
78
        }
79
80 10
        $scriptDataPrice = $appInfo[57][0][0][0][0][1] ?? null;
81 10
        $price = $scriptDataPrice !== null ? $this->extractPrice($scriptDataPrice) : 0.0;
82 10
        $currency = $scriptDataPrice[0][1] ?? 'USD';
83 10
        $priceText = $scriptDataPrice[0][2] ?? null;
84
85 10
        $offersIAPCost = $appInfo[19][0] ?? null;
86 10
        $containsAds = (bool) ($appInfo[48][0] ?? false);
87
88 10
        $androidVersion = $appInfo[140][1][1][0][0][1] ?? null;
89 10
        $appVersion = $appInfo[140][0][0][0] ?? null;
90
91 10
        if ($androidVersion !== null) {
92 4
            $minAndroidVersion = preg_replace('~.*?(\d+(\.\d+)*).*~', '$1', $androidVersion);
93
        } else {
94 7
            $minAndroidVersion = null;
95
        }
96
97 10
        $editorsChoice = (bool) ScraperUtil::getValue($appInfo, 'ds:5.1.2.136.0.1.0');
98 10
        $privacyPoliceUrl = $appInfo[99][0][5][2] ?? '';
99 10
        $categoryFamily = $this->extractCategory($appInfo[118][0][0][0] ?? []);
100 10
        $icon = $this->extractIcon($appInfo);
101 10
        $cover = $this->extractCover($appInfo);
102 10
        $screenshots = $this->extractScreenshots($appInfo);
103 10
        $video = $this->extractVideo($appInfo);
104 10
        $contentRating = $appInfo[111][1] ?? '';
105 10
        $released = $this->convertDate($appInfo[10][1][0] ?? null);
106 10
        $updated = $this->convertDate($appInfo[145][0][1][0] ?? null);
107 10
        $recentChanges = $this->extractRecentChanges($appInfo);
108
109 10
        $reviewsInfo = ScraperUtil::getValue($scriptData, 'ds:8.0');
110 10
        $reviews = $this->extractReviews(new AppId($id, $locale, $country), $reviewsInfo);
0 ignored issues
show
Bug introduced by
It seems like $reviewsInfo can also be of type null; however, parameter $data of Nelexa\GPlay\Scraper\App...raper::extractReviews() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

110
        $reviews = $this->extractReviews(new AppId($id, $locale, $country), /** @scrutinizer ignore-type */ $reviewsInfo);
Loading history...
111
112 10
        return AppInfo::newBuilder()
113 10
            ->setId($id)
114 10
            ->setLocale($locale)
115 10
            ->setCountry($country)
116 10
            ->setName($name)
117 10
            ->setDescription($description)
118 10
            ->setSummary($summary)
119 10
            ->setIcon($icon)
120 10
            ->setCover($cover)
121 10
            ->setScreenshots($screenshots)
122 10
            ->setDeveloper($developer)
123 10
            ->setCategory($category)
124 10
            ->setCategoryFamily($categoryFamily)
125 10
            ->setVideo($video)
126 10
            ->setRecentChanges($recentChanges)
127 10
            ->setEditorsChoice($editorsChoice)
128 10
            ->setPrivacyPoliceUrl($privacyPoliceUrl)
129 10
            ->setInstalls($installs)
130 10
            ->setInstallsText($installsText)
131 10
            ->setScore($score)
132 10
            ->setRecentChanges($recentChanges)
133 10
            ->setEditorsChoice($editorsChoice)
134 10
            ->setPrivacyPoliceUrl($privacyPoliceUrl)
135 10
            ->setInstalls($installs)
136 10
            ->setScore($score)
137 10
            ->setNumberVoters($numberVoters)
138 10
            ->setHistogramRating($histogramRating)
139 10
            ->setPrice($price)
140 10
            ->setCurrency($currency)
141 10
            ->setPriceText($priceText)
142 10
            ->setOffersIAPCost($offersIAPCost)
143 10
            ->setContainsAds($containsAds)
144 10
            ->setAppVersion($appVersion)
145 10
            ->setAndroidVersion($androidVersion)
146 10
            ->setMinAndroidVersion($minAndroidVersion)
147 10
            ->setContentRating($contentRating)
148 10
            ->setReleased($released)
149 10
            ->setUpdated($updated)
150 10
            ->setNumberReviews($numberReviews)
151 10
            ->setReviews($reviews)
152 10
            ->buildDetailInfo()
153
        ;
154
    }
155
156
    /**
157
     * @param array $appInfo
158
     *
159
     * @return string|null
160
     */
161 10
    private function extractSummary(array $appInfo): ?string
162
    {
163 10
        return empty($appInfo[73][0][1])
164
            ? null
165 10
            : ScraperUtil::html2text(str_replace("\n", ' ', $appInfo[73][0][1]));
166
    }
167
168
    /**
169
     * @param array $appInfo
170
     *
171
     * @return Developer
172
     */
173 10
    private function extractDeveloper(array $appInfo): Developer
174
    {
175 10
        $developerPage = GPlayApps::GOOGLE_PLAY_URL . $appInfo[68][1][4][2];
176 10
        $developerId = Query::parse(parse_url($developerPage, \PHP_URL_QUERY))[GPlayApps::REQ_PARAM_ID];
177 10
        $developerName = $appInfo[68][0];
178 10
        $developerEmail = $appInfo[69][1][0] ?? null;
179 10
        $developerWebsite = $appInfo[69][0][5][2] ?? null;
180 10
        $developerAddress = $appInfo[69][2][0] ?? null;
181
182 10
        return new Developer(
183 10
            Developer::newBuilder()
184 10
                ->setId($developerId)
185 10
                ->setUrl($developerPage)
186 10
                ->setName($developerName)
187 10
                ->setEmail($developerEmail)
188 10
                ->setAddress($developerAddress)
189 10
                ->setWebsite($developerWebsite)
190
        );
191
    }
192
193
    /**
194
     * @param array $data
195
     *
196
     * @return Category|null
197
     */
198 10
    private function extractCategory(array $data): ?Category
199
    {
200 10
        if (isset($data[1][4][2], $data[0], $data[2])) {
201 10
            $categorySlug = (string) $data[2];
202 10
            $categoryName = (string) $data[0];
203
204 10
            return new Category($categorySlug, $categoryName);
205
        }
206
207 9
        return null;
208
    }
209
210
    /**
211
     * @param array|null $data
212
     *
213
     * @return HistogramRating
214
     */
215 10
    private function extractHistogramRating(array $data): HistogramRating
216
    {
217 10
        return new HistogramRating(
218 10
            $data[1][1] ?? 0,
219 10
            $data[2][1] ?? 0,
220 10
            $data[3][1] ?? 0,
221 10
            $data[4][1] ?? 0,
222 10
            $data[5][1] ?? 0
223
        );
224
    }
225
226
    /**
227
     * @param array $scriptDataPrice
228
     *
229
     * @return float
230
     */
231 10
    protected function extractPrice(array $scriptDataPrice): ?float
232
    {
233 10
        return isset($scriptDataPrice[0][0])
234 10
            ? (float) ($scriptDataPrice[0][0] / 1000000)
235 10
            : 0.0;
236
    }
237
238
    /**
239
     * @param array $data
240
     *
241
     * @return GoogleImage|null
242
     */
243 10
    protected function extractIcon(array $data): ?GoogleImage
244
    {
245 10
        return empty($data[95][0][3][2])
246
            ? null
247 10
            : new GoogleImage($data[95][0][3][2]);
248
    }
249
250
    /**
251
     * @param array $data
252
     *
253
     * @return GoogleImage|null
254
     */
255 10
    protected function extractCover(array $data): ?GoogleImage
256
    {
257 10
        return empty($data[96][0][3][2])
258
            ? null
259 10
            : new GoogleImage($data[96][0][3][2]);
260
    }
261
262
    /**
263
     * @param array $data
264
     *
265
     * @return GoogleImage[]
266
     */
267 10
    private function extractScreenshots(array $data): array
268
    {
269 10
        return !empty($data[78][0][0][3][2]) ? array_map(
270 10
            static function (array $v) {
271 10
                return new GoogleImage($v[3][2]);
272
            },
273 10
            $data[78][0]
274 10
        ) : [];
275
    }
276
277
    /**
278
     * @param array $data
279
     *
280
     * @return Video|null
281
     */
282 10
    private function extractVideo(array $data): ?Video
283
    {
284 10
        if (isset($data[100][0][0][4], $data[100][0][1][3][3])) {
285
            $videoThumb = (string) $data[100][0][1][3][2];
286
            $youtubeId = (string) $data[100][0][4];
287
            $youtubeId = str_replace('yt:', '', strtok($youtubeId, '?'));
288
            $videoUrl = 'https://www.youtube.com/embed/' . $youtubeId . '?ps=play&vq=large&rel=0&autohide=1&showinfo=0';
289
290
            return new Video($videoThumb, $videoUrl);
291
        }
292
293 10
        return null;
294
    }
295
296
    /**
297
     * @param ?int $timestamp
298
     *
299
     * @return \DateTimeInterface|null
300
     */
301 10
    private function convertDate(?int $timestamp): ?\DateTimeInterface
302
    {
303 10
        if ($timestamp !== null) {
304 10
            return DateStringFormatter::unixTimeToDateTime($timestamp);
305
        }
306
307 2
        return null;
308
    }
309
310
    /**
311
     * @param array $data
312
     *
313
     * @return string|null
314
     */
315 10
    protected function extractRecentChanges(array $data): ?string
316
    {
317 10
        return empty($data[144][1][1])
318 2
            ? null
319 10
            : ScraperUtil::html2text($data[144][1][1]);
320
    }
321
322
    /**
323
     * @param AppId $appId
324
     * @param array $data
325
     *
326
     * @return Review[]
327
     */
328 10
    private function extractReviews(AppId $appId, array $data): array
329
    {
330 10
        if (empty($data[0])) {
331 1
            return [];
332
        }
333
334 10
        return ReviewsExtractor::extractReviews($appId, $data);
335
    }
336
}
337