Skip to content

[Console] Review console.ERROR related behavior #22441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<services>

<service id="console.exception_listener" class="Symfony\Component\Console\EventListener\ExceptionListener" public="false">
<service id="console.error_listener" class="Symfony\Component\Console\EventListener\ErrorListener" public="false">
<argument type="service" id="logger" on-invalid="null" />
<tag name="kernel.event_subscriber" />
<tag name="monolog.logger" channel="console" />
Expand Down
112 changes: 58 additions & 54 deletions src/Symfony/Component/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,46 +119,30 @@ public function run(InputInterface $input = null, OutputInterface $output = null
$output = new ConsoleOutput();
}

if (null !== $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
@trigger_error(sprintf('The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead.'), E_USER_DEPRECATED);
}

$this->configureIO($input, $output);

try {
$e = null;
$exitCode = $this->doRun($input, $output);
} catch (\Exception $e) {
$exception = $e;
} catch (\Throwable $e) {
$exception = new FatalThrowableError($e);
}

if (null !== $e && null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e, $e->getCode(), $this->runningCommand);
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);

$e = $event->getError();

if ($event->isErrorHandled()) {
$e = null;
$exitCode = 0;
} else {
if (!$e instanceof \Exception) {
throw $e;
}
$exitCode = $e->getCode();
}

$event = new ConsoleTerminateEvent($this->runningCommand, $input, $output, $exitCode);
$this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);
} catch (\Exception $x) {
$e = $x;
} catch (\Throwable $x) {
$e = new FatalThrowableError($x);
}

if (null !== $e) {
if (!$this->catchExceptions) {
throw $e;
throw $x;
}

if ($output instanceof ConsoleOutputInterface) {
$this->renderException($exception, $output->getErrorOutput());
$this->renderException($e, $output->getErrorOutput());
} else {
$this->renderException($exception, $output);
$this->renderException($e, $output);
}

$exitCode = $e->getCode();
Expand Down Expand Up @@ -214,8 +198,26 @@ public function doRun(InputInterface $input, OutputInterface $output)
$input = new ArrayInput(array('command' => $this->defaultCommand));
}

// the command name MUST be the first element of the input
$command = $this->find($name);
try {
$e = $this->runningCommand = null;
// the command name MUST be the first element of the input
$command = $this->find($name);
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
if (null !== $e) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
$e = $event->getError();

if (0 === $event->getExitCode()) {
return 0;
}
}

throw $e;
}

$this->runningCommand = $command;
$exitCode = $this->doRunCommand($command, $input, $output);
Expand Down Expand Up @@ -864,13 +866,7 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
}

if (null === $this->dispatcher) {
try {
return $command->run($input, $output);
} catch (\Exception $e) {
throw $e;
} catch (\Throwable $e) {
throw new FatalThrowableError($e);
}
return $command->run($input, $output);
}

// bind before the console.command event, so the listeners have access to input options/arguments
Expand All @@ -882,37 +878,45 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
}

$event = new ConsoleCommandEvent($command, $input, $output);
$this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);

if ($event->commandShouldRun()) {
try {
$e = null;
try {
$e = null;
$this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event);

if ($event->commandShouldRun()) {
$exitCode = $command->run($input, $output);
} catch (\Exception $x) {
$e = $x;
} catch (\Throwable $x) {
$e = new FatalThrowableError($x);
} else {
$exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
}

if (null !== $e) {
$event = new ConsoleExceptionEvent($command, $input, $output, $e, $e->getCode(), false);
} catch (\Exception $e) {
} catch (\Throwable $e) {
}
if (null !== $e) {
if ($this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
$x = $e instanceof \Exception ? $e : new FatalThrowableError($e);
$event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode());
$this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);

if ($e !== $event->getException()) {
@trigger_error('The "console.exception" event is deprecated since version 3.3 and will be removed in 4.0. Use the "console.error" event instead.', E_USER_DEPRECATED);

$x = $e = $event->getException();
if ($x !== $event->getException()) {
$e = $event->getException();
}
}
$event = new ConsoleErrorEvent($input, $output, $e, $command);
$this->dispatcher->dispatch(ConsoleEvents::ERROR, $event);
$e = $event->getError();

throw $x;
if (0 === $exitCode = $event->getExitCode()) {
$e = null;
}
} else {
$exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
}

$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
$this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);

if (null !== $e) {
throw $e;
}

return $event->getExitCode();
}

Expand Down
3 changes: 1 addition & 2 deletions src/Symfony/Component/Console/ConsoleEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ final class ConsoleEvents
const EXCEPTION = 'console.exception';

