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

ValidateConfigCommand::validateBindings()   C

Complexity

Conditions 16
Paths 47

Size

Total Lines 61
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 16
eloc 35
c 1
b 0
f 1
nc 47
nop 2
dl 0
loc 61
rs 5.5666

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\Framework\Container\Container;
8
use Gacela\Framework\Gacela;
9
use Override;
10
use Symfony\Component\Console\Command\Command;
11
use Symfony\Component\Console\Input\InputInterface;
12
use Symfony\Component\Console\Output\OutputInterface;
13
use Throwable;
14
15
use function class_exists;
16
use function count;
17
use function file_exists;
18
use function interface_exists;
19
use function is_callable;
20
use function is_object;
21
use function is_string;
22
use function sprintf;
23
24
final class ValidateConfigCommand extends Command
25
{
26
    #[Override]
27
    protected function configure(): void
28
    {
29
        $this->setName('validate:config')
30
            ->setDescription('Validate Gacela configuration for errors and best practices')
31
            ->setHelp($this->getHelpText());
32
    }
33
34
    #[Override]
35
    protected function execute(InputInterface $input, OutputInterface $output): int
36
    {
37
        $output->writeln('');
38
        $output->writeln('<info>Validating Gacela Configuration</info>');
39
        $output->writeln('<info>' . str_repeat('=', 60) . '</info>');
40
        $output->writeln('');
41
42
        $hasErrors = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $hasErrors is dead and can be removed.
Loading history...
43
        $hasWarnings = false;
44
45
        // Check if gacela.php exists
46
        $gacelaConfigPath = Gacela::rootDir() . '/gacela.php';
47
        if (!file_exists($gacelaConfigPath)) {
48
            $output->writeln('<fg=yellow>⚠ Warning:</> No gacela.php configuration file found');
49
            $output->writeln(sprintf('  Expected at: %s', $gacelaConfigPath));
50
            $output->writeln('');
51
            $hasWarnings = true;
0 ignored issues
show
Unused Code introduced by
The assignment to $hasWarnings is dead and can be removed.
Loading history...
52
        } else {
53
            $output->writeln(sprintf('<fg=green>✓</> Configuration file found: %s', $gacelaConfigPath));
54
            $output->writeln('');
55
        }
56
57
        // Validate bindings
58
        $container = Gacela::container();
59
        $bindingsValidation = $this->validateBindings($container, $output);
60
        $hasErrors = $bindingsValidation['errors'];
61
        $hasWarnings = $bindingsValidation['warnings'];
62
63
        // Validate config paths
64
        $configValidation = $this->validateConfigPaths($output);
65
        $hasErrors = $hasErrors || $configValidation['errors'];
66
        $hasWarnings = $hasWarnings || $configValidation['warnings'];
67
68
        // Check for circular dependencies (basic check)
69
        $circularDepsValidation = $this->checkCircularDependencies($container, $output);
70
        $hasErrors = $hasErrors || $circularDepsValidation['errors'];
71
        $hasWarnings = $hasWarnings || $circularDepsValidation['warnings'];
72
73
        // Summary
74
        $output->writeln('');
75
        $output->writeln('<info>' . str_repeat('=', 60) . '</info>');
76
77
        if ($hasErrors) {
78
            $output->writeln('<error>✗ Validation failed with errors</error>');
79
            $output->writeln('');
80
            return Command::FAILURE;
81
        }
82
83
        if ($hasWarnings) {
84
            $output->writeln('<fg=yellow>⚠ Validation completed with warnings</fg=yellow>');
85
            $output->writeln('');
86
            return Command::SUCCESS;
87
        }
88
89
        $output->writeln('<fg=green>✓ Configuration is valid!</fg=green>');
90
        $output->writeln('');
91
92
        return Command::SUCCESS;
93
    }
94
95
    /**
96
     * @return array{errors: bool, warnings: bool}
97
     */
98
    private function validateBindings(Container $container, OutputInterface $output): array
99
    {
100
        $output->writeln('<comment>Checking bindings...</comment>');
101
102
        $hasErrors = false;
103
        $hasWarnings = false;
104
105
        try {
106
            $bindings = $container->getBindings();
107
108
            if ($bindings === []) {
109
                $output->writeln('  <fg=cyan>No bindings configured</fg=cyan>');
110
                $output->writeln('');
111
                return ['errors' => false, 'warnings' => false];
112
            }
113
114
            $output->writeln(sprintf('  Found %d binding%s', count($bindings), count($bindings) === 1 ? '' : 's'));
115
            $output->writeln('');
116
117
            foreach ($bindings as $key => $value) {
118
                // Validate key exists as class or interface
119
                if (!class_exists($key) && !interface_exists($key)) {
120
                    $output->writeln(sprintf('  <error>✗ Binding key does not exist:</> %s', $key));
121
                    $hasErrors = true;
122
                    continue;
123
                }
124
125
                // Validate value
126
                if (is_string($value)) {
127
                    if (!class_exists($value)) {
128
                        $output->writeln(sprintf('  <error>✗ Binding value class does not exist:</> %s -> %s', $key, $value));
129
                        $hasErrors = true;
130
                        continue;
131
                    }
132
133
                    // Check if value is assignable to key (basic check)
134
                    if (class_exists($key) && !is_subclass_of($value, $key) && $value !== $key) {
135
                        $output->writeln(sprintf('  <fg=yellow>⚠ Warning: Binding value may not be compatible with key:</> %s -> %s', $key, $value));
136
                        $hasWarnings = true;
137
                    }
138
                } elseif (is_object($value)) {
139
                    // Object binding (including callables) - check if it's assignable to key
140
                    if (!is_callable($value) && class_exists($key) && !($value instanceof $key)) {
141
                        $output->writeln(sprintf('  <error>✗ Binding object is not instance of key:</> %s', $key));
142
                        $hasErrors = true;
143
                        continue;
144
                    }
145
146
                    // Callable objects are valid - no further validation needed
147
                }
148
149
                $output->writeln(sprintf('  <fg=green>✓</> %s', $key));
150
            }
151
        } catch (Throwable $throwable) {
152
            $output->writeln(sprintf('  <error>Error validating bindings: %s</error>', $throwable->getMessage()));
153
            $hasErrors = true;
154
        }
155
156
        $output->writeln('');
157
158
        return ['errors' => $hasErrors, 'warnings' => $hasWarnings];
159
    }
160
161
    /**
162
     * @return array{errors: bool, warnings: bool}
163
     */
164
    private function validateConfigPaths(OutputInterface $output): array
165
    {
166
        $output->writeln('<comment>Checking configuration paths...</comment>');
167
168
        // Note: We can't easily access the config paths from the current API
169
        // This is a placeholder for future enhancement
170
        $output->writeln('  <fg=cyan>Config path validation requires runtime configuration access</fg=cyan>');
171
        $output->writeln('');
172
173
        return ['errors' => false, 'warnings' => false];
174
    }
175
176
    /**
177
     * @return array{errors: bool, warnings: bool}
178
     */
179
    private function checkCircularDependencies(Container $container, OutputInterface $output): array
180
    {
181
        $output->writeln('<comment>Checking for circular dependencies...</comment>');
182
183
        $hasErrors = false;
184
        $hasWarnings = false;
185
186
        try {
187
            $bindings = $container->getBindings();
188
189
            foreach ($bindings as $key => $value) {
190
                if (!is_string($value)) {
191
                    continue;
192
                }
193
194
                // Check if value class depends back on key (simple check)
195
                if (class_exists($value)) {
196
                    try {
197
                        /**
198
                         * @psalm-suppress UnusedVariable
199
                         * @psalm-suppress MixedAssignment
200
                         */
201
                        $resolved = $container->get($key);
0 ignored issues
show
Unused Code introduced by
The assignment to $resolved is dead and can be removed.
Loading history...
202
                        // If we can resolve it without errors, it's likely fine
203
                    } catch (Throwable $throwable) {
204
                        if (str_contains($throwable->getMessage(), 'circular') || str_contains($throwable->getMessage(), 'recursive')) {
205
                            $output->writeln(sprintf('  <error>✗ Circular dependency detected:</> %s', $key));
206
                            $hasErrors = true;
207
                        }
208
                    }
209
                }
210
            }
211
212
            if (!$hasErrors) {
213
                $output->writeln('  <fg=green>✓ No circular dependencies detected</fg=green>');
214
            }
215
        } catch (Throwable $throwable) {
216
            $output->writeln(sprintf('  <fg=yellow>⚠ Warning: Could not check circular dependencies: %s</fg=yellow>', $throwable->getMessage()));
217
            $hasWarnings = true;
218
        }
219
220
        $output->writeln('');
221
222
        return ['errors' => $hasErrors, 'warnings' => $hasWarnings];
223
    }
224
225
    private function getHelpText(): string
226
    {
227
        return <<<'HELP'
228
This command validates your Gacela configuration for common errors and potential issues.
229
230
<info>What it checks:</info>
231
  - Existence of gacela.php configuration file
232
  - Bindings configuration:
233
    - Validates that binding keys (interfaces/classes) exist
234
    - Validates that binding values (classes) exist
235
    - Checks type compatibility between keys and values
236
  - Circular dependency detection (basic check)
237
  - Configuration file paths
238
239
<info>Validation levels:</info>
240
  <error>Errors</error> - Critical issues that will cause runtime failures
241
  <fg=yellow>Warnings</fg=yellow> - Potential issues or best practice violations that may work but should be reviewed
242
243
<info>Examples:</info>
244
  # Validate configuration
245
  bin/gacela validate:config
246
247
<comment>Best practices:</comment>
248
  - Run this command before deploying to production
249
  - Add it to your CI/CD pipeline
250
  - Run after adding new bindings or changing configuration
251
  - Use with cache:warm for complete pre-deployment validation
252
253
<info>Exit codes:</info>
254
  0 - Validation successful (may have warnings)
255
  1 - Validation failed with errors
256
HELP;
257
    }
258
}
259