Passed
Branch feature/refactoring (13cbf0)
by Alexey
03:46
created

AppInfo   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 550
Duplicated Lines 0 %

Test Coverage

Coverage 74%

Importance

Changes 0
Metric Value
wmc 46
eloc 146
c 0
b 0
f 0
dl 0
loc 550
ccs 111
cts 150
cp 0.74
rs 8.72

31 Methods

Rating   Name   Duplication   Size   Complexity  
A isAutoTranslatedDescription() 0 3 1
A getReleased() 0 3 1
A getContentRating() 0 3 1
A getAppVersion() 0 3 1
A getPrivacyPoliceUrl() 0 3 1
A getRecentChanges() 0 3 1
A getCategory() 0 3 1
A isContainsIAP() 0 3 1
A getSize() 0 3 1
A getCurrency() 0 3 1
A getInstalls() 0 3 1
A getCategoryFamily() 0 3 1
A getAndroidVersion() 0 3 1
A isContainsAds() 0 3 1
A getNumberVoters() 0 3 1
B asArray() 0 44 8
A getMinAndroidVersion() 0 3 1
A getPrice() 0 3 1
A equals() 0 30 6
A getScreenshots() 0 3 1
A isEditorsChoice() 0 3 1
A getTranslatedFromLocale() 0 3 1
A getVideo() 0 3 1
A getDescription() 0 3 1
A getNumberReviews() 0 3 1
A getReviews() 0 3 1
A __construct() 0 49 4
A getOffersIAPCost() 0 3 1
A getCover() 0 3 1
A getUpdated() 0 3 1
A getHistogramRating() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AppInfo 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.

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 AppInfo, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @author   Ne-Lexa
7
 * @license  MIT
8
 *
9
 * @see      https://github.com/Ne-Lexa/google-play-scraper
10
 */
11
12
namespace Nelexa\GPlay\Model;
13
14
use Nelexa\GPlay\GPlayApps;
15
use Nelexa\GPlay\Model\Builder\AppBuilder;
16
17
/**
18
 * Contains detailed information about the application from the Google Play store.
19
 *
20
 * @see App Basic information about the application from the Google Play store.
21
 * @see GPlayApps::getAppInfo() Returns detailed information about the Android
22
 *     application from the Google Play store.
23
 * @see GPlayApps::getAppsInfo() Returns detailed information about many android packages.
24
 * @see GPlayApps::getAppInLocales() Returns detailed information about an application
25
 *     from the Google Play store for an array of locales.
26
 * @see GPlayApps::getAppInfoForAvailableLocales() Returns detailed information about the
27
 *     application in all available locales.
28
 */
