Test Failed
Pull Request — main (#342)
by Chema
08:14 queued 04:08
created

CacheWarmCommand::execute()   B

Complexity

Conditions 10
Paths 28

Size

Total Lines 93
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
cc 10
eloc 60
c 2
b 1
f 1
nc 28
nop 2
dl 0
loc 93
rs 7.006

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 Gacela\Console\Infrastructure\Command;
6
7
use Gacela\Console\ConsoleFacade;
8
use Gacela\Framework\ClassResolver\Cache\ClassNamePhpCache;
9
use Gacela\Framework\Config\Config;
10
use Gacela\Framework\ServiceResolverAwareTrait;
11
use Override;
12
use Symfony\Component\Console\Command\Command;
13
use Symfony\Component\Console\Input\InputInterface;
14
use Symfony\Component\Console\Input\InputOption;
15
use Symfony\Component\Console\Output\OutputInterface;
16
17
use Throwable;
18
19
use function count;
20
use function file_exists;
21
use function sprintf;
22
23
/**
24
 * @method ConsoleFacade getFacade()
25
 */
26
final class CacheWarmCommand extends Command
27
{
28
    use ServiceResolverAwareTrait;
29
30
    #[Override]
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
    #[Override]
40
    protected function execute(InputInterface $input, OutputInterface $output): int
41
    {
42
        $output->writeln('');
43
        $output->writeln('<info>Warming Gacela cache...</info>');
44
        $output->writeln('<info>' . str_repeat('=', 60) . '</info>');
45
        $output->writeln('');
46
47
        $clearCache = (bool) $input->getOption('clear');
48
49
        if ($clearCache) {
50
            $this->clearCache($output);
51
        }
52
53
        $startTime = microtime(true);
54
        $startMemory = memory_get_usage(true);
55
56
        // Find all modules
57
        try {
58
            $modules = $this->getFacade()->findAllAppModules();
59
        } catch (Throwable $throwable) {
60
            // If module discovery fails (e.g., due to test fixtures), continue with empty set
61
            $output->writeln('<fg=yellow>Warning: Some modules could not be discovered due to errors</>');
62
            $output->writeln(sprintf('  Error: %s', $throwable->getMessage()));
63
            $modules = [];
64
        }
65
66
        // Filter out test/fixture modules that might cause issues
67
        $modules = array_filter($modules, static function ($module): bool {
68
            $className = $module->facadeClass();
69
            return !str_contains($className, 'Test')
70
                && !str_contains($className, '\\Fixtures\\')
71
                && !str_contains($className, '\\Benchmark\\');
72
        });
73
74
        $output->writeln(sprintf('<fg=cyan>Found %d modules</>', count($modules)));
75
        $output->writeln('');
76
77
        $resolvedCount = 0;
78
        $skippedCount = 0;
79
80
        // Pre-resolve all module classes
81
        foreach ($modules as $module) {
82
            $moduleClasses = [
83
                'Facade' => $module->facadeClass(),
84
                'Factory' => $module->factoryClass(),
85
                'Config' => $module->configClass(),
86
                'Provider' => $module->providerClass(),
87
            ];
88
89
            $output->writeln(sprintf('<comment>Processing:</> %s', $module->moduleName()));
90
91
            foreach ($moduleClasses as $type => $className) {
92
                if ($className === null) {
93
                    continue;
94
                }
95
96
                if (!class_exists($className)) {
97
                    $output->writeln(sprintf('  <fg=yellow>⚠ Skipped %s:</> %s (class not found)', $type, $className));
98
                    ++$skippedCount;
99
                    continue;
100
                }
101
102
                // Trigger class resolution to populate cache
103
                try {
104
                    class_exists($className, true);
105
                    $output->writeln(sprintf('  <fg=green>✓ Resolved %s:</> %s', $type, $className));
106
                    ++$resolvedCount;
107
                } catch (Throwable $e) {
108
                    $output->writeln(sprintf('  <fg=red>✗ Failed %s:</> %s (%s)', $type, $className, $e->getMessage()));
109
                    ++$skippedCount;
110
                }
111
            }
112
113
            $output->writeln('');
114
        }
115
116
        $endTime = microtime(true);
117
        $endMemory = memory_get_usage(true);
118
119
        $output->writeln('<info>' . str_repeat('=', 60) . '</info>');
120
        $output->writeln('<info>Cache warming complete!</info>');
121
        $output->writeln('');
122
        $output->writeln(sprintf('<fg=cyan>Modules processed:</> %d', count($modules)));
123
        $output->writeln(sprintf('<fg=cyan>Classes resolved:</> %d', $resolvedCount));
124
        $output->writeln(sprintf('<fg=cyan>Classes skipped:</> %d', $skippedCount));
125
        $output->writeln(sprintf('<fg=cyan>Time taken:</> %.3f seconds', $endTime - $startTime));
126
        $output->writeln(sprintf('<fg=cyan>Memory used:</> %s', $this->formatBytes($endMemory - $startMemory)));
127
        $output->writeln('');
128
129
        $this->displayCacheInfo($output);
130
131
        return Command::SUCCESS;
132
    }
133
134
    private function clearCache(OutputInterface $output): void
135
    {
136
        $cacheDir = Config::getInstance()->getCacheDir();
137
        $cacheFile = $cacheDir . DIRECTORY_SEPARATOR . ClassNamePhpCache::FILENAME;
138
139
        if (file_exists($cacheFile)) {
140
            unlink($cacheFile);
141
            $output->writeln('<fg=yellow>Cleared existing cache</>');
142
            $output->writeln('');
143
        }
144
    }
145
146
    private function displayCacheInfo(OutputInterface $output): void
147
    {
148
        $cacheDir = Config::getInstance()->getCacheDir();
149
        $cacheFile = $cacheDir . DIRECTORY_SEPARATOR . ClassNamePhpCache::FILENAME;
150
151
        if (file_exists($cacheFile)) {
152
            $output->writeln(sprintf('<fg=cyan>Cache file:</> %s', $cacheFile));
153
            $output->writeln(sprintf('<fg=cyan>Cache size:</> %s', $this->formatBytes((int) filesize($cacheFile))));
154
        } else {
155
            $output->writeln('<fg=yellow>Warning: Cache file was not created. File caching might be disabled.</>');
156
            $output->writeln('<comment>Enable file caching in your gacela.php configuration:</>');
157
            $output->writeln('<comment>  $config->enableFileCache();</>');
158
        }
159
160
        $output->writeln('');
161
    }
162
163
    private function formatBytes(int $bytes): string
164
    {
165
        if ($bytes < 1024) {
166
            return sprintf('%d B', $bytes);
167
        }
168
169
        if ($bytes < 1048576) {
170
            return sprintf('%.2f KB', $bytes / 1024);
171
        }
172
173
        return sprintf('%.2f MB', $bytes / 1048576);
174
    }
175
176
    private function getHelpText(): string
177
    {
178
        return <<<'HELP'
179
This command pre-resolves all module classes (Facades, Factories, Configs, and Providers)
180
and populates the Gacela cache for optimal production performance.
181
182
<info>What it does:</info>
183
  - Discovers all modules in your application
184
  - Resolves each module's Facade, Factory, Config, and Provider classes
185
  - Generates optimized cache files for class resolution
186
  - Reports statistics about the warming process
187
188
<info>When to use:</info>
189
  - During deployment to production
190
  - After adding new modules
191
  - After major refactoring
192
  - When you want to optimize bootstrap performance
193
194
<info>Options:</info>
195
  --clear, -c    Clear existing cache before warming (recommended for fresh start)
196
197
<info>Examples:</info>
198
  # Warm cache with existing data
199
  bin/gacela cache:warm
200
201
  # Clear and warm cache from scratch
202
  bin/gacela cache:warm --clear
203
204
<comment>Note:</comment> This command requires file caching to be enabled in your gacela.php configuration.
205
If file caching is disabled, the command will still run but won't create persistent cache files.
206
HELP;
207
    }
208
}
209