Skip to content

Commit

Permalink
merged branch fabpot/console-dispatcher (PR #7466)
Browse files Browse the repository at this point in the history
This PR was merged into the master branch.

Discussion
----------

Console dispatcher

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #3889, #6124
| License       | MIT
| Doc PR        | symfony/symfony-docs#2352

refs #1884, #1929

This is an alternative implementation for adding events to console applications.

This implementation has the following features:

* Available for anyone using the Console component and it is not tied to
  FrameworkBundle (this is important as one thing we are trying to solve is
  email sending from a command, and frameworks like Silex using the Console
  component needs a solution too);

* Non-intrusive as the current code has not been changed (except for renaming
  an internal variable that was wrongly named -- so that's not strictly needed
  for this PR)

* The new DispatchableApplication class also works without a dispatcher,
  falling back to the regular behavior. That makes easy to create applications
  that can benefit from a dispatcher when available, but can still work
  otherwise.

* Besides the *before* and *after* events, there is also an *exception* event
  that is dispatched whenever an exception is thrown.

* Each event is quite powerful and can manipulate the input, the output, but
  also the command to be executed.

Commits
-------

4f9a55a refactored the implementation of how a console application can handle events
4edf29d added helperSet to console event objects
f224102 Added events for CLI commands
  • Loading branch information
fabpot committed Mar 25, 2013
2 parents e94346e + 4f9a55a commit c1bd3b5
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 9 deletions.
67 changes: 58 additions & 9 deletions src/Symfony/Component/Console/Application.php
Expand Up @@ -27,6 +27,10 @@
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Helper\ProgressHelper;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleForExceptionEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;

/**
* An Application is the container for a collection of commands.
Expand Down Expand Up @@ -56,6 +60,7 @@ class Application
private $autoExit;
private $definition;
private $helperSet;
private $dispatcher;

/**
* Constructor.
Expand All @@ -80,6 +85,11 @@ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
}
}

public function setDispatcher(EventDispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}

/**
* Runs the current application.
*
Expand All @@ -103,7 +113,7 @@ public function run(InputInterface $input = null, OutputInterface $output = null
}

try {
$statusCode = $this->doRun($input, $output);
$exitCode = $this->doRun($input, $output);
} catch (\Exception $e) {
if (!$this->catchExceptions) {
throw $e;
Expand All @@ -114,21 +124,21 @@ public function run(InputInterface $input = null, OutputInterface $output = null
} else {
$this->renderException($e, $output);
}
$statusCode = $e->getCode();
$exitCode = $e->getCode();

$statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1;
$exitCode = is_numeric($exitCode) && $exitCode ? $exitCode : 1;
}

if ($this->autoExit) {
if ($statusCode > 255) {
$statusCode = 255;
if ($exitCode > 255) {
$exitCode = 255;
}
// @codeCoverageIgnoreStart
exit($statusCode);
exit($exitCode);
// @codeCoverageIgnoreEnd
}

return $statusCode;
return $exitCode;
}

/**
Expand Down Expand Up @@ -190,10 +200,10 @@ public function doRun(InputInterface $input, OutputInterface $output)
$command = $this->find($name);

$this->runningCommand = $command;
$statusCode = $command->run($input, $output);
$exitCode = $this->doRunCommand($command, $input, $output);
$this->runningCommand = null;

return is_numeric($statusCode) ? $statusCode : 0;
return is_numeric($exitCode) ? $exitCode : 0;
}

/**
Expand Down Expand Up @@ -911,6 +921,45 @@ public function getTerminalDimensions()
return array(null, null);
}

/**
* Runs the current command.
*
* If an event dispatcher has been attached to the application,
* events are also dispatched during the life-cycle of the command.
*
* @param Command $command A Command instance
* @param InputInterface $input An Input instance
* @param OutputInterface $output An Output instance
*
* @return integer 0 if everything went fine, or an error code
*/
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{
if (null === $this->dispatcher) {
return $command->run($input, $output);
}

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

try {
$exitCode = $command->run($input, $output);
} catch (\Exception $e) {
$event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode());
$this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event);

$event = new ConsoleForExceptionEvent($command, $input, $output, $e, $event->getExitCode());
$this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event);

