Skip to content

Commit

Permalink
[Console][Messenger] add RunCommandMessage and `RunCommandMessageHa…
Browse files Browse the repository at this point in the history
…ndler`
  • Loading branch information
kbond authored and fabpot committed Jul 30, 2023
1 parent 510e51f commit dd5b0b7
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
use Symfony\Component\Config\ResourceCheckerInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Messenger\RunCommandMessageHandler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
Expand Down Expand Up @@ -253,6 +254,11 @@ public function load(array $configs, ContainerBuilder $container)
if (!class_exists(DebugCommand::class)) {
$container->removeDefinition('console.command.dotenv_debug');
}

if (!class_exists(RunCommandMessageHandler::class)) {
$container->removeDefinition('console.messenger.application');
$container->removeDefinition('console.messenger.execute_command_handler');
}
}

// Load Cache configuration first as it is used by other components
Expand Down
15 changes: 15 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/console.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@
use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand;
use Symfony\Bundle\FrameworkBundle\Command\WorkflowDumpCommand;
use Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber;
use Symfony\Component\Console\EventListener\ErrorListener;
use Symfony\Component\Console\Messenger\RunCommandMessageHandler;
use Symfony\Component\Dotenv\Command\DebugCommand as DotenvDebugCommand;
use Symfony\Component\Messenger\Command\ConsumeMessagesCommand;
use Symfony\Component\Messenger\Command\DebugCommand as MessengerDebugCommand;
Expand Down Expand Up @@ -364,5 +366,18 @@
service('secrets.local_vault')->ignoreOnInvalid(),
])
->tag('console.command')

->set('console.messenger.application', Application::class)
->share(false)
->call('setAutoExit', [false])
->args([
service('kernel'),
])

->set('console.messenger.execute_command_handler', RunCommandMessageHandler::class)
->args([
service('console.messenger.application'),
])
->tag('messenger.message_handler')
;
};
1 change: 1 addition & 0 deletions src/Symfony/Component/Console/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CHANGELOG
* Add `SignalMap` to map signal value to its name
* Multi-line text in vertical tables is aligned properly
* The application can also catch errors with `Application::setCatchErrors(true)`
* Add `RunCommandMessage` and `RunCommandMessageHandler`

6.3
---
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Console\Exception;

use Symfony\Component\Console\Messenger\RunCommandContext;

/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
final class RunCommandFailedException extends RuntimeException
{
public function __construct(\Throwable|string $exception, public readonly RunCommandContext $context)
{
parent::__construct(
$exception instanceof \Throwable ? $exception->getMessage() : $exception,
$exception instanceof \Throwable ? $exception->getCode() : 0,
$exception instanceof \Throwable ? $exception : null,
);
}
}
23 changes: 23 additions & 0 deletions src/Symfony/Component/Console/Messenger/RunCommandContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Console\Messenger;

/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
final class RunCommandContext extends RunCommandMessage
{
public function __construct(RunCommandMessage $message, public readonly int $exitCode, public readonly string $output)
{
parent::__construct($message->input, $message->throwOnFailure, $message->catchExceptions);
}
}
36 changes: 36 additions & 0 deletions src/Symfony/Component/Console/Messenger/RunCommandMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Console\Messenger;

use Symfony\Component\Console\Exception\RunCommandFailedException;

/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
class RunCommandMessage implements \Stringable
{
/**
* @param bool $throwOnFailure If the command has a non-zero exit code, throw {@see RunCommandFailedException}
* @param bool $catchExceptions @see Application::setCatchExceptions()
*/
public function __construct(
public readonly string $input,
public readonly bool $throwOnFailure = true,
public readonly bool $catchExceptions = false,
) {
}

public function __toString(): string
{
return $this->input;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Console\Messenger;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RunCommandFailedException;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;

/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
final class RunCommandMessageHandler
{
public function __construct(private readonly Application $application)
{
}

public function __invoke(RunCommandMessage $message): RunCommandContext
{
$input = new StringInput($message->input);
$output = new BufferedOutput();

$this->application->setCatchExceptions($message->catchExceptions);

try {
$exitCode = $this->application->run($input, $output);
} catch (\Throwable $e) {
throw new RunCommandFailedException($e, new RunCommandContext($message, Command::FAILURE, $output->fetch()));
}

if ($message->throwOnFailure && Command::SUCCESS !== $exitCode) {
throw new RunCommandFailedException(sprintf('Command "%s" exited with code "%s".', $message->input, $exitCode), new RunCommandContext($message, $exitCode, $output->fetch()));
}

return new RunCommandContext($message, $exitCode, $output->fetch());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Console\Tests\Messenger;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RunCommandFailedException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Messenger\RunCommandMessage;
use Symfony\Component\Console\Messenger\RunCommandMessageHandler;
use Symfony\Component\Console\Output\OutputInterface;

/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
final class RunCommandMessageHandlerTest extends TestCase
{
public function testExecutesCommand()
{
$handler = new RunCommandMessageHandler($this->createApplicationWithCommand());
$context = $handler(new RunCommandMessage('test:command'));

$this->assertSame(0, $context->exitCode);
$this->assertStringContainsString('some message', $context->output);
}

public function testExecutesCommandThatThrowsException()
{
$handler = new RunCommandMessageHandler($this->createApplicationWithCommand());

try {
$handler(new RunCommandMessage('test:command --throw'));
} catch (RunCommandFailedException $e) {
$this->assertSame(1, $e->context->exitCode);
$this->assertStringContainsString('some message', $e->context->output);
$this->assertInstanceOf(\RuntimeException::class, $e->getPrevious());
$this->assertSame('exception message', $e->getMessage());

return;
}

$this->fail('Exception not thrown.');
}

public function testExecutesCommandThatCatchesThrownException()
{
$handler = new RunCommandMessageHandler($this->createApplicationWithCommand());
$context = $handler(new RunCommandMessage('test:command --throw -v', throwOnFailure: false, catchExceptions: true));

$this->assertSame(1, $context->exitCode);
$this->assertStringContainsString('[RuntimeException]', $context->output);
$this->assertStringContainsString('exception message', $context->output);
}

public function testThrowOnNonSuccess()
{
$handler = new RunCommandMessageHandler($this->createApplicationWithCommand());

try {
$handler(new RunCommandMessage('test:command --exit=1'));
} catch (RunCommandFailedException $e) {
$this->assertSame(1, $e->context->exitCode);
$this->assertStringContainsString('some message', $e->context->output);
$this->assertSame('Command "test:command --exit=1" exited with code "1".', $e->getMessage());
$this->assertNull($e->getPrevious());

return;
}

$this->fail('Exception not thrown.');
}

private function createApplicationWithCommand(): Application
{
$application = new Application();
$application->setAutoExit(false);
$application->addCommands([
new class() extends Command {
public function configure(): void
{
$this
->setName('test:command')
->addOption('throw')
->addOption('exit', null, InputOption::VALUE_REQUIRED, 0)
;
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->write('some message');

if ($input->getOption('throw')) {
throw new \RuntimeException('exception message');
}

return (int) $input->getOption('exit');
}
},
]);

return $application;
}
}
1 change: 1 addition & 0 deletions src/Symfony/Component/Console/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"symfony/event-dispatcher": "^5.4|^6.0|^7.0",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/lock": "^5.4|^6.0|^7.0",
"symfony/messenger": "^5.4|^6.0|^7.0",
"symfony/process": "^5.4|^6.0|^7.0",
"symfony/var-dumper": "^5.4|^6.0|^7.0",
"psr/log": "^1|^2|^3"
Expand Down

0 comments on commit dd5b0b7

Please sign in to comment.