joomla-framework / console

Joomla Framework Console Package
GNU General Public License v2.0
9 stars 4 forks source link

Simplify the integration by extending the Symfony Application #3

Closed mbabker closed 5 years ago

mbabker commented 5 years ago

When first implemented, a lot of logic had to be recreated from the symfony/console package because it expects the application to be a Symfony\Component\Console\Application object and we couldn't do that because the expectation in the Joomla ecosystem is all applications are a subclass of Joomla\Application\AbstractApplication, and PHP being what it is you can't have two parent classes. Thanks to https://github.com/joomla-framework/application/pull/83 though, we can use an interface for Joomla applications and therefore have our application subclass both extend the Symfony application and declare itself a Joomla application by implementing the interface. Now, all the extra code needed (which is legitimately 99% of this package) because we couldn't subclass the Symfony application is not needed, and we're refactored to the minimum implementation needed.

Two other challenges present themselves in this approach, one of which has been addressed and the other needing an upstream solution in joomla/application.

First, the Symfony application typehints Symfony\Component\EventDispatcher\EventDispatcherInterface for the event dispatcher and uses Symfony\Component\EventDispatcher\Event as the root for its event classes. This is not compatible with Joomla\Event\DispatcherInterface and Joomla\Event\EventInterface. To address this issue, a new event dispatcher bridge package is created which allows the Symfony dispatcher to dispatch events to the Joomla API and vice-versa (maybe one day PSR-14 makes this a little easier to pull off).

Second, there is a naming conflict between Symfony\Component\Console\Application::get($name) and Joomla\Application\ConfigurationAwareApplicationInterface::get($name, $default). The Symfony method is used to get a Command class object while the Joomla method is used to read a value from the injected configuration Registry. That one isn't addressed yet and before a tag on the joomla/application repository can be made with the interfaces we need to figure something out here, so for now our console application can't be made configuration aware.

As far as using the event dispatcher bridge goes, see the below example. The rest is left to the bridge's internals.

require __DIR__ . '/vendor/autoload.php';

$decoratedDispatcher = new \Joomla\Event\Dispatcher;
$dispatcher = new \Joomla\SymfonyEventDispatcherBridge\Joomla\Dispatcher($decoratedDispatcher);

$dispatcher->addListener(\Symfony\Component\Console\ConsoleEvents::COMMAND, function (\Symfony\Component\EventDispatcher\Event $event) {
    /*
     * $event is an instance of `Joomla\SymfonyEventDispatcherBridge\Symfony\Event` decorating `Symfony\Component\Console\Event\ConsoleCommandEvent`
     */

    /** @var \Symfony\Component\Console\Output\OutputInterface $output */
    $output = $event->getOutput();

    $output->writeln('<comment>Before command execute event triggered.</comment>');
});
$dispatcher->addListener(\Symfony\Component\Console\ConsoleEvents::ERROR, function (\Symfony\Component\EventDispatcher\Event $event) {
    /*
     * $event is an instance of `Joomla\SymfonyEventDispatcherBridge\Symfony\Event` decorating `Symfony\Component\Console\Event\ConsoleErrorEvent`
     */

    /** @var \Symfony\Component\Console\Output\OutputInterface $output */
    $output = $event->getOutput();

    $event->getOutput()->writeln('<comment>Error event triggered.</comment>');
});
$dispatcher->addListener(\Symfony\Component\Console\ConsoleEvents::TERMINATE, function (\Symfony\Component\EventDispatcher\Event $event) {
    /*
     * $event is an instance of `Joomla\SymfonyEventDispatcherBridge\Symfony\Event` decorating `Symfony\Component\Console\Event\ConsoleTerminateEvent`
     */

    /** @var \Symfony\Component\Console\Output\OutputInterface $output */
    $output = $event->getOutput();

    $event->getOutput()->writeln('<comment>Terminate event triggered.</comment>');
});

$app = new \Joomla\Console\Application;
$app->setName('Console Tester');
$app->setDispatcher($dispatcher);

/*
 * Undocumented in the method signature, but this method call supports the same arguments as `Symfony\Component\Console\Application::run()`
 */
$app->execute();
mbabker commented 5 years ago

// @wilsonge