PopulateAniListService::getAnimeById()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 75
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 75
rs 10
cc 3
nc 2
nop 1

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
namespace App\Services;
6
7
use App\Models\AnidbInfo;
8
use App\Models\AnidbTitle;
9
use GuzzleHttp\Client;
10
use GuzzleHttp\Exception\ClientException;
11
use GuzzleHttp\Exception\GuzzleException;
12
13
class PopulateAniListService
14
{
15
    /**
16
     * AniList GraphQL API endpoint
17
     */
18
    private const API_URL = 'https://graphql.anilist.co';
19
20
    /**
21
     * Rate limit: 20 requests per minute (conservative limit to avoid hitting AniList's 90/min limit)
22
     */
23
    private const RATE_LIMIT_PER_MINUTE = 20;
24
25
    /**
26
     * Whether to echo message output.
27
     */
28
    public bool $echooutput;
29
30
    /**
31
     * The directory to store anime covers.
32
     */
33
    public string $imgSavePath;
34
35
    /**
36
     * HTTP client for API requests
37
     */
38
    protected Client $client;
39
40
    /**
41
     * Rate limiting: track requests and timestamps
42
     */
43
    private array $rateLimitQueue = [];
44
45
    /**
46
     * Track when we received a 429 error and need to pause.
47
     * Format: ['timestamp' => int, 'paused_until' => int]
48
     */
49
    private static ?array $rateLimitPause = null;
50
51
    /**
52
     * @throws \Exception
53
     */
54
    public function __construct()
55
    {
56
        $this->echooutput = config('nntmux.echocli');
57
58
        // Use storage_path directly to match CoverController expectations
59
        $this->imgSavePath = storage_path('covers/anime/');
60
        $this->client = new Client([
61
            'base_uri' => self::API_URL,
62
            'timeout' => 30,
63
            'headers' => [
64
                'Content-Type' => 'application/json',
65
                'Accept' => 'application/json',
66
            ],
67
        ]);
68
    }
69
70
    /**
71
     * Main switch that initiates AniList table population.
72
     *
73
     * @throws \Exception
74
     */
75
    public function populateTable(string $type = '', int|string $anilistId = ''): void
76
    {
77
        switch ($type) {
78
            case 'info':
79
                $this->populateInfoTable((string) $anilistId);
80
                break;
81
        }
82
    }
83
84
    /**
85
     * Search for anime by title using AniList GraphQL API.
86
     *
87
     * @return array|false
88
     *
89
     * @throws \Exception
90
     */
91
    public function searchAnime(string $title, int $limit = 10)
92
    {
93
        $query = '
94
            query ($search: String, $perPage: Int) {
95
                Page (perPage: $perPage) {
96
                    media (search: $search, type: ANIME) {
97
                        id
98
                        idMal
99
                        title {
100
                            romaji
101
                            english
102
                            native
103
                        }
104
                        type
105
                        format
106
                        status
107
                        countryOfOrigin
108
                        episodes
109
                        duration
110
                        source
111
                        popularity
112
                        favourites
113
                        startDate {
114
                            year
115
                            month
116
                            day
117
                        }
118
                        endDate {
119
                            year
120
                            month
121
                            day
122
                        }
123
                        description
124
                        averageScore
125
                        hashtag
126
                        coverImage {
127
                            large
128
                        }
129
                        genres
130
                        studios {
131
                            nodes {
132
                                name
133
                            }
134
                        }
135
                        characters {
136
                            nodes {
137
                                name {
138
                                    full
139
                                }
140
                            }
141
                        }
142
                        relations {
143
                            edges {
144
                                relationType
145
                                node {
146
                                    id
147
                                    title {
148
                                        romaji
149
                                        english
150
                                    }
151
                                }
152
                            }
153
                        }
154
                    }
155
                }
156
            }
157
        ';
158
159
        $variables = [
160
            'search' => $title,
161
            'perPage' => $limit,
162
        ];
163
164
        $response = $this->makeGraphQLRequest($query, $variables);
165
166
        if ($response === false || ! isset($response['data']['Page']['media'])) {
167
            return false;
168
        }
169
170
        return $response['data']['Page']['media'];
171
    }
172
173
    /**
174
     * Refresh AniList data for a specific anidbid.
175
     * If anilist_id exists in database, uses it. Otherwise returns false.
176
     *
177
     * @return bool True on success, false on failure
178
     *
179
     * @throws \Exception
180
     */
181
    public function refreshAnimeByAnidbid(int $anidbid): bool
182
    {
183
        // Get anilist_id from database
184
        $anidbInfo = AnidbInfo::query()->where('anidbid', $anidbid)->first();
185
186
        if (! $anidbInfo || ! $anidbInfo->anilist_id) {
187
            return false;
188
        }
189
190
        // Fetch fresh data from AniList
191
        $anilistData = $this->getAnimeById($anidbInfo->anilist_id);
192
193
        if ($anilistData === false) {
194
            return false;
195
        }
196
197
        // Update database with fresh data
198
        $this->insertAniListInfo($anidbid, $anilistData);
199
200
        return true;
201
    }
202
203
    /**
204
     * Get anime by AniList ID.
205
     *
206
     * @return array|false
207
     *
208
     * @throws \Exception
209
     */
210
    public function getAnimeById(int $anilistId)
211
    {
212
        $query = '
213
            query ($id: Int) {
214
                Media (id: $id, type: ANIME) {
215
                    id
216
                    idMal
217
                    title {
218
                        romaji
219
                        english
220
                        native
221
                    }
222
                    type
223
                    format
224
                    status
225
                    countryOfOrigin
226
                    episodes
227
                    duration
228
                    source
229
                    popularity
230
                    favourites
231
                    startDate {
232
                        year
233
                        month
234
                        day
235
                    }
236
                    endDate {
237
                        year
238
                        month
239
                        day
240
                    }
241
                    description
242
                    averageScore
243
                    hashtag
244
                    coverImage {
245
                        large
246
                    }
247
                    genres
248
                    studios {
249
                        nodes {
250
                            name
251
                        }
252
                    }
253
                    characters {
254
                        nodes {
255
                            name {
256
                                full
257
                            }
258
                        }
259
                    }
260
                    relations {
261
                        edges {
262
                            relationType
263
                            node {
264
                                id
265
                                title {
266
                                    romaji
267
                                    english
268
                                }
269
                            }
270
                        }
271
                    }
272
                }
273
            }
274
        ';
275
276
        $variables = ['id' => $anilistId];
277
278
        $response = $this->makeGraphQLRequest($query, $variables);
279
280
        if ($response === false || ! isset($response['data']['Media'])) {
281
            return false;
282
        }
283
284
        return $response['data']['Media'];
285
    }
286
287
    /**
288
     * Make a GraphQL request to AniList API with rate limiting.
289
     *
290
     * @return array|false
291
     *
292
     * @throws \Exception
293
     */
294
    private function makeGraphQLRequest(string $query, array $variables = [])
295
    {
296
        // Check if we're paused due to 429 error
297
        if (self::$rateLimitPause !== null) {
298
            $now = time();
299
            $pausedUntil = self::$rateLimitPause['paused_until'];
300
301
            if ($now < $pausedUntil) {
302
                $remainingSeconds = $pausedUntil - $now;
303
                if ($this->echooutput) {
304
                    cli()->warning("API calls paused due to 429 error. Resuming in {$remainingSeconds} seconds...");
305
                }
306
307
                // Throw exception to stop processing
308
                throw new \Exception("AniList API rate limit exceeded (429). Paused until " . date('Y-m-d H:i:s', $pausedUntil));
309
            } else {
310
                // Pause period expired, clear it
311
                self::$rateLimitPause = null;
312
            }
313
        }
314
315
        // Enforce rate limiting
316
        $this->enforceRateLimit();
317
318
        try {
319
            $response = $this->client->post('', [
320
                'json' => [
321
                    'query' => $query,
322
                    'variables' => $variables,
323
                ],
324
            ]);
325
326
            $statusCode = $response->getStatusCode();
327
            $body = json_decode($response->getBody()->getContents(), true);
328
329
            // Handle 429 Too Many Requests - pause for 15 minutes
330
            if ($statusCode === 429) {
331
                $pauseUntil = time() + (15 * 60); // 15 minutes from now
332
                self::$rateLimitPause = [
333
                    'timestamp' => time(),
334
                    'paused_until' => $pauseUntil,
335
                ];
336
337
                if ($this->echooutput) {
338
                    cli()->error('AniList API returned 429 (Too Many Requests). Pausing all API calls for 15 minutes.');
339
                }
340
341
                return false;
342
            }
343
344
            // Track rate limit from headers
345
            $remaining = (int) ($response->getHeader('X-RateLimit-Remaining')[0] ?? self::RATE_LIMIT_PER_MINUTE);
346
            $resetAt = (int) ($response->getHeader('X-RateLimit-Reset')[0] ?? time() + 60);
347
348
            // Record this request
349
            $this->rateLimitQueue[] = [
350
                'timestamp' => time(),
351
                'remaining' => $remaining,
352
                'resetAt' => $resetAt,
353
            ];
354
355
            // Clean old entries (older than 1 minute)
356
            $this->rateLimitQueue = array_filter($this->rateLimitQueue, function ($entry) {
357
                return $entry['timestamp'] > (time() - 60);
358
            });
359
360
            if ($statusCode === 200 && isset($body['data'])) {
361
                return $body;
362
            }
363
364
            if (isset($body['errors'])) {
365
                if ($this->echooutput) {
366
                    cli()->error('AniList API Error: '.json_encode($body['errors']));
367
                }
368
369
                return false;
370
            }
371
372
            return false;
373
        } catch (ClientException $e) {
374
            // Check if this is a 429 error from Guzzle
375
            $statusCode = $e->getResponse()->getStatusCode();
376
            if ($statusCode === 429) {
377
                $pauseUntil = time() + (15 * 60); // 15 minutes from now
378
                self::$rateLimitPause = [
379
                    'timestamp' => time(),
380
                    'paused_until' => $pauseUntil,
381
                ];
382
383
                if ($this->echooutput) {
384
                    cli()->error('AniList API returned 429 (Too Many Requests). Pausing all API calls for 15 minutes.');
385
                }
386
387
                throw new \Exception("AniList API rate limit exceeded (429). Paused until " . date('Y-m-d H:i:s', $pauseUntil));
388
            }
389
390
            if ($this->echooutput) {
391
                cli()->error('AniList API Request Failed: '.$e->getMessage());
392
            }
393
394
            return false;
395
        } catch (GuzzleException $e) {
396
            if ($this->echooutput) {
397
                cli()->error('AniList API Request Failed: '.$e->getMessage());
398
            }
399
400
            return false;
401
        }
402
    }
403
404
    /**
405
     * Enforce rate limiting: 90 requests per minute.
406
     *
407
     * @throws \Exception
408
     */
409
    private function enforceRateLimit(): void
410
    {
411
        // Count requests in the last minute
412
        $recentRequests = array_filter($this->rateLimitQueue, function ($entry) {
413
            return $entry['timestamp'] > (time() - 60);
414
        });
415
416
        $requestCount = count($recentRequests);
417
418
        // If we're approaching the limit, wait
419
        if ($requestCount >= (self::RATE_LIMIT_PER_MINUTE - 5)) {
420
            // Wait until the oldest request is more than 1 minute old
421
            if (! empty($this->rateLimitQueue)) {
422
                $oldestRequest = min(array_column($this->rateLimitQueue, 'timestamp'));
423
                $waitTime = 60 - (time() - $oldestRequest) + 1;
424
425
                if ($waitTime > 0 && $waitTime <= 60) {
426
                    if ($this->echooutput) {
427
                        cli()->warning("Rate limit approaching. Waiting {$waitTime} seconds...");
428
                    }
429
                    sleep($waitTime);
430
                }
431
            }
432
        }
433
    }
434
435
    /**
436
     * Insert or update AniList title data.
437
     */
438
    private function insertAniListTitle(int $anidbid, string $type, string $lang, string $title): void
439
    {
440
        $check = AnidbTitle::query()->where([
441
            'anidbid' => $anidbid,
442
            'type' => $type,
443
            'lang' => $lang,
444
            'title' => $title,
445
        ])->first();
446
447
        if ($check === null) {
448
            AnidbTitle::insertOrIgnore([
449
                'anidbid' => $anidbid,
450
                'type' => $type,
451
                'lang' => $lang,
452
                'title' => $title,
453
            ]);
454
        }
455
    }
456
457
    /**
458
     * Insert or update AniList info data.
459
     */
460
    protected function insertAniListInfo(int $anidbid, array $anilistData): void
461
    {
462
        // Extract data from AniList response
463
        $startDate = null;
464
        if (isset($anilistData['startDate']['year'], $anilistData['startDate']['month'], $anilistData['startDate']['day'])) {
465
            $startDate = sprintf(
466
                '%04d-%02d-%02d',
467
                $anilistData['startDate']['year'],
468
                $anilistData['startDate']['month'] ?? 1,
469
                $anilistData['startDate']['day'] ?? 1
470
            );
471
        }
472
473
        $endDate = null;
474
        if (isset($anilistData['endDate']['year'], $anilistData['endDate']['month'], $anilistData['endDate']['day'])) {
475
            $endDate = sprintf(
476
                '%04d-%02d-%02d',
477
                $anilistData['endDate']['year'],
478
                $anilistData['endDate']['month'] ?? 1,
479
                $anilistData['endDate']['day'] ?? 1
480
            );
481
        }
482
483
        $description = $anilistData['description'] ?? '';
484
        // Remove HTML tags from description
485
        $description = strip_tags($description);
486
        $description = html_entity_decode($description, ENT_QUOTES | ENT_HTML5, 'UTF-8');
487
488
        $rating = isset($anilistData['averageScore']) ? (string) $anilistData['averageScore'] : null;
489
490
        $picture = null;
491
        if (isset($anilistData['coverImage']['large'])) {
492
            $picture = basename(parse_url($anilistData['coverImage']['large'], PHP_URL_PATH));
493
        }
494
495
        $genres = '';
496
        if (isset($anilistData['genres']) && is_array($anilistData['genres'])) {
497
            $genres = implode(', ', $anilistData['genres']);
498
        }
499
500
        $creators = '';
501
        if (isset($anilistData['studios']['nodes']) && is_array($anilistData['studios']['nodes'])) {
502
            $studioNames = array_map(function ($studio) {
503
                return $studio['name'] ?? '';
504
            }, $anilistData['studios']['nodes']);
505
            $creators = implode(', ', array_filter($studioNames));
506
        }
507
508
        $characters = '';
509
        if (isset($anilistData['characters']['nodes']) && is_array($anilistData['characters']['nodes'])) {
510
            $characterNames = array_map(function ($character) {
511
                return $character['name']['full'] ?? '';
512
            }, array_slice($anilistData['characters']['nodes'], 0, 10)); // Limit to 10 characters
513
            $characters = implode(', ', array_filter($characterNames));
514
        }
515
516
        $related = '';
517
        $similar = '';
518
        if (isset($anilistData['relations']['edges']) && is_array($anilistData['relations']['edges'])) {
519
            $relatedItems = [];
520
            $similarItems = [];
521
            foreach ($anilistData['relations']['edges'] as $edge) {
522
                $relationType = $edge['relationType'] ?? '';
523
                $title = $edge['node']['title']['english'] ?? $edge['node']['title']['romaji'] ?? '';
524
                $id = $edge['node']['id'] ?? '';
525
526
                if (in_array($relationType, ['SEQUEL', 'PREQUEL', 'SIDE_STORY', 'PARENT', 'SPIN_OFF'])) {
527
                    $relatedItems[] = $title.' ('.$id.')';
528
                } elseif (in_array($relationType, ['ALTERNATIVE', 'CHARACTER'])) {
529
                    $similarItems[] = $title.' ('.$id.')';
530
                }
531
            }
532
            $related = implode(', ', array_slice($relatedItems, 0, 20));
533
            $similar = implode(', ', array_slice($similarItems, 0, 20));
534
        }
535
536
        $anilistId = $anilistData['id'] ?? null;
537
        $malId = $anilistData['idMal'] ?? null;
538
        $country = $anilistData['countryOfOrigin'] ?? null;
539
        $mediaType = $anilistData['type'] ?? null; // ANIME or MANGA
540
        $episodes = isset($anilistData['episodes']) ? (int) $anilistData['episodes'] : null;
541
        $duration = isset($anilistData['duration']) ? (int) $anilistData['duration'] : null;
542
        $status = $anilistData['status'] ?? null;
543
        $source = $anilistData['source'] ?? null;
544
        $hashtag = $anilistData['hashtag'] ?? null;
545
546
        // Use updateOrInsert to handle race conditions atomically
547
        // This prevents duplicate key errors when multiple processes try to insert the same record
548
        $check = AnidbInfo::query()->where('anidbid', $anidbid)->first();
549
550
        $data = [
551
            'anidbid' => $anidbid,
552
            'anilist_id' => $anilistId,
553
            'mal_id' => $malId,
554
            'country' => $country,
555
            'media_type' => $mediaType,
556
            'type' => $anilistData['format'] ?? null,
557
            'episodes' => $episodes,
558
            'duration' => $duration,
559
            'status' => $status,
560
            'source' => $source,
561
            'hashtag' => $hashtag,
562
            'startdate' => $startDate,
563
            'enddate' => $endDate,
564
            'description' => $description,
565
            'rating' => $rating,
566
            'picture' => $picture,
567
            'categories' => $genres,
568
            'characters' => $characters,
569
            'creators' => $creators,
570
            'related' => $related,
571
            'similar' => $similar,
572
            'updated' => now(),
573
        ];
574
575
        // If record exists, preserve existing values for null fields
576
        if ($check !== null) {
577
            $data['anilist_id'] = $anilistId ?? $check->anilist_id;
578
            $data['mal_id'] = $malId ?? $check->mal_id;
579
            $data['country'] = $country ?? $check->country;
580
            $data['media_type'] = $mediaType ?? $check->media_type;
581
            $data['type'] = $anilistData['format'] ?? $anilistData['type'] ?? $check->type;
582
            $data['episodes'] = $episodes ?? $check->episodes;
583
            $data['duration'] = $duration ?? $check->duration;
584
            $data['status'] = $status ?? $check->status;
585
            $data['source'] = $source ?? $check->source;
586
            $data['hashtag'] = $hashtag ?? $check->hashtag;
587
            $data['startdate'] = $startDate ?? $check->startdate;
588
            $data['enddate'] = $endDate ?? $check->enddate;
589
            $data['description'] = $description ?: $check->description;
590
            $data['rating'] = $rating ?? $check->rating;
591
            $data['picture'] = $picture ?? $check->picture;
592
            $data['categories'] = $genres ?: $check->categories;
593
            $data['characters'] = $characters ?: $check->characters;
594
            $data['creators'] = $creators ?: $check->creators;
595
            $data['related'] = $related ?: $check->related;
596
            $data['similar'] = $similar ?: $check->similar;
597
        }
598
599
        // Use updateOrInsert to atomically handle insert/update (prevents race conditions)
600
        AnidbInfo::query()->updateOrInsert(
601
            ['anidbid' => $anidbid],
602
            $data
603
        );
604
605
        // Insert titles
606
        if (isset($anilistData['title']['romaji']) && ! empty($anilistData['title']['romaji'])) {
607
            $this->insertAniListTitle($anidbid, 'main', 'x-jat', $anilistData['title']['romaji']);
608
        }
609
        if (isset($anilistData['title']['english']) && ! empty($anilistData['title']['english'])) {
610
            $this->insertAniListTitle($anidbid, 'official', 'en', $anilistData['title']['english']);
611
        }
612
        if (isset($anilistData['title']['native']) && ! empty($anilistData['title']['native'])) {
613
            $this->insertAniListTitle($anidbid, 'main', 'ja', $anilistData['title']['native']);
614
        }
615
616
        // Download cover image if available
617
        if (! empty($picture) && isset($anilistData['coverImage']['large'])) {
618
            $this->downloadCoverImage($anidbid, $anilistData['coverImage']['large']);
619
        }
620
    }
621
622
    /**
623
     * Download cover image from AniList.
624
     */
625
    private function downloadCoverImage(int $anidbid, string $imageUrl): void
626
    {
627
        // Use the format expected by getReleaseCover: {id}-cover.jpg
628
        // This matches the format used by movies: {id}-cover.jpg
629
        $coverFilename = $anidbid.'-cover.jpg';
630
        $coverPath = $this->imgSavePath.$coverFilename;
631
632
        if (file_exists($coverPath)) {
633
            return; // Already exists
634
        }
635
636
        try {
637
            // Ensure directory exists with proper permissions
638
            if (! is_dir($this->imgSavePath)) {
639
                if (! mkdir($this->imgSavePath, 0755, true) && ! is_dir($this->imgSavePath)) {
640
                    throw new \RuntimeException(sprintf('Directory "%s" was not created', $this->imgSavePath));
641
                }
642
            }
643
644
            $response = $this->client->get($imageUrl);
645
            $imageData = $response->getBody()->getContents();
646
647
            // Write the image file
648
            $bytesWritten = file_put_contents($coverPath, $imageData);
649
650
            if ($bytesWritten === false) {
651
                throw new \RuntimeException("Failed to write cover image to {$coverPath}");
652
            }
653
654
            // Set proper file permissions
655
            chmod($coverPath, 0644);
656
657
            if ($this->echooutput) {
658
                cli()->info("Downloaded cover image for ID {$anidbid} from AniList to {$coverPath}");
659
            }
660
        } catch (GuzzleException $e) {
661
            if ($this->echooutput) {
662
                cli()->warning("Failed to download cover image for ID {$anidbid}: ".$e->getMessage());
663
            }
664
        } catch (\Exception $e) {
665
            if ($this->echooutput) {
666
                cli()->error("Error saving cover image for ID {$anidbid}: ".$e->getMessage());
667
            }
668
        }
669
    }
670
671
    /**
672
     * Directs flow for populating the AniList Info table.
673
     *
674
     * @throws \Exception
675
     */
676
    private function populateInfoTable(string $anilistId = ''): void
677
    {
678
        if (empty($anilistId)) {
679
            // Get titles that need updating (missing anilist_id or stale)
680
            $anidbIds = AnidbTitle::query()
681
                ->selectRaw('DISTINCT anidb_titles.anidbid')
682
                ->leftJoin('anidb_info as ai', 'ai.anidbid', '=', 'anidb_titles.anidbid')
683
                ->where(function ($query) {
684
                    $query->whereNull('ai.anilist_id')
685
                        ->orWhere('ai.updated', '<', now()->subWeek());
686
                })
687
                ->limit(100) // Process in batches
688
                ->get();
689
690
            foreach ($anidbIds as $anidb) {
691
                // Try to find by title first
692
                $title = AnidbTitle::query()
693
                    ->where('anidbid', $anidb->anidbid)
694
                    ->where('lang', 'en')
695
                    ->value('title');
696
697
                if ($title) {
698
                    $searchResults = $this->searchAnime($title, 1);
699
                    if ($searchResults && ! empty($searchResults)) {
700
                        $anilistData = $searchResults[0];
701
                        $this->insertAniListInfo($anidb->anidbid, $anilistData);
702
                        // Rate limiting is handled in makeGraphQLRequest
703
                    }
704
                }
705
            }
706
        } else {
707
            // Get specific anime by AniList ID
708
            $anilistData = $this->getAnimeById((int) $anilistId);
709
710
            if ($anilistData === false) {
711
                if ($this->echooutput) {
712
                    cli()->info("Anime with AniList ID: {$anilistId} not found.");
713
                }
714
715
                return;
716
            }
717
718
            // We need to find or create an anidbid for this
719
            // For now, use anilist_id as anidbid if not found
720
            $anidbid = AnidbInfo::query()
721
                ->where('anilist_id', $anilistId)
722
                ->value('anidbid');
723
724
            if (! $anidbid) {
0 ignored issues
show
introduced by
$anidbid is of type App\Models\AnidbInfo, thus it always evaluated to true.
Loading history...
725
                // Create a new entry using anilist_id as anidbid
726
                $anidbid = (int) $anilistId;
727
            }
728
729
            $this->insertAniListInfo($anidbid, $anilistData);
0 ignored issues
show
Bug introduced by
$anidbid of type App\Models\AnidbInfo is incompatible with the type integer expected by parameter $anidbid of App\Services\PopulateAni...ce::insertAniListInfo(). ( Ignorable by Annotation )

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

729
            $this->insertAniListInfo(/** @scrutinizer ignore-type */ $anidbid, $anilistData);
Loading history...
730
        }
731
    }
732
}
733
734