Skip to content

Commit b2deac6

Browse files
committed
[Console] Add support for SignalableCommandInterface with invokable commands
1 parent 72c0a36 commit b2deac6

File tree

5 files changed

+141
-1
lines changed

5 files changed

+141
-1
lines changed

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ CHANGELOG
1414
* Add support for `LockableTrait` in invokable commands
1515
* Deprecate returning a non-integer value from a `\Closure` function set via `Command::setCode()`
1616
* Mark `#[AsCommand]` attribute as `@final`
17+
* Add support for `SignalableCommandInterface` with invokable commands
1718

1819
7.2
1920
---
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Console\Command;
13+
14+
use Symfony\Component\Console\Exception\InvalidArgumentException;
15+
use Symfony\Component\Console\Exception\LogicException;
16+
17+
/**
18+
* @internal
19+
*/
20+
class SignalableCommand extends Command implements SignalableCommandInterface
21+
{
22+
private ?SignalableCommandInterface $code = null;
23+
24+
public function setCode(callable $code): static
25+
{
26+
if (!$code instanceof SignalableCommandInterface) {
27+
throw new InvalidArgumentException(\sprintf('The callable must be an object that implements the "%s" interface.', SignalableCommandInterface::class));
28+
}
29+
30+
$this->code = $code;
31+
32+
return parent::setCode($code);
33+
}
34+
35+
public function getSubscribedSignals(): array
36+
{
37+
if (null === $this->code) {
38+
throw new LogicException('A callable must be set using the setCode() method.');
39+
}
40+
41+
return $this->code->getSubscribedSignals();
42+
}
43+
44+
public function handleSignal(int $signal, false|int $previousExitCode = 0): int|false
45+
{
46+
if (null === $this->code) {
47+
throw new LogicException('A callable must be set using the setCode() method.');
48+
}
49+
50+
return $this->code->handleSignal($signal, $previousExitCode);
51+
}
52+
}

src/Symfony/Component/Console/DependencyInjection/AddConsoleCommandPass.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Symfony\Component\Console\Attribute\AsCommand;
1515
use Symfony\Component\Console\Command\Command;
1616
use Symfony\Component\Console\Command\LazyCommand;
17+
use Symfony\Component\Console\Command\SignalableCommand;
18+
use Symfony\Component\Console\Command\SignalableCommandInterface;
1719
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
1820
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1921
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -51,7 +53,7 @@ public function process(ContainerBuilder $container): void
5153
}
5254

5355
$invokableRef = new Reference($id);
54-
$definition = $container->register($id .= '.command', $class = Command::class)
56+
$definition = $container->register($id .= '.command', $class = $r->implementsInterface(SignalableCommandInterface::class) ? SignalableCommand::class : Command::class)
5557
->addMethodCall('setCode', [$invokableRef]);
5658
} else {
5759
$invokableRef = null;

src/Symfony/Component/Console/Tests/ApplicationTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Console\Command\Command;
1818
use Symfony\Component\Console\Command\HelpCommand;
1919
use Symfony\Component\Console\Command\LazyCommand;
20+
use Symfony\Component\Console\Command\SignalableCommand;
2021
use Symfony\Component\Console\Command\SignalableCommandInterface;
2122
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
2223
use Symfony\Component\Console\CommandLoader\FactoryCommandLoader;
@@ -2255,6 +2256,47 @@ public function testSignalableRestoresStty()
22552256
$this->assertSame($previousSttyMode, $sttyMode);
22562257
}
22572258

2259+
public function testSignalableInvokableCommand()
2260+
{
2261+
$command = new SignalableCommand();
2262+
$command->setName('signal-invokable');
2263+
$command->setCode($invokable = new class implements SignalableCommandInterface {
2264+
public bool $signaled = false;
2265+
2266+
public function __invoke(): int
2267+
{
2268+
posix_kill(posix_getpid(), \SIGUSR1);
2269+
2270+
for ($i = 0; $i < 1000; ++$i) {
2271+
usleep(100);
2272+
if ($this->signaled) {
2273+
return 1;
2274+
}
2275+
}
2276+
2277+
return 0;
2278+
}
2279+
2280+
public function getSubscribedSignals(): array
2281+
{
2282+
return SignalRegistry::isSupported() ? [\SIGUSR1] : [];
2283+
}
2284+
2285+
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
2286+
{
2287+
$this->signaled = true;
2288+
2289+
return false;
2290+
}
2291+
});
2292+
2293+
$application = $this->createSignalableApplication($command, null);
2294+
$application->setSignalsToDispatchEvent(\SIGUSR1);
2295+
2296+
$this->assertSame(1, $application->run(new ArrayInput(['signal-invokable'])));
2297+
$this->assertTrue($invokable->signaled);
2298+
}
2299+
22582300
/**
22592301
* @requires extension pcntl
22602302
*/

src/Symfony/Component/Console/Tests/DependencyInjection/AddConsoleCommandPassTest.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Symfony\Component\Console\Attribute\AsCommand;
1616
use Symfony\Component\Console\Command\Command;
1717
use Symfony\Component\Console\Command\LazyCommand;
18+
use Symfony\Component\Console\Command\SignalableCommand;
19+
use Symfony\Component\Console\Command\SignalableCommandInterface;
1820
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
1921
use Symfony\Component\Console\DependencyInjection\AddConsoleCommandPass;
2022
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
@@ -322,6 +324,29 @@ public function testProcessInvokableCommand()
322324
$command = $container->get('console.command_loader')->get('invokable');
323325

324326
self::assertTrue($container->has('invokable_command.command'));
327+
self::assertSame(Command::class, $container->getDefinition('invokable_command.command')->getClass());
328+
self::assertSame('The command description', $command->getDescription());
329+
self::assertSame('The %command.name% command help content.', $command->getHelp());
330+
}
331+
332+
public function testProcessInvokableSignalableCommand()
333+
{
334+
$container = new ContainerBuilder();
335+
$container->addCompilerPass(new AddConsoleCommandPass(), PassConfig::TYPE_BEFORE_REMOVING);
336+
337+
$definition = new Definition(InvokableSignalableCommand::class);
338+
$definition->addTag('console.command', [
339+
'command' => 'invokable-signalable',
340+
'description' => 'The command description',
341+
'help' => 'The %command.name% command help content.',
342+
]);
343+
$container->setDefinition('invokable_signalable_command', $definition);
344+
345+
$container->compile();
346+
$command = $container->get('console.command_loader')->get('invokable-signalable');
347+
348+
self::assertTrue($container->has('invokable_signalable_command.command'));
349+
self::assertSame(SignalableCommand::class, $container->getDefinition('invokable_signalable_command.command')->getClass());
325350
self::assertSame('The command description', $command->getDescription());
326351
self::assertSame('The %command.name% command help content.', $command->getHelp());
327352
}
@@ -361,3 +386,21 @@ public function __invoke(): void
361386
{
362387
}
363388
}
389+
390+
#[AsCommand(name: 'invokable-signalable', description: 'Just testing', help: 'The %command.name% help content.')]
391+
class InvokableSignalableCommand implements SignalableCommandInterface
392+
{
393+
public function __invoke(): void
394+
{
395+
}
396+
397+
public function getSubscribedSignals(): array
398+
{
399+
return [];
400+
}
401+
402+
public function handleSignal(int $signal, false|int $previousExitCode = 0): int|false
403+
{
404+
return false;
405+
}
406+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy