0

Note: I refer to the Symfony Console component quite a lot in my question, but I think this question could be considered broader if thought about in the context of any user interface.

I am using the Symfony Console component to create a console application. I am trying to keep class coupling to a minimum, as it makes unit testing easier and is generally better practice.

My app has some processes which may take some time to complete, so I want to keep the user informed as to what is happening using progress bars and general text output as it runs. Symfony requires an instance of the Symfony Console OutputInterface to be passed to any command's execute method. So far so good; I can create progress bars and output text as I please. However, all of the heavy lifting of my app doesn't happen in the commands' execute methods and is instead within the core classes of my application. These classes shouldn't and don't know they are being used in a console application.

I am struggling to keep it this way because I don't know how to provide feedback to the console (or whatever user interface) without injecting the output class into my core. Doing so would result in tight coupling between the console output class and my application core. I have thought about using an event dispatcher (Symfony has one), but that too means my core will be coupled with the dispatcher (maybe that's fine). Ideally, I need to sort of "bubble" my application state back up to the execute method of the invoked command, where I can then perform output.

Could someone point me in the right direction, please? I feel like this must actually be quite a common case but can't find much about it.

Thanks for your time in advance!

Ben Guest
  • 1,488
  • 2
  • 17
  • 29

1 Answers1

3

I have succesfully used the event dispatcher approach before. You can trigger events at the start, progress, and end of processing for example, and have an event listener update the progress bar based on that.

<?php

$progress = $this->getHelperSet()->get('progress');
$dispatcher = $this->getContainer()->get('event_dispatcher');

$dispatcher->addListener('my_start_event', function (GenericEvent $event) use ($progress, $output) {
    $progress->start($output, $event->getArgument('total'));
});

$dispatcher->addListener('my_progress_event', function () use ($progress) {
    $progress->advance();
});

$dispatcher->addListener('my_finish_event', function () use ($progress) {
    $progress->finish();
});

If you really want to avoid coupling of the event dispatcher in your service, you could extend or decorate your class (probably implementing a shared interface), and only use the event dispatcher there. You would need an extension point (public or protected method) in the base class however to be able to notify of any progress.

Gerry
  • 6,012
  • 21
  • 33
  • This does sound like the simplest solution. I guess at least then my app core will have no knowledge of the user interface and will only be additionally concerned with firing off progress events. Ah yes I see what you mean about extending my service (like EventAwareService or something) and having a protected method in the original service. – Ben Guest Oct 18 '16 at 13:57