/**
* The ERROR event occurs when an uncaught exception appears or
* a throwable error.
* The ERROR event occurs when an uncaught exception or error appears.
*
* This event allows you to deal with the exception/error or
* to modify the thrown exception.
Expand Down
63 changes: 17 additions & 46 deletions src/Symfony/Component/Console/Event/ConsoleErrorEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,22 @@
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Debug\Exception\FatalThrowableError;

/**
* Allows to handle throwables thrown while running a command.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class ConsoleErrorEvent extends ConsoleExceptionEvent
final class ConsoleErrorEvent extends ConsoleEvent
{
private $error;
private $handled = false;
private $exitCode;

public function __construct(InputInterface $input, OutputInterface $output, $error, $exitCode, Command $command = null)
public function __construct(InputInterface $input, OutputInterface $output, $error, Command $command = null)
{
if (!$error instanceof \Throwable && !$error instanceof \Exception) {
throw new InvalidArgumentException(sprintf('The error passed to ConsoleErrorEvent must be an instance of \Throwable or \Exception, "%s" was passed instead.', is_object($error) ? get_class($error) : gettype($error)));
}

$exception = $error;
if (!$error instanceof \Exception) {
$exception = new FatalThrowableError($error);
}
parent::__construct($command, $input, $output, $exception, $exitCode, false);
parent::__construct($command, $input, $output);

$this->error = $error;
$this->setError($error);
}

/**
Expand Down Expand Up @@ -67,46 +58,26 @@ public function setError($error)
}

/**
* Marks the error/exception as handled.
*
* If it is not marked as handled, the error/exception will be displayed in
* the command output.
*/
public function markErrorAsHandled()
{
$this->handled = true;
}

/**
* Whether the error/exception is handled by a listener or not.
*
* If it is not yet handled, the error/exception will be displayed in the
* command output.
* Sets the exit code.
*
* @return bool
*/
public function isErrorHandled()
{
return $this->handled;
}

/**
* @deprecated Since version 3.3, to be removed in 4.0. Use getError() instead
* @param int $exitCode The command exit code
*/
public function getException()
public function setExitCode($exitCode)
{
@trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Use ConsoleErrorEvent::getError() instead.', __METHOD__), E_USER_DEPRECATED);
$this->exitCode = (int) $exitCode;

return parent::getException();
$r = new \ReflectionProperty($this->error, 'code');
$r->setAccessible(true);
$r->setValue($this->error, $this->exitCode);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why altering the exception ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because the other part of the exception handling uses getCode as the way to set the exit status code from an exception

}

/**
* @deprecated Since version 3.3, to be removed in 4.0. Use setError() instead
* Gets the exit code.
*
* @return int The command exit code
*/
public function setException(\Exception $exception)
public function getExitCode()
{
@trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Use ConsoleErrorEvent::setError() instead.', __METHOD__), E_USER_DEPRECATED);

parent::setException($exception);
return null !== $this->exitCode ? $this->exitCode : ($this->error->getCode() ?: 1);
}
}
12 changes: 5 additions & 7 deletions src/Symfony/Component/Console/Event/ConsoleExceptionEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\Component\Console\Event;

@trigger_error(sprintf('The "%s" class is deprecated since version 3.3 and will be removed in 4.0. Use the ConsoleErrorEvent instead.', ConsoleExceptionEvent::class), E_USER_DEPRECATED);

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
Expand All @@ -20,22 +22,18 @@
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @deprecated ConsoleExceptionEvent is deprecated since version 3.3 and will be removed in 4.0. Use ConsoleErrorEvent instead.
* @deprecated since version 3.3, to be removed in 4.0. Use ConsoleErrorEvent instead.
*/
class ConsoleExceptionEvent extends ConsoleEvent
{
private $exception;
private $exitCode;

public function __construct(Command $command = null, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode, $deprecation = true)
public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode)
{
if ($deprecation) {
@trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use the ConsoleErrorEvent instead.', __CLASS__), E_USER_DEPRECATED);
}

parent::__construct($command, $input, $output);

$this->exception = $exception;
$this->setException($exception);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useless change

$this->exitCode = (int) $exitCode;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ConsoleTerminateEvent extends ConsoleEvent
*/
private $exitCode;

public function __construct(Command $command = null, InputInterface $input, OutputInterface $output, $exitCode)
public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode)
{
parent::__construct($command, $input, $output);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
namespace Symfony\Component\Console\EventListener;

use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
* @author James Halsall <james.t.halsall@googlemail.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class ExceptionListener implements EventSubscriberInterface
class ErrorListener implements EventSubscriberInterface
{
private $logger;

Expand Down
Loading