Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[2.2] [FrameworkBundle] Add events to console commands #3889

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

Closed
wants to merge 15 commits into from

Conversation

flevour
Copy link
Contributor

@flevour flevour commented Apr 11, 2012

Bug fix: no
Feature addition: yes
Backwards compatibility break: no
Symfony2 tests pass: Build Status

Fixes the following tickets: #1929
Todo:

  • tests needed?

if (true === $input->hasParameterOption(array('--shell', '-s'))) {
$shell = new Shell($this);
$shell->setProcessIsolation($input->hasParameterOption(array('--process-isolation')));
$shell->run();

return 0;
$statusCode = 0;
Copy link
Member

Choose a reason for hiding this comment

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

the code when using the shell should not be changed. Events should be dispatched by the commands launched inside the shell, not before and after running the shell itself

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright, I was unsure about that point. Fixed.

@flevour
Copy link
Contributor Author

flevour commented Apr 12, 2012

I am bit confused here. I await some helping pointers from any core dev.
EventDispatcher yes? If no, how to do this without recreating another EventDispatcher?

$dispatcher = $this->kernel->getContainer()->get('event_dispatcher');
$dispatcher->dispatch('console.init');

$statusCode = parent::doRun($input, $output);
Copy link
Contributor

Choose a reason for hiding this comment

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

this variable would better be named $exitCode

@lsmith77
Copy link
Contributor

i havent tested this branch, but in concept it seems good to me. please also add a test case. see RegisterKernelListenersPassTest

@stof
Copy link
Member

stof commented Apr 16, 2012

@lsmith77 no need to add a test for the compiler pass as it should be removed (it duplicates things already possible with the existing pass for event listeners as it is exactly the same with a different tag)

@lsmith77
Copy link
Contributor

which "existing pass for event listeners" are you talking about?

@lsmith77
Copy link
Contributor

are you saying that RegisterKernelListenersPass should be refactored to also handle "console" events?

@stof
Copy link
Member

stof commented Apr 16, 2012

@lsmith77 there is nothing to refactor. Simply register a listener with <tag name="kernel.event_listener" event="console.init" method="foo" />

@lsmith77
Copy link
Contributor

ah i see now .. since he is using $container->getDefinition('event_dispatcher'); instead of a separate event dispatcher (which would not make sense).

its maybe a bit iffy to call it kernel.event_listener now, but also not totally wrong as its just a result of the console being sort of a 2nd class citizen with our entire kernel setup.

@flevour
Copy link
Contributor Author

flevour commented Apr 16, 2012

Thanks for your feedback, I'll try to work on this PR in the next few days.

@flevour
Copy link
Contributor Author

flevour commented Apr 24, 2012

I applied the suggestions above.
Namely:

  • removed the CompilerPass
  • renamed $statusCode into $exitCode
  • implemented 2 Events (init and terminate) to let the programmer extend behaviour before and after command execution. I couldn't use 'ConsoleEvents::EXIT' as the 'exit' token is reserved.

*
* @author Francesco Levorato <[email protected]>
*/
class ConsoleInitEvent extends KernelEvent
Copy link
Member

Choose a reason for hiding this comment

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

you should not extend KernelEvent as KernelEvent has the request and the request type which are totally irrelevant here

@flevour
Copy link
Contributor Author

flevour commented May 2, 2012

I probably did a bit of a mess with my last push. Do you have any more suggestions/comments to add?

/**
* The input received by the command.
*
* @var Symfony\Component\Console\Input\InputInterface
Copy link
Member

Choose a reason for hiding this comment

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

you should use the short class name (the other solution would be the FQCN, i.e. with the leadaing \ but Sf2 uses the short classname everywhere). The current code will break the autocompletion is some IDE (PhpStorm would consider it as Symfony\Bundle\FrameworkBundle\Console\Event\Symfony\Component\Console\Input\InputInterface for instance)

@Seldaek
Copy link
Member

Seldaek commented Oct 18, 2012

Dirty hack idea (because I enjoy that sport): before dispatching register a new autoloader that will create any class that does not exist with:

eval(<<<CODE
class $class
{
    public function __call($name, $args) 
    {
    }
}
CODE
);

That way services can be created and then called even if they don't exist anymore. It would mask any classname mistake though, so it should only be registered when the cache:clear command is called IMO.

@stof
Copy link
Member

stof commented Oct 18, 2012

@Seldaek running any command in the prod environment with an outdated cache would also break things

@flevour
Copy link
Contributor Author

flevour commented Oct 18, 2012

I have given some thought but I still don't have any solution.
Let me recap.

When executed with --no-debug, the console application will use the currently cached kernel and container. If a listener/subscriber class gets removed (e.g.: in a deploy) the console application breaks with a fatal error, as it tries to load the event dispatcher from the cached container which in turn contains references to the not-existing-any-more class. In general all usages of the console application will break, but in particular it won't be possible to use it to clear the cache.

I can think of 3 approaches:
A. Preventive care: avoid edge cases aka fatal errors before they happen, take some countermeasures to avoid them
B. Cure the symptom: try to handle the problem when it occurs.

Here is a list of some proposals:
A

  1. implement a compiler pass to store any listener/subscriber class name in a container parameter as an array. The console application or the cache clear command, will loop through this parameter and check the existence of each element
  2. create a class ParanoidEventDispatcher which dynamically extends from %event_dispatcher.class% in order to add our own logic to the addListener/addSubscriber methods (note that it's not possible to wrap the EventDispatcher as the fatal error happens on its instantiation) and implement a compiler pass to inject the new ParanoidEventDispatcher in place of the original one.
  3. implement a new event dispatcher ParanoidEventDispatcher with the needed fixes in addListener/addSubscriber methods, make it available as a service and use it instead of the event_dispatcher service. Duplicating the logic contained in RegisterKernelListenersPass in order to attach to this new service the same listeners and subscribers of event_dispatcher.
  4. force a rm -rf before cache:clear execution
    B
  5. @Seldaek solution
  6. some creepy shutdown function to handle the fatal error and give hints to the user

I don't like any of these proposals, but if I really I had to choose, my order of choice would be A3, A1, A2.

@stof
Copy link
Member

stof commented Oct 18, 2012

The cache:clear command cannot be responsible to check the instantiation of the dispatcher as the dispatcher is sued before calling the command.

@flevour
Copy link
Contributor Author

flevour commented Oct 19, 2012

@stof I assume you are commenting on A4. Yes, the code for rm -rf would need to be in the Application not in the CacheClearCommand itself.

@Seldaek
Copy link
Member

Seldaek commented Oct 19, 2012

IMO the only command that matters for the issue of "listener classes
gone" if cache:clear, if it's called maybe the events should just not be
triggered at all, or you should implement one of those hacks.

It's the same with the routing, annotations and whatever else is in the
cache, if you remove the class and you don't clear your cache in prod
environment you will have weird bugs where the framework goes further
than it should before blowing up on a missing class.

It is a predicate of the framework that if you change the code, you
should clear your prod cache before deploying, so IMO it's ok to have
that problem for other commands. But the cache:clear command must work
at all costs (including dropping listeners, maybe while outputting a
warning), because that's the way many people will use to clear/warmup
their cache.

@flevour
Copy link
Contributor Author

flevour commented Oct 30, 2012

One other (very crazy) idea could be:

A5: also cache (aka copy) the event listeners/subscribers classes and all their dependencies and configure the autoloader accordingly. Even if it's probably more hassle than it's worth, It would make some sense as a cache works only as long as all the classes it depends on still exist. The extreme result of this path is we end up caching the whole application, but if we apply this only in the case of cache:clear dependencies, it might be not as bad.

Going back to a more traditional thinking, A3 (implement a new dispatcher) might be the cleanest/easiest solution to offer console events and at the same provide useful output should something break during cache:clear.

Please share your opinions and suggestions so I can get this PR moving again!

@bamarni
Copy link
Contributor

bamarni commented Oct 31, 2012

How about a flag on the command, just like the isEnabled method, there could be an isSilent method defaulting to false, and when set to true (eg. cache:clear command) the Application wouldn't dispatch any event?

@bamarni
Copy link
Contributor

bamarni commented Nov 17, 2012

@flevour : I've continued your PR with this commit : bamarni/symfony@3072ee3

Are you fine with this implementation? I've not tested it yet but it should give you an idea about what I meant.

Unfortunately I can't submit a PR against your fork as github doesn't show it to me in the available symfony forks list. Maybe you can cherry-pick it?

@bamarni
Copy link
Contributor

bamarni commented Nov 25, 2012

@flevour : did you had time to look at my commit? It should fix the issue, I guess now it only misses some small tweaks and unit tests.

If you don't have time to devote on this, I'd be happy to help you to sort this out by opening another PR, but of course I'll squash the commits with your name so that you keep the authorship for this feature!

@dlsniper
Copy link
Contributor

@fabpot what about this? It's also needed for symfony/swiftmailer-bundle#23
Should this be rescheduled for 2.3?

@fabpot
Copy link
Member

fabpot commented Mar 23, 2013

Closing in favor of #6124

@fabpot fabpot closed this Mar 23, 2013
fabpot pushed a commit to fabpot/symfony that referenced this pull request Mar 24, 2013
This adds an init and terminate event for commands. They are
dispatched from ContainerAwareCommand.

The cache:clear command can't implement this (cf. symfony#3889 on Github).
@fabpot fabpot mentioned this pull request Mar 24, 2013
fabpot added a commit that referenced this pull request Mar 25, 2013
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants