Skip to content

Commit

Permalink
Extract Application default listeners to dedicated listener provider
Browse files Browse the repository at this point in the history
Use double-dispatch approach to ensure default listeners are registered.

Signed-off-by: Aleksei Khudiakov <[email protected]>
  • Loading branch information
Xerkus committed Dec 10, 2023
1 parent 6b7cd1c commit 1a79dc8
Show file tree
Hide file tree
Showing 6 changed files with 401 additions and 74 deletions.
19 changes: 2 additions & 17 deletions src/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,6 @@ class Application implements EventsCapableInterface
public const ERROR_EXCEPTION = 'error-exception';
public const ERROR_ROUTER_NO_MATCH = 'error-router-no-match';

/**
* Default application event listeners
*
* @var array
*/
protected $defaultListeners = [
'RouteListener',
'DispatchListener',
'HttpMethodListener',
'ViewManager',
'SendResponseListener',
];

/**
* MVC event token
*
Expand All @@ -78,16 +65,14 @@ class Application implements EventsCapableInterface
public function __construct(
protected ServiceManager $serviceManager,
EventManagerInterface $events,
ApplicationListenersProvider $listenersProvider,
?RequestInterface $request = null,
?ResponseInterface $response = null
) {
$this->setEventManager($events);
$listenersProvider->registerListeners($this);
$this->request = $request ?: $serviceManager->get('Request');
$this->response = $response ?: $serviceManager->get('Response');

foreach ($this->defaultListeners as $listener) {
$serviceManager->get($listener)->attach($this->events);
}
}

/**
Expand Down
91 changes: 91 additions & 0 deletions src/ApplicationListenersProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php

declare(strict_types=1);

namespace Laminas\Mvc;

use Laminas\EventManager\ListenerAggregateInterface;
use Laminas\Mvc\Exception\DomainException;
use Psr\Container\ContainerInterface;

use function array_merge;
use function array_unique;
use function get_debug_type;
use function is_string;
use function sprintf;

use const SORT_REGULAR;

/**
* Provides lazy container
*/
final class ApplicationListenersProvider
{
public const DEFAULT_LISTENERS = [
'RouteListener',
'DispatchListener',
'HttpMethodListener',
'ViewManager',
'SendResponseListener',
];

/**
* @param array<string|ListenerAggregateInterface> $listeners
*/
private function __construct(private readonly ContainerInterface $container, private readonly array $listeners)
{
}

/**
* @param array<string|ListenerAggregateInterface> $extraListeners
*/
public static function withDefaultListeners(ContainerInterface $container, array $extraListeners): self
{
return new self(
$container,
array_unique(array_merge(self::DEFAULT_LISTENERS, $extraListeners), SORT_REGULAR)
);
}

/**
* @param array<string|ListenerAggregateInterface> $extraListeners
*/
public static function withoutDefaultListeners(ContainerInterface $container, array $extraListeners): self
{
return new self(
$container,
array_unique($extraListeners, SORT_REGULAR)
);
}

/**
* @return array<string|ListenerAggregateInterface>
*/
public function getListeners(): array
{
return $this->listeners;
}

public function registerListeners(Application $application): void
{
$events = $application->getEventManager();
foreach ($this->listeners as $listener) {
$msg = '';
if (is_string($listener)) {
$msg = sprintf(' with container id "%s"', $listener);
/** @var mixed $listener */
$listener = $this->container->get($listener);
}
if (! $listener instanceof ListenerAggregateInterface) {
throw new DomainException(sprintf(
'Application listener%s expected to be instance of %s, %s given',
$msg,
ListenerAggregateInterface::class,
get_debug_type($listener)
));
}

$listener->attach($events);
}
}
}
67 changes: 36 additions & 31 deletions src/ConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Laminas\Mvc\RouteListener;
use Laminas\Mvc\SendResponseListener;
use Laminas\Mvc\Service\ApplicationFactory;
use Laminas\Mvc\Service\ApplicationListenersProviderFactory;
use Laminas\Mvc\Service\ControllerManagerFactory;
use Laminas\Mvc\Service\ControllerPluginManagerFactory;
use Laminas\Mvc\Service\DispatchListenerFactory;
Expand Down Expand Up @@ -59,7 +60,10 @@ class ConfigProvider
public function __invoke(): array
{
return [
'dependencies' => $this->getDependencies(),
'dependencies' => $this->getDependencies(),
Application::class => [
'listeners' => [],
],
];
}

