Skip to content

Commit e0b812a

Browse files
feat: Use symfony console.command_loader to get the list of commands (#150)
Co-authored-by: Christopher Georg <[email protected]>
1 parent 8951d6d commit e0b812a

File tree

3 files changed

+61
-108
lines changed

3 files changed

+61
-108
lines changed

Service/CommandParser.php

Lines changed: 47 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
namespace Dukecity\CommandSchedulerBundle\Service;
44

55
use Exception;
6-
use Symfony\Bundle\FrameworkBundle\Console\Application;
6+
use Symfony\Component\Console\Command\Command;
7+
use Symfony\Component\Console\Descriptor\JsonDescriptor;
78
use Symfony\Component\Console\Exception\CommandNotFoundException;
8-
use Symfony\Component\Console\Input\ArrayInput;
9-
use Symfony\Component\Console\Output\StreamOutput;
10-
use Symfony\Component\ErrorHandler\Debug;
9+
use Symfony\Component\Console\Output\BufferedOutput;
1110
use Symfony\Component\HttpKernel\KernelInterface;
1211

1312
/**
@@ -45,12 +44,17 @@ public function isNamespacingValid(array $excludedNamespaces, array $includedNam
4544
);
4645
}
4746

48-
47+
/**
48+
* @param string[] $namespaces
49+
*/
4950
public function setExcludedNamespaces(array $namespaces = []): void
5051
{
5152
$this->excludedNamespaces = $namespaces;
5253
}
5354

55+
/**
56+
* @param string[] $namespaces
57+
*/
5458
public function setIncludedNamespaces(array $namespaces = []): void
5559
{
5660
$this->includedNamespaces = $namespaces;
@@ -59,65 +63,30 @@ public function setIncludedNamespaces(array $namespaces = []): void
5963
/**
6064
* Get all available commands from symfony
6165
*
62-
* @throws Exception
66+
* @return string[] Command names
6367
*/
64-
public function getAvailableCommands(string $format="xml", string $env="prod"): string|array
68+
public function getAvailableCommands(): array
6569
{
66-
$application = new Application($this->kernel);
67-
$application->setAutoExit(false);
68-
69-
$input = new ArrayInput(
70-
[
71-
'command' => 'list',
72-
'--format' => $format,
73-
'--env' => $env,
74-
]
75-
);
76-
77-
try {
78-
# needed for PHPUnit > 10
79-
# https://github.com/sebastianbergmann/phpunit/issues/5721
80-
if($this->kernel->getEnvironment() !== 'test')
81-
{
82-
Debug::enable();
83-
}
84-
85-
$output = new StreamOutput(fopen('php://memory', 'wb+'));
86-
$application->run($input, $output);
87-
88-
rewind($output->getStream());
89-
90-
if($format === "xml")
91-
{return stream_get_contents($output->getStream());}
92-
93-
if($format === "json")
94-
{return json_decode(
95-
stream_get_contents($output->getStream()),
96-
true,
97-
512,
98-
JSON_THROW_ON_ERROR
99-
);}
100-
101-
throw new \InvalidArgumentException('Only xml and json are allowed');
102-
} catch (\Throwable) {
103-
throw new \RuntimeException('Listing of commands could not be read');
104-
}
70+
return $this->kernel
71+
->getContainer()
72+
->get('console.command_loader')
73+
->getNames();
10574
}
10675

10776
/**
10877
* Execute the console command "list" and parse the output to have all available command.
10978
*
11079
* @return array<string, string[]> ["Namespace1" => ["Command1", "Command2"]]
11180
*
112-
* @throws Exception
81+
* @throws \InvalidArgumentException
11382
*/
114-
public function getCommands(string $env="prod"): array
83+
public function getCommands(): array
11584
{
11685
if (!$this->isNamespacingValid($this->excludedNamespaces, $this->includedNamespaces)) {
11786
throw new \InvalidArgumentException('Cannot combine excludedNamespaces with includedNamespaces');
11887
}
11988

120-
return $this->extractCommands($this->getAvailableCommands("json", $env));
89+
return $this->extractCommands($this->getAvailableCommands());
12190
}
12291

12392

@@ -127,17 +96,16 @@ public function getCommands(string $env="prod"): array
12796
* @return array<string, array<string, mixed>>
12897
* @throws Exception
12998
*/
130-
public function getAllowedCommandDetails(string $env="prod"): array
99+
public function getAllowedCommandDetails(): array
131100
{
132-
# var_dump($this->getCommands($env));
133-
return $this->getCommandDetails($this->getCommands($env));
101+
return $this->getCommandDetails($this->getAvailableCommands());
134102
}
135103

136104

137105
/**
138106
* Is the command-List wrapped in namespaces?
139107
*
140-
* @param array<string, array<string, string>> $commands
108+
* @param array<string, array<string, string>>|string[] $commands
141109
* @return string[]
142110
*/
143111
public function reduceNamespacedCommands(array $commands): array
@@ -166,25 +134,29 @@ public function reduceNamespacedCommands(array $commands): array
166134
}
167135

168136
/**
137+
* @param string[] $commands
169138
* @return array<string, array<string, mixed>>
170139
* @throws Exception
171140
*/
172141
public function getCommandDetails(array $commands): array
173142
{
174-
$availableCommands = $this->getAvailableCommands("json");
175143
$result = [];
176-
#$command->getDefinition();
177144

178-
# Is the command-List wrapped in namespaces?
179-
$commands = $this->reduceNamespacedCommands($commands);
145+
$commandLoader = $this->kernel->getContainer()->get('console.command_loader');
146+
$jsonDescriptor = new JsonDescriptor();
180147

181-
foreach ($availableCommands["commands"] as $command)
182-
{
183-
#var_dump($command);
184-
if(in_array($command["name"], $commands, true))
185-
{
186-
$result[$command["name"]] = $command;
148+
foreach ($commands as $commandName) {
149+
if (!$commandLoader->has($commandName)) {
150+
continue;
187151
}
152+
153+
/** @var Command $command */
154+
$command = $commandLoader->get($commandName);
155+
156+
$buffer = new BufferedOutput();
157+
$jsonDescriptor->describe($buffer, $command);
158+
159+
$result[$commandName] = json_decode($buffer->fetch(), true);
188160
}
189161

190162
if(count($result)===0)
@@ -193,48 +165,29 @@ public function getCommandDetails(array $commands): array
193165
return $result;
194166
}
195167

196-
197-
198-
199168
/**
200169
* Extract an array of available Symfony commands from the JSON output.
201170
*
202-
* @param array<string, array<int, mixed>> $commands
171+
* @param string[] $commands Command names
203172
* @return array<string, array<int|string, mixed>|string>
204-
* ["namespaces]
205-
* [0]
206-
* ["id"] => cache
207-
* ["commands"] => ["cache:clear", "cache:warmup", ...]
173+
* ["namespaces"][0]
208174
*/
209175
private function extractCommands(array $commands): array
210176
{
211-
if (count($commands) === 0) {
212-
return [];
213-
}
214-
215177
$commandsList = [];
216178

217-
try {
218-
foreach ($commands["namespaces"] as $namespace) {
219-
$namespaceId = (string) $namespace["id"];
220-
221-
# Blacklisting and Whitelisting
222-
if ((count($this->excludedNamespaces) > 0 && in_array($namespaceId, $this->excludedNamespaces, true))
223-
||
224-
(count($this->includedNamespaces) > 0 && !in_array($namespaceId, $this->includedNamespaces, true))
225-
) {
226-
continue;
227-
}
179+
foreach ($commands as $commandName) {
180+
$namespaceId = explode(':', $commandName)[0];
228181

229-
# Add Command Name to array
230-
foreach ($namespace["commands"] as $command) {
231-
232-
$commandsList[$namespaceId][$command] = $command;
233-
}
182+
# Blacklisting and Whitelisting
183+
if ((count($this->excludedNamespaces) > 0 && in_array($namespaceId, $this->excludedNamespaces, true))
184+
||
185+
(count($this->includedNamespaces) > 0 && !in_array($namespaceId, $this->includedNamespaces, true))
186+
) {
187+
continue;
234188
}
235-
} catch (Exception) {
236-
// return an empty CommandList
237-
$commandsList = ['ERROR: please check php bin/console list --format=json' => 'error'];
189+
190+
$commandsList[$namespaceId][$commandName] = $commandName;
238191
}
239192

240193
return $commandsList;

Tests/Controller/ApiControllerTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function testConsoleCommands(): void
4747
$jsonArray = json_decode($jsonResponse, true, 512, JSON_THROW_ON_ERROR);
4848

4949
$this->assertGreaterThanOrEqual(1, count($jsonArray));
50-
$this->assertArrayHasKey('_global', $jsonArray);
50+
$this->assertArrayHasKey('about', $jsonArray);
5151
$this->assertSame("assets:install", $jsonArray["assets"]["assets:install"]);
5252
$this->assertSame("debug:autowiring", $jsonArray["debug"]["debug:autowiring"]);
5353
}
@@ -68,7 +68,7 @@ public function testConsoleCommandsDetailsAll(): void
6868
$this->assertArrayHasKey('about', $commands);
6969
$this->assertSame("about", $commands["about"]["name"]);
7070

71-
$this->assertArrayHasKey('list', $commands);
71+
$this->assertArrayHasKey('assets:install', $commands);
7272
$this->assertArrayHasKey('cache:clear', $commands);
7373
}
7474

@@ -78,7 +78,7 @@ public function testConsoleCommandsDetailsAll(): void
7878
public function testConsoleCommandsDetails(): void
7979
{
8080
// List all available console commands
81-
$this->client->request('GET', '/command-scheduler/api/console_commands_details/about,list,cache:clear,asserts:install');
81+
$this->client->request('GET', '/command-scheduler/api/console_commands_details/about,list,cache:clear,assets:install,app:unknown');
8282
self::assertResponseIsSuccessful();
8383

8484
$jsonResponse = $this->client->getResponse()->getContent();
@@ -88,7 +88,7 @@ public function testConsoleCommandsDetails(): void
8888
$this->assertArrayHasKey('about', $commands);
8989
$this->assertSame("about", $commands["about"]["name"]);
9090

91-
$this->assertArrayHasKey('list', $commands);
91+
$this->assertArrayHasKey('assets:install', $commands);
9292
$this->assertArrayHasKey('cache:clear', $commands);
9393
}
9494

Tests/Service/CommandParserTest.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,22 @@ public function setUp(): void
2626
public function testGetCommandDetails(): void
2727
{
2828
# Single parameter
29-
$commandDetails = $this->commandParser->getCommandDetails(["help"]);
29+
$commandDetails = $this->commandParser->getCommandDetails(["about"]);
3030

3131
$this->assertIsArray($commandDetails);
32-
$this->assertArrayHasKey('help', $commandDetails);
33-
$this->assertSame("help", $commandDetails["help"]["name"]);
32+
$this->assertArrayHasKey('about', $commandDetails);
33+
$this->assertSame("about", $commandDetails["about"]["name"]);
3434

3535
# Multiple parameters
3636
$commandDetails = $this->commandParser->getCommandDetails(
37-
["help", "assets:install", "cache:clear"]
37+
["about", "assets:install", "cache:clear"]
3838
);
3939

4040
$this->assertIsArray($commandDetails);
41-
$this->assertArrayHasKey('help', $commandDetails);
42-
$this->assertSame("help", $commandDetails["help"]["name"]);
41+
$this->assertArrayHasKey('about', $commandDetails);
42+
$this->assertSame("about", $commandDetails["about"]["name"]);
4343

44-
$command = $commandDetails["help"];
44+
$command = $commandDetails["about"];
4545
$this->assertArrayHasKey('name', $command);
4646
$this->assertArrayHasKey('usage', $command);
4747
$this->assertArrayHasKey('description', $command);
@@ -66,7 +66,7 @@ public function testGetCommands(): void
6666
$commands = $this->commandParser->getCommands();
6767

6868
$this->assertIsArray($commands);
69-
$this->assertArrayHasKey('_global', $commands);
69+
$this->assertArrayHasKey('about', $commands);
7070
$this->assertSame("assets:install", $commands["assets"]["assets:install"]);
7171
}
7272

@@ -81,7 +81,7 @@ public function testGetAllowedCommands(): void
8181
$this->assertArrayHasKey('about', $commands);
8282
$this->assertSame("about", $commands["about"]["name"]);
8383

84-
$this->assertArrayHasKey('list', $commands);
84+
$this->assertArrayHasKey('assets:install', $commands);
8585
$this->assertArrayHasKey('cache:clear', $commands);
8686
}
8787

@@ -122,7 +122,7 @@ public function testWhitelistingGetCommands(): void
122122
$this->commandParser->setExcludedNamespaces([]);
123123
$this->commandParser->setIncludedNamespaces(["cache", "debug"]);
124124

125-
$commands = $this->commandParser->getCommands("test");
125+
$commands = $this->commandParser->getCommands();
126126

127127
$this->assertIsArray($commands);
128128
$this->assertArrayHasKey("debug", $commands);

0 commit comments

Comments
 (0)