throw $event->getException();
}

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

return $event->getExitCode();
}

/**
* Gets the name of the command based on input.
*
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Component/Console/CHANGELOG.md
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
2.3.0
-----

* added support for events in `Application`
* added a way to normalize EOLs in `ApplicationTester::getDisplay()` and `CommandTester::getDisplay()`
* added a way to set the progress bar progress via the `setCurrent` method

Expand Down
55 changes: 55 additions & 0 deletions src/Symfony/Component/Console/ConsoleEvents.php
@@ -0,0 +1,55 @@
<?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;

/**
* Contains all events dispatched by an Application.
*
* @author Francesco Levorato <git@flevour.net>
*/
final class ConsoleEvents
{
/**
* The COMMAND event allows you to attach listeners before any command is
* executed by the console. It also allows you to modify the command, input and output
* before they are handled to the command.
*
* The event listener method receives a Symfony\Component\Console\Event\ConsoleCommandEvent
* instance.
*
* @var string
*/
const COMMAND = 'console.command';

/**
* The TERMINATE event allows you to attach listeners after a command is
* executed by the console.
*
* The event listener method receives a Symfony\Component\Console\Event\ConsoleTerminateEvent
* instance.
*
* @var string
*/
const TERMINATE = 'console.terminate';

/**
* The EXCEPTION event occurs when an uncaught exception appears.
*
* This event allows you to deal with the exception or
* to modify the thrown exception. The event listener method receives
* a Symfony\Component\Console\Event\ConsoleForExceptionEvent
* instance.
*
* @var string
*/
const EXCEPTION = 'console.exception';
}
25 changes: 25 additions & 0 deletions src/Symfony/Component/Console/Event/ConsoleCommandEvent.php
@@ -0,0 +1,25 @@
<?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\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Allows to do things before the command is executed.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConsoleCommandEvent extends ConsoleEvent
{
}
67 changes: 67 additions & 0 deletions src/Symfony/Component/Console/Event/ConsoleEvent.php
@@ -0,0 +1,67 @@
<?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\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\Event;

/**
* Allows to inspect input and output of a command.
*
* @author Francesco Levorato <git@flevour.net>
*/
class ConsoleEvent extends Event
{
protected $command;

private $input;
private $output;

public function __construct(Command $command, InputInterface $input, OutputInterface $output)
{
$this->command = $command;
$this->input = $input;
$this->output = $output;
}

/**
* Gets the command that is executed.
*
* @return Command A Command instance
*/
public function getCommand()
{
return $this->command;
}

/**
* Gets the input instance.
*
* @return InputInterface An InputInterface instance
*/
public function getInput()
{
return $this->input;
}

/**
* Gets the output instance.
*
* @return OutputInterface An OutputInterface instance
*/
public function getOutput()
{
return $this->output;
}
}
67 changes: 67 additions & 0 deletions src/Symfony/Component/Console/Event/ConsoleForExceptionEvent.php
@@ -0,0 +1,67 @@
<?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\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Allows to handle exception thrown in a command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConsoleForExceptionEvent extends ConsoleEvent
{
private $exception;
private $exitCode;

public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode)
{
parent::__construct($command, $input, $output);

$this->setException($exception);
$this->exitCode = $exitCode;
}

/**
* Returns the thrown exception.
*
* @return \Exception The thrown exception
*/
public function getException()
{
return $this->exception;
}

/**
* Replaces the thrown exception.
*
* This exception will be thrown if no response is set in the event.
*
* @param \Exception $exception The thrown exception
*/
public function setException(\Exception $exception)
{
$this->exception = $exception;
}

/**
* Gets the exit code.
*
* @return integer The command exit code
*/
public function getExitCode()
{
return $this->exitCode;
}
}

0 comments on commit c1bd3b5

Please sign in to comment.