Passed
Branch develop (2fd4b5)
by Alexey
01:37
created

AppDetail   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 528
Duplicated Lines 0 %

Test Coverage

Coverage 74.29%

Importance

Changes 0
Metric Value
eloc 138
dl 0
loc 528
ccs 104
cts 140
cp 0.7429
rs 8.72
c 0
b 0
f 0
wmc 46

31 Methods

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

How to fix   Complexity   

Complex Class

Complex classes like AppDetail 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 AppDetail, and based on these observations, apply Extract Interface, too.

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