Expand Down Expand Up @@ -97,36 +101,37 @@ public function getDependencies(): array
ControllerManager::class => 'ControllerManager',
],
'factories' => [
'EventManager' => EventManagerFactory::class,
'SharedEventManager' => static fn() => new SharedEventManager(),
'Application' => ApplicationFactory::class,
'ControllerManager' => ControllerManagerFactory::class,
'ControllerPluginManager' => ControllerPluginManagerFactory::class,
'DispatchListener' => DispatchListenerFactory::class,
'HttpExceptionStrategy' => HttpExceptionStrategyFactory::class,
'HttpMethodListener' => HttpMethodListenerFactory::class,
'HttpRouteNotFoundStrategy' => HttpRouteNotFoundStrategyFactory::class,
'HttpViewManager' => HttpViewManagerFactory::class,
'InjectTemplateListener' => InjectTemplateListenerFactory::class,
'PaginatorPluginManager' => PaginatorPluginManagerFactory::class,
'Request' => RequestFactory::class,
'Response' => ResponseFactory::class,
'ViewHelperManager' => ViewHelperManagerFactory::class,
DefaultRenderingStrategy::class => HttpDefaultRenderingStrategyFactory::class,
'ViewFeedStrategy' => ViewFeedStrategyFactory::class,
'ViewJsonStrategy' => ViewJsonStrategyFactory::class,
'ViewManager' => ViewManagerFactory::class,
'ViewResolver' => ViewResolverFactory::class,
'ViewTemplateMapResolver' => ViewTemplateMapResolverFactory::class,
'ViewTemplatePathStack' => ViewTemplatePathStackFactory::class,
'ViewPrefixPathStackResolver' => ViewPrefixPathStackResolverFactory::class,
RouteListener::class => InvokableFactory::class,
SendResponseListener::class => SendResponseListenerFactory::class,
FeedRenderer::class => InvokableFactory::class,
JsonRenderer::class => InvokableFactory::class,
PhpRenderer::class => ViewPhpRendererFactory::class,
PhpRendererStrategy::class => ViewPhpRendererStrategyFactory::class,
View::class => ViewFactory::class,
'EventManager' => EventManagerFactory::class,
'SharedEventManager' => static fn() => new SharedEventManager(),
'Application' => ApplicationFactory::class,
'ControllerManager' => ControllerManagerFactory::class,
'ControllerPluginManager' => ControllerPluginManagerFactory::class,
'DispatchListener' => DispatchListenerFactory::class,
'HttpExceptionStrategy' => HttpExceptionStrategyFactory::class,
'HttpMethodListener' => HttpMethodListenerFactory::class,
'HttpRouteNotFoundStrategy' => HttpRouteNotFoundStrategyFactory::class,
'HttpViewManager' => HttpViewManagerFactory::class,
'InjectTemplateListener' => InjectTemplateListenerFactory::class,
'PaginatorPluginManager' => PaginatorPluginManagerFactory::class,
'Request' => RequestFactory::class,
'Response' => ResponseFactory::class,
'ViewHelperManager' => ViewHelperManagerFactory::class,
DefaultRenderingStrategy::class => HttpDefaultRenderingStrategyFactory::class,
'ViewFeedStrategy' => ViewFeedStrategyFactory::class,
'ViewJsonStrategy' => ViewJsonStrategyFactory::class,
'ViewManager' => ViewManagerFactory::class,
'ViewResolver' => ViewResolverFactory::class,
'ViewTemplateMapResolver' => ViewTemplateMapResolverFactory::class,
'ViewTemplatePathStack' => ViewTemplatePathStackFactory::class,
'ViewPrefixPathStackResolver' => ViewPrefixPathStackResolverFactory::class,
ApplicationListenersProvider::class => ApplicationListenersProviderFactory::class,
RouteListener::class => InvokableFactory::class,
SendResponseListener::class => SendResponseListenerFactory::class,
FeedRenderer::class => InvokableFactory::class,
JsonRenderer::class => InvokableFactory::class,
PhpRenderer::class => ViewPhpRendererFactory::class,
PhpRendererStrategy::class => ViewPhpRendererStrategyFactory::class,
View::class => ViewFactory::class,
],
'shared' => [
'EventManager' => false,
Expand Down
33 changes: 7 additions & 26 deletions src/Service/ApplicationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,21 @@

namespace Laminas\Mvc\Service;

use Laminas\EventManager\EventManagerInterface;
use Laminas\Mvc\Application;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Laminas\Mvc\ApplicationListenersProvider;
use Psr\Container\ContainerInterface;

class ApplicationFactory implements FactoryInterface
final class ApplicationFactory
{
/**
* Create the Application service
*
* Creates a Laminas\Mvc\Application service, passing it the configuration
* service and the service manager instance.
*
* @param string $name
* @param null|array $options
* @return Application
*/
public function __invoke(ContainerInterface $container, $name, ?array $options = null)
public function __invoke(ContainerInterface $container): Application
{
$application = new Application(
return new Application(
$container,
$container->get('EventManager'),
$container->get(EventManagerInterface::class),
$container->get(ApplicationListenersProvider::class),
$container->get('Request'),
$container->get('Response')
);

if (! $container->has('config')) {
return $application;
}

$em = $application->getEventManager();
$listeners = $container->get('config')[Application::class]['listeners'] ?? [];
foreach ($listeners as $listener) {
$container->get($listener)->attach($em);
}
return $application;
}
}
31 changes: 31 additions & 0 deletions src/Service/ApplicationListenersProviderFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Laminas\Mvc\Service;

use Laminas\Mvc\Application;
use Laminas\Mvc\ApplicationListenersProvider;
use Psr\Container\ContainerInterface;

use function assert;
use function is_array;

final class ApplicationListenersProviderFactory
{
/**
* For default listeners @see ApplicationListenersProvider::DEFAULT_LISTENERS
*
* Extra listeners could be specified via configuration at `$config[Application::class]['listeners']`
* or overridden via delegator factory.
*/
public function __invoke(ContainerInterface $container): ApplicationListenersProvider
{
$config = $container->get('config');
assert(is_array($config));

/** @psalm-var list<string> $listeners */
$listeners = $config[Application::class]['listeners'] ?? [];
return ApplicationListenersProvider::withDefaultListeners($container, $listeners);
}
}
Loading

0 comments on commit 1a79dc8

Please sign in to comment.