29
final class AppInfo extends App
30
{
31
    /** @var string Default currency. */
32
    private const DEFAULT_CURRENCY = 'USD';
33
34
    /** @var string Application description. */
35
    private $description;
36
37
    /**
38
     * Locale (language) of the original description. Google automatically translates
39
     * the description of the application if the developer has not added it to the
40
     * Play Console in the "Add your own translation text" section. If a translation
41
     * is added, the value will be null.
42
     *
43
     * @var string|null locale of the original description or null
44
     *
45
     * @see https://support.google.com/googleplay/android-developer/answer/3125566
46
     */
47
    private $translatedFromLocale;
48
49
    /** @var GoogleImage|null Cover image. */
50
    private $cover;
51
52
    /** @var GoogleImage[] Screenshots of the application. */
53
    private $screenshots;
54
55
    /** @var Category Application category. */
56
    private $category;
57
58
    /** @var Category|null Family category or null/ */
59
    private $categoryFamily;
60
61
    /** @var Video|null Promo video or null. */
62
    private $video;
63
64
    /** @var string|null Recent changes. */
65
    private $recentChanges;
66
67
    /** @var bool Editors' choice. */
68
    private $editorsChoice;
69
70
    /** @var int Number of application installations. */
71
    private $installs;
72
73
    /** @var HistogramRating Histogram rating. */
74
    private $histogramRating;
75
76
    /** @var float Price of the application in the Google Play store. */
77
    private $price;
78
79
    /** @var string $currency Currency price of the application. */
80
    private $currency;
81
82
    /** @var string|null In-App Purchase price. */
83
    private $offersIAPCost;
84
85
    /** @var bool Application contains ads. */
86
    private $containsAds;
87
88
    /** @var string|null Application size, null if the size depends on the device. */
89
    private $size;
90
91
    /** @var string|null Application version, null if the application version depends on the device. */
92
    private $appVersion;
93
94
    /** @var string|null Android version, null if android version depends on the device. */
95
    private $androidVersion;
96
97
    /** @var string|null Minimum android version, null if android version depends on the device. */
98
    private $minAndroidVersion;
99
100
    /** @var string|null Content rating. */
101
    private $contentRating;
102
103
    /** @var string|null Privacy policy URL. */
104
    private $privacyPoliceUrl;
105
106
    /** @var \DateTimeInterface|null Release date if known. */
107
    private $released;
108
109
    /** @var \DateTimeInterface|null Update date or null. */
110
    private $updated;
111
112
    /** @var int Number of voters. */
113
    private $numberVoters;
114
115
    /** @var int Number of reviews. */
116
    private $numberReviews;
117
118
    /** @var Review[] Some useful reviews. */
119
    private $reviews;
120
121
    /**
122
     * Returns an object with detailed information about the application.
123
     *
124
     * @param AppBuilder $builder application builder
125
     *
126
     * @ignore
127
     */
128 21
    public function __construct(AppBuilder $builder)
129
    {
130 21
        parent::__construct($builder);
131
132 21
        if (empty($builder->getDescription())) {
133 1
            throw new \InvalidArgumentException(
134 1
                'Application description cannot be null or empty. Solution: $appBuilder->setDescription(...);'
135
            );
136
        }
137
138 21
        if (empty($builder->getScreenshots())) {
139 1
            throw new \InvalidArgumentException(
140 1
                'Screenshots of the application must contain at least one screenshot. Solution: $appBuilder->setScreenshots(...); or $appBuilder->addScreenshot(...);'
141
            );
142
        }
143
144 21
        if ($builder->getCategory() === null) {
145 1
            throw new \InvalidArgumentException(
146 1
                'Application category cannot be null. Solution: $appBuilder->setCategory(...);'
147
            );
148
        }
149
150 21
        $this->description = $builder->getDescription();
151 21
        $this->translatedFromLocale = $builder->getTranslatedFromLocale();
152 21
        $this->cover = $builder->getCover();
153 21
        $this->screenshots = $builder->getScreenshots();
154 21
        $this->category = $builder->getCategory();
155 21
        $this->categoryFamily = $builder->getCategoryFamily();
156 21
        $this->privacyPoliceUrl = $builder->getPrivacyPoliceUrl();
157 21
        $this->video = $builder->getVideo();
158 21
        $this->recentChanges = $builder->getRecentChanges();
159 21
        $this->editorsChoice = $builder->isEditorsChoice();
160 21
        $this->installs = $builder->getInstalls();
161 21
        $this->histogramRating = $builder->getHistogramRating() ??
162 2
            new HistogramRating(0, 0, 0, 0, 0);
163 21
        $this->price = $builder->getPrice();
164 21
        $this->currency = $builder->getCurrency() ?? self::DEFAULT_CURRENCY;
165 21
        $this->offersIAPCost = $builder->getOffersIAPCost();
166 21
        $this->containsAds = $builder->isContainsAds();
167 21
        $this->size = $builder->getSize();
168 21
        $this->appVersion = $builder->getAppVersion();
169 21
        $this->androidVersion = $builder->getAndroidVersion();
170 21
        $this->minAndroidVersion = $builder->getMinAndroidVersion();
171 21
        $this->contentRating = $builder->getContentRating();
172 21
        $this->released = $builder->getReleased();
173 21
        $this->updated = $builder->getUpdated();
174 21
        $this->numberVoters = $builder->getNumberVoters();
175 21
        $this->numberReviews = $builder->getNumberReviews();
176 21
        $this->reviews = $builder->getReviews();
177 21
    }
178
179
    /**
180
     * Returns a description of the application.
181
     *
182
     * @return string description of the application
183
     */
184 1
    public function getDescription(): string
185
    {
186 1
        return $this->description;
187
    }
188
189
    /**
190
     * Checks if the class description is automatically translated via Google Translate.
191
     *
192
     * @return bool `true` if the description was automatically translated using Google Translate and
193
     *              `false` if the developer added a description for the locale in the Google Play Console
194
     */
195 13
    public function isAutoTranslatedDescription(): bool
196
    {
197 13
        return $this->translatedFromLocale !== null;
198
    }
199
200
    /**
201
     * Returns locale (language) of the original description.
202
     *
203
     * Google automatically translates the description of the application if the developer
204
     * has not added it to the Play Console in the "Add your own translation text" section.
205
     * If a translation is added, the value will be null.
206
     *
207
     * @return string|null if the developer added a translation of the description, then the
208
     *                     value will be `null`, otherwise the original language of the application description
209
     */
210 14
    public function getTranslatedFromLocale(): ?string
211
    {
212 14
        return $this->translatedFromLocale;
213
    }
214
215
    /**
216
     * Returns cover image.
217
     *
218
     * **Where it's displayed**
219
     * The feature graphic is displayed in front of screenshots of the application and in
220
     * the list of developer applications. If a promo video is added, a **Play** button
221
     * will overlay on the feature graphic so users can watch the promo video.
222
     *
223
     * Google Play requirements:
224
     * * JPEG or 24-bit PNG (no alpha)
225
     * * Dimensions: 1024px by 500px
226
     *
227
     * @return GoogleImage|null cover image or `null`
228
     *
229
     * @see https://support.google.com/googleplay/android-developer/answer/1078870?hl=en Graphic assets,
230
     *     screenshots, & video. Section **Feature graphic**.
231
     */
232 1
    public function getCover(): ?GoogleImage
233
    {
234 1
        return $this->cover;
235
    }
236
237
    /**
238
     * Returns screenshots of the application.
239
     *
240
     * The array must contain at least 2 screenshots.
241
     *
242
     * Google Play screenshots requirements:
243
     * * JPEG or 24-bit PNG (no alpha)
244
     * * Minimum dimension: 320px
245
     * * Maximum dimension: 3840px
246
     * * The maximum dimension of the screenshot can't be more than twice as long as the minimum dimension.
247
     *
248
     * @return GoogleImage[] array of screenshots
249
     */
250 2
    public function getScreenshots(): array
251
    {
252 2
        return $this->screenshots;
253
    }
254
255
    /**
256
     * Returns the category of the application.
257
     *
258
     * @return Category category of application
259
     */
260 1
    public function getCategory(): Category
261
    {
262 1
        return $this->category;
263
    }
264
265
    /**
266
     * Returns family category.
267
     *
268
     * @return Category|null family category or `null`
269
     */
270 1
    public function getCategoryFamily(): ?Category
271
    {
272 1
        return $this->categoryFamily;
273
    }
274
275
    /**
276
     * Returns a video about the application.
277
     *
278
     * @return Video|null promo video or `null`
279
     */
280 1
    public function getVideo(): ?Video
281
    {
282 1
        return $this->video;
283
    }
284
285
    /**
286
     * Returns recent changes.
287
     *
288
     * @return string|null recent changes or null if not provided
289
     */
290 1
    public function getRecentChanges(): ?string
291
    {
292 1
        return $this->recentChanges;
293
    }
294
295
    /**
296
     * Checks if the application is an editors' choice.
297
     *
298
     * @return bool `true` if the application is selected by Google Play editor, otherwise `false`
299
     */
300 1
    public function isEditorsChoice(): bool
301
    {
302 1
        return $this->editorsChoice;
303
    }
304
305
    /**
306
     * Returns the number of installations of the application.
307
     *
308
     * @return int the number of installations of the application
309
     */
310 1
    public function getInstalls(): int
311
    {
312 1
        return $this->installs;
313
    }
314
315
    /**
316
     * Returns histogram rating.
317
     *
318
     * @return HistogramRating histogram rating
319
     */
320 1
    public function getHistogramRating(): HistogramRating
321
    {
322 1
        return $this->histogramRating;
323
    }
324
325
    /**
326
     * Returns the price of the app in the Google Play store.
327
     *
328
     * @return float price or 0.00 if the app is free
329
     *
330
     * @see AppInfo::getCurrency() Returns the price currency
331
     *     of the app in the Google Play store.
332
     */
333 1
    public function getPrice(): float
334
    {
335 1
        return $this->price;
336
    }
337
338
    /**
339
     * Returns the price currency of the app in the Google Play store.
340
     *
341
     * @return string currency price of the application, default USD
342
     */
343 1
    public function getCurrency(): string
344
    {
345 1
        return $this->currency;
346
    }
347
348
    /**
349
     * Checks if the app contains In-App Purchases (IAP).
350
     *
351
     * @return bool `true` if the application contains AIP, and `false` if not contains
352
     */
353 1
    public function isContainsIAP(): bool
354
    {
355 1
        return $this->offersIAPCost !== null;
356
    }
357
358
    /**
359
     * Returns the cost of In-App Purchases (IAP).
360
     *
361
     * @return string|null in-App Purchase price
362
     */
363 1
    public function getOffersIAPCost(): ?string
364
    {
365 1
        return $this->offersIAPCost;
366
    }
367
368
    /**
369
     * Checks if the app contains ads.
370
     *
371
     * @return bool `true` if the application contains ads, and `false` if not contains
372
     */
373 1
    public function isContainsAds(): bool
374
    {
375 1
        return $this->containsAds;
376
    }
377
378
    /**
379
     * Returns the size of the application.
380
     *
381
     * @return string|null application size, `null` if the size depends on the device
382
     */
383 1
    public function getSize(): ?string
384
    {
385 1
        return $this->size;
386
    }
387
388
    /**
389
     * Returns the version of the application.
390
     *
391
     * @return string|null application version, `null` if the application version depends on the device
392
     */
393 1
    public function getAppVersion(): ?string
394
    {
395 1
        return $this->appVersion;
396
    }
397
398
    /**
399
     * Returns the supported version of Android.
400
     *
401
     * @return string|null android version, `null` if android version depends on the device
402
     */
403 1
    public function getAndroidVersion(): ?string
404
    {
405 1
        return $this->androidVersion;
406
    }
407
408
    /**
409
     * Returns the minimum supported version of Android.
410
     *
411
     * @return string|null minimum android version, `null` if android version depends on the device
412
     */
413 1
    public function getMinAndroidVersion(): ?string
414
    {
415 1
        return $this->minAndroidVersion;
416
    }
417
418
    /**
419
     * Returns the age limit.
420
     *
421
     * @return string|null Content rating or `null` if not provided
422
     */
423 1
    public function getContentRating(): ?string
424
    {
425 1
        return $this->contentRating;
426
    }
427
428
    /**
429
     * Returns privacy policy URL.
430
     *
431
     * @return string|null privacy policy URL
432
     */
433 1
    public function getPrivacyPoliceUrl(): ?string
434
    {
435 1
        return $this->privacyPoliceUrl;
436
    }
437
438
    /**
439
     * Returns the release date.
440
     *
441
     * @return \DateTimeInterface|null release date or `null` if not provided
442
     */
443 13
    public function getReleased(): ?\DateTimeInterface
444
    {
445 13
        return $this->released;
446
    }
447
448
    /**
449
     * Returns the date of the update.
450
     *
451
     * @return \DateTimeInterface|null update date or `null` if not provided
452
     */
453 1
    public function getUpdated(): ?\DateTimeInterface
454
    {
455 1
        return $this->updated;
456
    }
457
458
    /**
459
     * Returns the number of voters.
460
     *
461
     * @return int number of voters
462
     */
463 1
    public function getNumberVoters(): int
464
    {
465 1
        return $this->numberVoters;
466
    }
467
468
    /**
469
     * Returns the number of reviews.
470
     *
471
     * @return int number of reviews
472
     */
473 1
    public function getNumberReviews(): int
474
    {
475 1
        return $this->numberReviews;
476
    }
477
478
    /**
479
     * Returns some useful reviews.
480
     *
481
     * @return Review[] some useful reviews
482
     */
483 1
    public function getReviews(): array
484
    {
485 1
        return $this->reviews;
486
    }
487
488
    /**
489
     * Checks for equality of applications.
490
     *
491
     * @param AppInfo $otherApp application with which is compared
492
     *
493
     * @return bool `true` if the contents of the objects being changed are the same
494
     *              and `false` if the objects contain different data
495
     *
496
     * @internal
497
     */
498 14
    public function equals(self $otherApp): bool
499
    {
500 14
        if ($otherApp->getId() !== $this->getId()) {
501
            return false;
502
        }
503
504 14
        if ($otherApp->getName() !== $this->getName()) {
505 10
            return false;
506
        }
507
508 14
        if ($otherApp->description !== $this->description) {
509 13
            return false;
510
        }
511
512 11
        if ($otherApp->recentChanges !== $this->recentChanges) {
513 1
            return false;
514
        }
515
516 11
        if ($otherApp->getIcon()->getOriginalSizeUrl() !== $this->getIcon()->getOriginalSizeUrl()) {
517 1
            return false;
518
        }
519 11
        $diff = array_udiff(
520 11
            $otherApp->screenshots,
521 11
            $this->screenshots,
522
            static function (GoogleImage $a, GoogleImage $b) {
523 11
                return strcmp($a->getOriginalSizeUrl(), $b->getOriginalSizeUrl());
524 11
            }
525
        );
526
527 11
        return empty($diff);
528
    }
529
530
    /**
531
     * Returns class properties as an array.
532
     *
533
     * @return array class properties as an array
534
     */
535
    public function asArray(): array
536
    {
537
        $array = parent::asArray();
538
        $array['description'] = $this->description;
539
        $array['translatedFromLocale'] = $this->translatedFromLocale;
540
        $array['cover'] = $this->cover !== null ? $this->cover->getUrl() : null;
541
        $array['screenshots'] = array_map(
542
            static function (GoogleImage $googleImage) {
543
                return $googleImage->getUrl();
544
            },
545
            $this->screenshots
546
        );
547
        $array['category'] = $this->category->asArray();
548
        $array['categoryFamily'] = $this->categoryFamily !== null ? $this->categoryFamily->asArray() : null;
549
        $array['video'] = $this->video !== null ? $this->video->asArray() : null;
550
        $array['privacyPoliceUrl'] = $this->privacyPoliceUrl;
551
        $array['recentChange'] = $this->recentChanges;
552
        $array['editorsChoice'] = $this->editorsChoice;
553
        $array['installs'] = $this->installs;
554
        $array['numberVoters'] = $this->numberVoters;
555
        $array['histogramRating'] = $this->histogramRating;
556
        $array['price'] = $this->price;
557
        $array['currency'] = $this->currency;
558
        $array['offersIAP'] = $this->isContainsIAP();
559
        $array['offersIAPCost'] = $this->offersIAPCost;
560
        $array['containsAds'] = $this->containsAds;
561
        $array['size'] = $this->size;
562
        $array['appVersion'] = $this->appVersion;
563
        $array['androidVersion'] = $this->androidVersion;
564
        $array['minAndroidVersion'] = $this->minAndroidVersion;
565
        $array['contentRating'] = $this->contentRating;
566
        $array['released'] = $this->released !== null ? $this->released->format(\DateTimeInterface::RFC3339) : null;
567
        $array['releasedTimestamp'] = $this->released !== null ? $this->released->getTimestamp() : 0;
568
        $array['updated'] = $this->updated !== null ? $this->updated->format(\DateTimeInterface::RFC3339) : null;
569
        $array['updatedTimestamp'] = $this->updated !== null ? $this->updated->getTimestamp() : 0;
570
        $array['numberReviews'] = $this->numberReviews;
571
        $array['reviews'] = array_map(
572
            static function (Review $review) {
573
                return $review->asArray();
574
            },
575
            $this->reviews
576
        );
577
578
        return $array;
579
    }
580
}
581