Test Failed
Push — feat/cache-warming ( 037623...493f4d )
by Chema
04:16
created

CacheWarmCommand::execute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 34
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
cc 2
eloc 21
c 3
b 1
f 1
nc 2
nop 2
dl 0
loc 34
rs 9.584
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Console\Infrastructure\Command;
6
7
use Gacela\Console\Application\CacheWarm\CacheManager;
8
use Gacela\Console\Application\CacheWarm\CacheWarmOutputFormatter;
9
use Gacela\Console\Application\CacheWarm\CacheWarmService;
10
use Gacela\Console\Application\CacheWarm\ClassNotFoundException;
11
use Gacela\Console\Application\CacheWarm\PerformanceMetrics;
12
use Gacela\Console\ConsoleFacade;
13
use Gacela\Console\Domain\AllAppModules\AppModule;
14
use Gacela\Framework\ServiceResolverAwareTrait;
15
use Symfony\Component\Console\Command\Command;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Symfony\Component\Console\Input\InputOption;
18
use Symfony\Component\Console\Output\OutputInterface;
19
20
use Throwable;
21
22
use function count;
23
24
/**
25
 * @method ConsoleFacade getFacade()
26
 */
27
final class CacheWarmCommand extends Command
28
{
29
    use ServiceResolverAwareTrait;
30
31
    protected function configure(): void
32
    {
33
        $this->setName('cache:warm')
34
            ->setDescription('Pre-resolve all module classes and warm the cache for production')
35
            ->setHelp($this->getHelpText())
36
            ->addOption('clear', 'c', InputOption::VALUE_NONE, 'Clear existing cache before warming');
37
    }
38
39
    protected function execute(InputInterface $input, OutputInterface $output): int
40
    {
41
        $cacheManager = new CacheManager();
42
        $cacheWarmService = new CacheWarmService($this->getFacade());
43
        $formatter = new CacheWarmOutputFormatter($output);
44
        $metrics = new PerformanceMetrics();
45
46
        $formatter->writeHeader();
47
48
        $clearCache = (bool) $input->getOption('clear');
49
50
        if ($clearCache) {
51
            $cacheManager->clearCache();
52
            $formatter->writeCacheCleared();
53
        }
54
55
        $modules = $this->discoverModules($cacheWarmService, $formatter);
56
        $modules = $cacheWarmService->filterProductionModules($modules);
57
58
        $formatter->writeModulesFound($modules);
59
60
        [$resolvedCount, $skippedCount] = $this->warmModulesCache($modules, $cacheWarmService, $formatter);
61
62
        $formatter->writeSummary(
63
            count($modules),
64
            $resolvedCount,
65
            $skippedCount,
66
            $metrics->formatElapsedTime(),
67
            $metrics->formatMemoryUsed(),
68
        );
69
70
        $this->displayCacheInfo($cacheManager, $formatter);
71
72
        return Command::SUCCESS;
73
    }
74
75
    /**
76
     * @return list<AppModule>
77
     */
78
    private function discoverModules(
79
        CacheWarmService $cacheWarmService,
80
        CacheWarmOutputFormatter $formatter,
81
    ): array {
82
        try {
83
            return $cacheWarmService->discoverModules();
84
        } catch (Throwable $throwable) {
85
            $formatter->writeModuleDiscoveryWarning($throwable->getMessage());
86
            return [];
87
        }
88
    }
89
90
    /**
91
     * @param list<AppModule> $modules
92
     *
93
     * @return array{int, int}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{int, int} at position 2 could not be parsed: Expected ':' at position 2, but found 'int'.
Loading history...
94
     */
95
    private function warmModulesCache(
96
        array $modules,
97
        CacheWarmService $cacheWarmService,
98
        CacheWarmOutputFormatter $formatter,
99
    ): array {
100
        $resolvedCount = 0;
101
        $skippedCount = 0;
102
103
        foreach ($modules as $module) {
104
            $formatter->writeModuleName($module->moduleName());
105
106
            $moduleClasses = $cacheWarmService->getModuleClasses($module);
107
108
            foreach ($moduleClasses as $classInfo) {
109
                try {
110
                    $cacheWarmService->resolveClass($classInfo['className']);
111
                    $formatter->writeClassResolved($classInfo['type'], $classInfo['className']);
112
                    ++$resolvedCount;
113
                } catch (ClassNotFoundException) {
114
                    $formatter->writeClassSkipped($classInfo['type'], $classInfo['className']);
115
                    ++$skippedCount;
116
                } catch (Throwable $e) {
117
                    $formatter->writeClassFailed($classInfo['type'], $classInfo['className'], $e->getMessage());
118
                    ++$skippedCount;
119
                }
120
            }
121
122
            $formatter->writeEmptyLine();
123
        }
124
125
        return [$resolvedCount, $skippedCount];
126
    }
127
128
    private function displayCacheInfo(
129
        CacheManager $cacheManager,
130
        CacheWarmOutputFormatter $formatter,
131
    ): void {
132
        if ($cacheManager->cacheFileExists()) {
133
            $cacheFile = $cacheManager->getCacheFilePath();
134
            $cacheSize = $cacheManager->getFormattedCacheFileSize();
135
            $formatter->writeCacheInfo($cacheFile, $cacheSize);
136
        } else {
137
            $formatter->writeCacheWarning();
138
        }
139
    }
140
141
    private function getHelpText(): string
142
    {
143
        return <<<'HELP'
144
This command pre-resolves all module classes (Facades, Factories, Configs, and Providers)
145
and populates the Gacela cache for optimal production performance.
146
147
<info>What it does:</info>
148
  - Discovers all modules in your application
149
  - Resolves each module's Facade, Factory, Config, and Provider classes
150
  - Generates optimized cache files for class resolution
151
  - Reports statistics about the warming process
152
153
<info>When to use:</info>
154
  - During deployment to production
155
  - After adding new modules
156
  - After major refactoring
157
  - When you want to optimize bootstrap performance
158
159
<info>Options:</info>
160
  --clear, -c    Clear existing cache before warming (recommended for fresh start)
161
162
<info>Examples:</info>
163
  # Warm cache with existing data
164
  bin/gacela cache:warm
165
166
  # Clear and warm cache from scratch
167
  bin/gacela cache:warm --clear
168
169
<comment>Note:</comment> This command requires file caching to be enabled in your gacela.php configuration.
170
If file caching is disabled, the command will still run but won't create persistent cache files.
171
HELP;
172
    }
173
}
174