IgdbGameMatchingCommand::execute()   F
last analyzed

Complexity

Conditions 17
Paths 2256

Size

Total Lines 135
Code Lines 90

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 17
eloc 90
nc 2256
nop 2
dl 0
loc 135
rs 1.0581
c 1
b 0
f 0

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
declare(strict_types=1);
4
5
namespace VideoGamesRecords\CoreBundle\Command;
6
7
use Doctrine\ORM\EntityManagerInterface;
8
use Symfony\Component\Console\Attribute\AsCommand;
9
use Symfony\Component\Console\Command\Command;
10
use Symfony\Component\Console\Input\InputInterface;
11
use Symfony\Component\Console\Input\InputOption;
12
use Symfony\Component\Console\Output\OutputInterface;
13
use Symfony\Component\Console\Style\SymfonyStyle;
14
use VideoGamesRecords\CoreBundle\Entity\Game;
15
use VideoGamesRecords\CoreBundle\Domain\Igdb\Service\GameMatchingService;
16
17
#[AsCommand(
18
    name: 'vgr:igdb:match-games',
19
    description: 'Automatically match Core Bundle games with IGDB games'
20
)]
21
class IgdbGameMatchingCommand extends Command
22
{
23
    public function __construct(
24
        private readonly EntityManagerInterface $entityManager,
25
        private readonly GameMatchingService $gameMatchingService
26
    ) {
27
        parent::__construct();
28
    }
29
30
    protected function configure(): void
31
    {
32
        $this
33
            ->addOption(
34
                'dry-run',
35
                'd',
36
                InputOption::VALUE_NONE,
37
                'Show what would be matched without actually updating the database'
38
            )
39
            ->addOption(
40
                'game-id',
41
                'g',
42
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
43
                'Process only specific game IDs (can be used multiple times)'
44
            )
45
            ->addOption(
46
                'limit',
47
                'l',
48
                InputOption::VALUE_OPTIONAL,
49
                'Maximum number of games to process',
50
                100
51
            )
52
            ->addOption(
53
                'batch-size',
54
                'b',
55
                InputOption::VALUE_OPTIONAL,
56
                'Number of games to process in each batch before flushing',
57
                10
58
            )
59
            ->addOption(
60
                'without-igdb-only',
61
                'w',
62
                InputOption::VALUE_NONE,
63
                'Process only games that do not have an IGDB association yet'
64
            );
65
    }
66
67
    protected function execute(InputInterface $input, OutputInterface $output): int
68
    {
69
        $io = new SymfonyStyle($input, $output);
70
        $dryRun = $input->getOption('dry-run');
71
        $gameIds = $input->getOption('game-id');
72
        $limit = (int) $input->getOption('limit');
73
        $batchSize = (int) $input->getOption('batch-size');
74
        $withoutIgdbOnly = $input->getOption('without-igdb-only');
75
76
        $io->title('IGDB Game Matching Service');
77
78
        if ($dryRun) {
79
            $io->note('Running in DRY-RUN mode - no changes will be made to the database');
80
        }
81
82
        try {
83
            $games = $this->getGamesToProcess($gameIds, $withoutIgdbOnly, $limit);
84
            $totalGames = count($games);
85
86
            if ($totalGames === 0) {
87
                $io->success('No games to process.');
88
                return Command::SUCCESS;
89
            }
90
91
            $io->info(sprintf('Found %d games to process', $totalGames));
92
93
            if (!$dryRun) {
94
                $statistics = $this->gameMatchingService->getMatchingStatistics();
95
                $io->section('Current Statistics');
96
                $io->table(
97
                    ['Metric', 'Value'],
98
                    [
99
                        ['Total Games', $statistics['total_games']],
100
                        ['Games with IGDB Association', $statistics['games_with_igdb_association']],
101
                        ['Games without IGDB Association', $statistics['games_without_igdb_association']],
102
                        ['Matching Percentage', $statistics['matching_percentage'] . '%']
103
                    ]
104
                );
105
            }
106
107
            $io->progressStart($totalGames);
108
109
            $processedCount = 0;
110
            $matchedCount = 0;
111
            $errorCount = 0;
112
            $skippedCount = 0;
113
114
            foreach (array_chunk($games, $batchSize) as $gameBatch) {
115
                foreach ($gameBatch as $game) {
116
                    try {
117
                        if ($game->getIgdbGame() !== null && $withoutIgdbOnly) {
118
                            $skippedCount++;
119
                            $io->progressAdvance();
120
                            continue;
121
                        }
122
123
                        if ($dryRun) {
124
                            $io->text(sprintf(
125
                                'Would process: %s (ID: %d) - Platforms: %s',
126
                                $game->getLibGameEn(),
127
                                $game->getId(),
128
                                $this->getPlatformNames($game)
129
                            ));
130
                            $processedCount++;
131
                        } else {
132
                            $igdbGame = $this->gameMatchingService->findAndAssociateIgdbGame($game);
133
134
                            if ($igdbGame !== null) {
135
                                $matchedCount++;
136
                                $io->text(sprintf(
137
                                    'Matched: %s → IGDB Game ID: %d',
138
                                    $game->getLibGameEn(),
139
                                    $igdbGame->getId()
140
                                ));
141
                            }
142
                            $processedCount++;
143
                        }
144
                    } catch (\Exception $e) {
145
                        $errorCount++;
146
                        $io->error(sprintf(
147
                            'Error processing game %s (ID: %d): %s',
148
                            $game->getLibGameEn(),
149
                            $game->getId(),
150
                            $e->getMessage()
151
                        ));
152
                    }
153
154
                    $io->progressAdvance();
155
                }
156
157
                if (!$dryRun) {
158
                    $this->entityManager->clear();
159
                }
160
            }
161
162
            $io->progressFinish();
163
164
            $io->section('Processing Summary');
165
            $resultTable = [
166
                ['Total Games Processed', $processedCount],
167
                ['Successfully Matched', $matchedCount],
168
                ['Errors', $errorCount],
169
                ['Skipped (already matched)', $skippedCount]
170
            ];
171
172
            if ($dryRun) {
173
                $resultTable[] = ['⚠️  DRY RUN MODE', 'No changes made'];
174
            }
175
176
            $io->table(['Metric', 'Count'], $resultTable);
177
178
            if (!$dryRun && $matchedCount > 0) {
179
                $io->section('Updated Statistics');
180
                $finalStatistics = $this->gameMatchingService->getMatchingStatistics();
181
                $io->table(
182
                    ['Metric', 'Value'],
183
                    [
184
                        ['Total Games', $finalStatistics['total_games']],
185
                        ['Games with IGDB Association', $finalStatistics['games_with_igdb_association']],
186
                        ['Games without IGDB Association', $finalStatistics['games_without_igdb_association']],
187
                        ['Matching Percentage', $finalStatistics['matching_percentage'] . '%']
188
                    ]
189
                );
190
            }
191
192
            if ($errorCount > 0) {
193
                $io->warning(sprintf('%d errors occurred during processing. Check the logs for details.', $errorCount));
194
                return Command::FAILURE;
195
            }
196
197
            $io->success('Game matching completed successfully!');
198
            return Command::SUCCESS;
199
        } catch (\Exception $e) {
200
            $io->error('Command failed: ' . $e->getMessage());
201
            return Command::FAILURE;
202
        }
203
    }
204
205
    private function getGamesToProcess(?array $gameIds, bool $withoutIgdbOnly, int $limit): array
206
    {
207
        $queryBuilder = $this->entityManager->getRepository(Game::class)->createQueryBuilder('g');
208
209
        if (!empty($gameIds)) {
210
            $queryBuilder->where('g.id IN (:gameIds)')
211
                        ->setParameter('gameIds', array_map('intval', $gameIds));
212
        }
213
214
        if ($withoutIgdbOnly) {
215
            $queryBuilder->andWhere('g.igdbGame IS NULL');
216
        }
217
218
        $queryBuilder->setMaxResults($limit);
219
220
        return $queryBuilder->getQuery()->getResult();
221
    }
222
223
    private function getPlatformNames(Game $game): string
224
    {
225
        $platformNames = [];
226
        foreach ($game->getPlatforms() as $platform) {
227
            $platformNames[] = $platform->getLibPlatform();
228
        }
229
        return implode(', ', $platformNames);
230
    }
231
}
232