Passed
Push — feat/cache-warming ( 7624ee )
by Chema
05:29
created

CacheWarmCommand::execute()   B

Complexity

Conditions 7
Paths 14

Size

Total Lines 77
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 7
eloc 50
c 1
b 0
f 1
nc 14
nop 2
dl 0
loc 77
rs 8.1575

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 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
        $modules = $this->getFacade()->findAllAppModules();
58
        $output->writeln(sprintf('<fg=cyan>Found %d modules</>', count($modules)));
59
        $output->writeln('');
60
61
        $resolvedCount = 0;
62
        $skippedCount = 0;
63
64
        // Pre-resolve all module classes
65
        foreach ($modules as $module) {
66
            $moduleClasses = [
67
                'Facade' => $module->facadeClass(),
68
                'Factory' => $module->factoryClass(),
69
                'Config' => $module->configClass(),
70
                'Provider' => $module->providerClass(),
71
            ];
72
73
            $output->writeln(sprintf('<comment>Processing:</> %s', $module->moduleName()));
74
75
            foreach ($moduleClasses as $type => $className) {
76
                if ($className === null) {
77
                    continue;
78
                }
79
80
                if (!class_exists($className)) {
81
                    $output->writeln(sprintf('  <fg=yellow>⚠ Skipped %s:</> %s (class not found)', $type, $className));
82
                    ++$skippedCount;
83
                    continue;
84
                }
85
86
                // Trigger class resolution to populate cache
87
                try {
88
                    class_exists($className, true);
89
                    $output->writeln(sprintf('  <fg=green>✓ Resolved %s:</> %s', $type, $className));
90
                    ++$resolvedCount;
91
                } catch (Throwable $e) {
92
                    $output->writeln(sprintf('  <fg=red>✗ Failed %s:</> %s (%s)', $type, $className, $e->getMessage()));
93
                    ++$skippedCount;
94
                }
95
            }
96
97
            $output->writeln('');
98
        }
99
100
        $endTime = microtime(true);
101
        $endMemory = memory_get_usage(true);
102
103
        $output->writeln('<info>' . str_repeat('=', 60) . '</info>');
104
        $output->writeln('<info>Cache warming complete!</info>');
105
        $output->writeln('');
106
        $output->writeln(sprintf('<fg=cyan>Modules processed:</> %d', count($modules)));
107
        $output->writeln(sprintf('<fg=cyan>Classes resolved:</> %d', $resolvedCount));
108
        $output->writeln(sprintf('<fg=cyan>Classes skipped:</> %d', $skippedCount));
109
        $output->writeln(sprintf('<fg=cyan>Time taken:</> %.3f seconds', $endTime - $startTime));
110
        $output->writeln(sprintf('<fg=cyan>Memory used:</> %s', $this->formatBytes($endMemory - $startMemory)));
111
        $output->writeln('');
112
113
        $this->displayCacheInfo($output);
114
115
        return Command::SUCCESS;
116
    }
117
118
    private function clearCache(OutputInterface $output): void
119
    {
120
        $cacheDir = Config::getInstance()->getCacheDir();
121
        $cacheFile = $cacheDir . DIRECTORY_SEPARATOR . ClassNamePhpCache::FILENAME;
122
123
        if (file_exists($cacheFile)) {
124
            unlink($cacheFile);
125
            $output->writeln('<fg=yellow>Cleared existing cache</>');
126
            $output->writeln('');
127
        }
128
    }
129
130
    private function displayCacheInfo(OutputInterface $output): void
131
    {
132
        $cacheDir = Config::getInstance()->getCacheDir();
133
        $cacheFile = $cacheDir . DIRECTORY_SEPARATOR . ClassNamePhpCache::FILENAME;
134
135
        if (file_exists($cacheFile)) {
136
            $output->writeln(sprintf('<fg=cyan>Cache file:</> %s', $cacheFile));
137
            $output->writeln(sprintf('<fg=cyan>Cache size:</> %s', $this->formatBytes((int) filesize($cacheFile))));
138
        } else {
139
            $output->writeln('<fg=yellow>Warning: Cache file was not created. File caching might be disabled.</>');
140
            $output->writeln('<comment>Enable file caching in your gacela.php configuration:</>');
141
            $output->writeln('<comment>  $config->enableFileCache();</>');
142
        }
143
144
        $output->writeln('');
145
    }
146
147
    private function formatBytes(int $bytes): string
148
    {
149
        if ($bytes < 1024) {
150
            return sprintf('%d B', $bytes);
151
        }
152
153
        if ($bytes < 1048576) {
154
            return sprintf('%.2f KB', $bytes / 1024);
155
        }
156
157
        return sprintf('%.2f MB', $bytes / 1048576);
158
    }
159
160
    private function getHelpText(): string
161
    {
162
        return <<<'HELP'
163
This command pre-resolves all module classes (Facades, Factories, Configs, and Providers)
164
and populates the Gacela cache for optimal production performance.
165
166
<info>What it does:</info>
167
  - Discovers all modules in your application
168
  - Resolves each module's Facade, Factory, Config, and Provider classes
169
  - Generates optimized cache files for class resolution
170
  - Reports statistics about the warming process
171
172
<info>When to use:</info>
173
  - During deployment to production
174
  - After adding new modules
175
  - After major refactoring
176
  - When you want to optimize bootstrap performance
177
178
<info>Options:</info>
179
  --clear, -c    Clear existing cache before warming (recommended for fresh start)
180
181
<info>Examples:</info>
182
  # Warm cache with existing data
183
  bin/gacela cache:warm
184
185
  # Clear and warm cache from scratch
186
  bin/gacela cache:warm --clear
187
188
<comment>Note:</comment> This command requires file caching to be enabled in your gacela.php configuration.
189
If file caching is disabled, the command will still run but won't create persistent cache files.
190
HELP;
191
    }
192
}
193