Skip to content

Commit

Permalink
Merge 17f326f into 21d36bf
Browse files Browse the repository at this point in the history
  • Loading branch information
weierophinney committed Jan 20, 2021
2 parents 21d36bf + 17f326f commit b8d2f37
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 8 deletions.
85 changes: 85 additions & 0 deletions docs/book/events.md
@@ -0,0 +1,85 @@
# Events

The [symfony/console component](https://symfony.com/doc/current/components/console.html) allows attaching an event dispatcher instance to a console application.
During the lifetime of a console command, the application will trigger a number of events, to which you may subscribe listeners.
Internally, laminas/laminas-cli itself adds a listener on the `Symfony\Component\Console\ConsoleEvents::TERMINATE` event in order to provide [command chains](command-chains.md).

If you wish to subscribe to any of the various symfony/console events, you will need to provide an alternate event dispatcher instance.
You may do so by defining a `Laminas\Cli\SymfonyEventDispatcher` service in your container that resolves to a `Symfony\Component\EventDispatcher\EventDispatcherInterface` instance. (We use this instead of the more generic `Symfony\Contracts\EventDispatcher\EventDispatcherInterface` so that we can use its `addListener()` method to subscribe our own listener.)

As an example, let's say you want to register the `Symfony\Component\Console\EventListener\ErrorListener` in your console application for purposes of debugging.
First, we will create a factory for this listener in the file `src/App/ConsoleErrorListenerFactory.php`:

```php
<?php

declare(strict_types=1);

namespace App;

use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventListener\ErrorListener;

final class ConsoleErrorListenerFactory
{
public function __invoke(ContainerInterface $container): ErrorListener
{
return new ErrorListener($container->get(LoggerInterface::class));
}
}
```

> The above example assumes you have already wired the `Psr\Log\LoggerInterface` service in your container configuration.
Next, we will create the class `App\ConsoleEventDispatcherFactory` in the file `src/App/ConsoleEventDispatcherFactory.php`.
The factory will create an `EventDispatcher` instance, attach the error listener, and return the dispatcher.

```php
<?php

declare(strict_types=1);

namespace App;

use Psr\Container\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventListener\ErrorListener;

final class ConsoleEventDispatcherFactory
{
public function __invoke(ContainerInterface $container): EventDispatcher
{
$dispatcher = new EventDispatcher();
$dispatcher->addListener($container->get(ErrorListener::class));

return $dispatcher;
}
}
```

Finally, we need to wire both our `ErrorListener` and our `EventDispatcher` services in our container.
We can do so by creating a configuration file named `config/autoload/console.global.php` if it does not already exist, and adding the following contents:

```php
return [
'{CONTAINER_KEY}' => [
'factories' => [
'Laminas\Cli\SymfonyEventDispatcher' => \App\ConsoleEventDispatcherFactory::class,
\Symfony\Component\EventDispatcher\EventListener\ErrorListener::class => \App\ConsoleErrorListenerFactory::class,
// ...
],
// ...
],
// ...
];
```

> For the value of `{CONTAINER_KEY}`, substitute the following:
>
> - For laminas-mvc applications, use the value "service_manager".
> - For Mezzio applications, use the value "dependencies".
Later, if you want to register other listeners, you can either update your `App\ConsoleEventDispatcherFactory`, or you can add [delegator factories](https://docs.laminas.dev/laminas-servicemanager/delegators/) on the "Laminas\Cli\SymfonyEventDispatcher" service.

Read the [symfony/console events documentation](https://symfony.com/doc/current/components/console/events.html) more information on how to add listeners to the event dispatcher.
1 change: 1 addition & 0 deletions mkdocs.yml
Expand Up @@ -6,6 +6,7 @@ nav:
- Command Chains: command-chains.md
- Command Params: command-params.md
- Autocompletion: autocompletion.md
- Events: events.md
site_name: laminas-cli
site_description: 'Command-line interface for Laminas projects'
repo_url: 'https://github.com/laminas/laminas-cli'
Expand Down
8 changes: 7 additions & 1 deletion src/ApplicationFactory.php
Expand Up @@ -16,6 +16,7 @@
use Symfony\Component\Console\Application;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Webmozart\Assert\Assert;

use function strstr;
Expand All @@ -40,7 +41,12 @@ public function __invoke(ContainerInterface $container): Application
Assert::isMap($commands);
Assert::allString($commands);

$dispatcher = new EventDispatcher();
$eventDispatcherServiceName = __NAMESPACE__ . '\SymfonyEventDispatcher';
$dispatcher = $container->has($eventDispatcherServiceName)
? $container->get($eventDispatcherServiceName)
: new EventDispatcher();
Assert::isInstanceOf($dispatcher, EventDispatcherInterface::class);

$dispatcher->addListener(ConsoleEvents::TERMINATE, new TerminateListener($config));

$application = new Application('laminas', $version);
Expand Down
60 changes: 60 additions & 0 deletions test/ApplicationFactoryTest.php
@@ -0,0 +1,60 @@
<?php

/**
* @see https://github.com/laminas/laminas-cli for the canonical source repository
* @copyright https://github.com/laminas/laminas-cli/blob/master/COPYRIGHT.md
* @license https://github.com/laminas/laminas-cli/blob/master/LICENSE.md New BSD License
*/

declare(strict_types=1);

namespace LaminasTest\Cli;

use Laminas\Cli\ApplicationFactory;
use Laminas\Cli\Listener\TerminateListener;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/** @psalm-suppress PropertyNotSetInConstructor */
class ApplicationFactoryTest extends TestCase
{
public function testPullsEventDispatcherFromContainerWhenPresent(): void
{
$config = [
'laminas-cli' => [],
];

$dispatcher = $this->createMock(EventDispatcherInterface::class);
$dispatcher
->expects($this->once())
->method('addListener')
->with(
ConsoleEvents::TERMINATE,
$this->isInstanceOf(TerminateListener::class)
);

$container = $this->createMock(ContainerInterface::class);
$container
->expects($this->once())
->method('has')
->with('Laminas\Cli\SymfonyEventDispatcher')
->willReturn(true);
$container
->expects($this->exactly(2))
->method('get')
->withConsecutive(
['config'],
['Laminas\Cli\SymfonyEventDispatcher']
)
->willReturnOnConsecutiveCalls(
$config,
$dispatcher
);

/** @psalm-suppress InternalClass */
$this->assertInstanceOf(Application::class, (new ApplicationFactory())($container));
}
}
24 changes: 17 additions & 7 deletions test/ApplicationTest.php
Expand Up @@ -475,7 +475,13 @@ public function testParamInput(): void
public function testParamInputNonInteractiveMissingParameter(): void
{
$container = $this->createMock(ContainerInterface::class);
$container->method('has')->with(ParamCommand::class)->willReturn(true);
$container
->expects($this->atLeast(2))
->method('has')
->willReturnMap([
['Laminas\Cli\SymfonyEventDispatcher', false],
[ParamCommand::class, true],
]);
$container->method('get')->willReturnMap([
[
'config',
Expand Down Expand Up @@ -528,10 +534,12 @@ public function testListIncludesCommandWithDependencies(): void
/** @psalm-var ContainerInterface&\PHPUnit\Framework\MockObject\MockObject $container */
$container = $this->createMock(ContainerInterface::class);
$container
->expects($this->atLeastOnce())
->expects($this->atLeast(2))
->method('has')
->with($this->equalTo(ExampleCommandWithDependencies::class))
->willReturn(true);
->willReturnMap([
['Laminas\Cli\SymfonyEventDispatcher', false],
[ExampleCommandWithDependencies::class, true],
]);

$container
->method('get')
Expand Down Expand Up @@ -587,10 +595,12 @@ public function testHelpDisplaysInformationForCommandWithDependencies(): void
$container = $this->createMock(ContainerInterface::class);

$container
->expects($this->atLeastOnce())
->expects($this->atLeast(2))
->method('has')
->with($this->equalTo(ExampleCommandWithDependencies::class))
->willReturn(true);
->willReturnMap([
['Laminas\Cli\SymfonyEventDispatcher', false],
[ExampleCommandWithDependencies::class, true],
]);

$container
->method('get')
Expand Down

0 comments on commit b8d2f37

Please sign in to comment.