Passed
Push — feat/debug-container-command ( cf5949 )
by Chema
11:39 queued 06:29
created

DebugContainerCommand::getHelpText()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 25
nc 1
nop 0
dl 0
loc 3
rs 9.52
c 1
b 0
f 0
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\ServiceResolverAwareTrait;
9
use Override;
10
use Symfony\Component\Console\Command\Command;
11
use Symfony\Component\Console\Input\InputArgument;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Input\InputOption;
14
use Symfony\Component\Console\Output\OutputInterface;
15
16
use function class_exists;
17
use function count;
18
use function sprintf;
19
use function strlen;
20
21
/**
22
 * @method ConsoleFacade getFacade()
23
 */
24
final class DebugContainerCommand extends Command
25
{
26
    use ServiceResolverAwareTrait;
27
28
    #[Override]
29
    protected function configure(): void
30
    {
31
        $this->setName('debug:container')
32
            ->setDescription('Display container debugging information (user bindings and plugins only)')
33
            ->setHelp($this->getHelpText())
34
            ->addArgument('class', InputArgument::OPTIONAL, 'Fully qualified class name to show dependency tree for')
35
            ->addOption('stats', 's', InputOption::VALUE_NONE, 'Show container statistics')
36
            ->addOption('tree', 't', InputOption::VALUE_NONE, 'Show dependency tree for specified class');
37
    }
38
39
    #[Override]
40
    protected function execute(InputInterface $input, OutputInterface $output): int
41
    {
42
        /** @var string|null $className */
43
        $className = $input->getArgument('class');
44
        /** @var bool $showTree */
45
        $showTree = (bool) $input->getOption('tree');
46
47
        // Validate arguments
48
        if ($showTree && $className === null) {
49
            $output->writeln('<error>The --tree option requires a class name argument</error>');
50
            return Command::FAILURE;
51
        }
52
53
        // If class provided without --tree flag, assume --tree
54
        if ($className !== null) {
55
            return $this->displayDependencyTree($output, $className);
56
        }
57
58
        // Default to showing stats if no arguments
59
        return $this->displayStats($output);
60
    }
61
62
    private function displayStats(OutputInterface $output): int
63
    {
64
        $output->writeln('');
65
        $output->writeln('<info>Container Statistics</info>');
66
        $output->writeln('<info>' . str_repeat('=', 60) . '</info>');
67
        $output->writeln('');
68
69
        $stats = $this->getFacade()->getContainerStats();
70
71
        $output->writeln(sprintf('<fg=cyan>Registered Services:</> %d', $stats['registered_services']));
72
        $output->writeln(sprintf('<fg=cyan>Frozen Services:</> %d', $stats['frozen_services']));
73
        $output->writeln(sprintf('<fg=cyan>Factory Services:</> %d', $stats['factory_services']));
74
        $output->writeln(sprintf('<fg=cyan>User Bindings:</> %d', $stats['bindings']));
75
        $output->writeln(sprintf('<fg=cyan>Cached Dependencies:</> %d', $stats['cached_dependencies']));
76
        $output->writeln(sprintf('<fg=cyan>Memory Usage:</> %s', $stats['memory_usage']));
77
        $output->writeln('');
78
79
        if ($stats['registered_services'] === 0) {
80
            $output->writeln('<comment>Container is empty - no services registered yet</comment>');
81
            $output->writeln('');
82
        }
83
84
        $output->writeln('<comment>Note: This shows only user-defined bindings and plugins.</comment>');
85
        $output->writeln("<comment>Gacela's internal services are not included in these statistics.</comment>");
86
        $output->writeln('');
87
88
        return Command::SUCCESS;
89
    }
90
91
    private function displayDependencyTree(OutputInterface $output, string $className): int
92
    {
93
        if (!class_exists($className)) {
94
            $output->writeln(sprintf('<error>Class "%s" does not exist</error>', $className));
95
            return Command::FAILURE;
96
        }
97
98
        $output->writeln('');
99
        $output->writeln(sprintf('<info>Dependency Tree for %s</info>', $className));
100
        $output->writeln('<info>' . str_repeat('=', 60) . '</info>');
101
        $output->writeln('');
102
103
        $dependencyTree = $this->getFacade()->getContainerDependencyTree($className);
104
105
        if ($dependencyTree === []) {
106
            $output->writeln(sprintf('Class "%s" has no dependencies', $className));
107
            $output->writeln('');
108
            return Command::SUCCESS;
109
        }
110
111
        $counter = 1;
112
        foreach ($dependencyTree as $dependency) {
113
            $indent = $this->getIndentLevel($dependency);
114
            $cleanDependency = $this->cleanDependency($dependency);
115
116
            $output->writeln(sprintf('%s%d. %s', str_repeat('  ', $indent), $counter, $cleanDependency));
117
            ++$counter;
118
        }
119
120
        $output->writeln('');
121
        $output->writeln(sprintf('<fg=cyan>Total Dependencies:</> %d', count($dependencyTree)));
122
        $output->writeln('');
123
        $output->writeln('<comment>Note: Indentation shows dependency depth.</comment>');
124
        $output->writeln('<comment>This tree shows only user-defined dependencies.</comment>');
125
        $output->writeln('');
126
127
        return Command::SUCCESS;
128
    }
129
130
    private function getIndentLevel(string $dependency): int
131
    {
132
        // Count leading spaces to determine depth
133
        $matches = [];
134
        if (preg_match('/^(\s*)/', $dependency, $matches) === 1) {
135
            return (int)(strlen($matches[1]) / 2);
136
        }
137
138
        return 0;
139
    }
140
141
    private function cleanDependency(string $dependency): string
142
    {
143
        return trim($dependency);
144
    }
145
146
    private function getHelpText(): string
147
    {
148
        return <<<'HELP'
149
This command displays debugging information about the Gacela dependency injection container.
150
151
<comment>IMPORTANT:</comment> This command shows only user-defined bindings and plugins configured in your
152
application. It does NOT show Gacela's internal services, framework classes, or
153
auto-wired dependencies that are resolved automatically.
154
155
<info>Statistics Mode:</info>
156
  Shows an overview of the container state including number of registered services,
157
  frozen services, factory services, user bindings, cached dependencies, and memory usage.
158
159
<info>Dependency Tree Mode:</info>
160
  Shows the complete dependency chain for a given class, displaying all constructor
161
  dependencies recursively. This helps identify circular dependencies and understand
162
  how services are wired together.
163
164
<info>Examples:</info>
165
  # Show container statistics
166
  bin/gacela debug:container
167
  bin/gacela debug:container --stats
168
169
  # Show dependency tree for a class
170
  bin/gacela debug:container "App\MyModule\MyFacade"
171
  bin/gacela debug:container "App\MyModule\MyFacade" --tree
172
HELP;
173
    }
174
}
175