diff --git a/psalm-baseline.xml b/psalm-baseline.xml index aecacea9..d34cb61d 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,19 +1,13 @@ - + - - Application - $r - get('EventManager')]]> - $listener get('Router')]]> - $listener $response $response request]]> @@ -22,9 +16,6 @@ array|object - - attach - serviceManager->get('config')]]> @@ -176,9 +167,6 @@ getContent()]]> getMethod()]]> - - customHttpMethodsMap[$method]]]> - $action $action @@ -331,7 +319,6 @@ $request - $modelAcceptString $modelName $modelString @@ -343,7 +330,6 @@ getMatchedAgainst()->params[self::INJECT_VIEWMODEL_NAME]]]> - $modelAcceptString $modelName $modelString $modelStrings @@ -351,9 +337,6 @@ new $name() - - $modelAcceptString - ModelInterface|null @@ -622,11 +605,6 @@ - - $application - $application - $application - reportMonitorEvent @@ -660,11 +638,6 @@ dispatch - - $application::ERROR_CONTROLLER_INVALID - $application::ERROR_CONTROLLER_NOT_FOUND - $application::ERROR_EXCEPTION - $container - get('EventManager')]]> get('Request')]]> get('Response')]]> - $listener - - $listener - $listeners - - - attach - @@ -1296,9 +1260,6 @@ attach - - $services - @@ -1309,9 +1270,6 @@ - - $serviceConfig - $services $services @@ -1429,16 +1387,10 @@ - - $serviceConfig - $services $services - - $services->get('HttpRouter')]]> - $services @@ -1451,20 +1403,10 @@ bootstrap - get setUri - - $serviceConfig - - - $services - - - $services->get('HttpRouter')]]> - $application $request @@ -1474,21 +1416,15 @@ bootstrap - get setUri - - $serviceConfig - $services $services - - $services->get('HttpRouter')]]> - + $services @@ -1501,7 +1437,6 @@ bootstrap - get setUri @@ -1509,25 +1444,13 @@ $e - - $log - - - - - - - getRouteMatch - $eventManager - $eventManager $events - $serviceConfig setMethods @@ -1569,7 +1492,6 @@ static function (MvcEvent $event) { - $defaultListenerName getContent()]]> getContent()]]> $router @@ -1580,17 +1502,10 @@ $controllerLoader $controllerManager - $defaultListener - $defaultListenerName - $defaultListenersNames - $defaultListeners[] $error $error $events - $listener - $listener $listenerService - $registeredListeners[] $request $request $request @@ -2471,16 +2386,6 @@ $e $e - - $log - - - - - - - - @@ -2517,10 +2422,6 @@ $e - - - - @@ -2819,9 +2720,6 @@ testDetachesListeners - - $child - diff --git a/src/Application.php b/src/Application.php index b9e11fc2..c3777fe8 100644 --- a/src/Application.php +++ b/src/Application.php @@ -4,44 +4,32 @@ namespace Laminas\Mvc; -use Laminas\EventManager\EventManagerAwareInterface; +use Closure; use Laminas\EventManager\EventManagerInterface; -use Laminas\ServiceManager\ServiceManager; +use Laminas\EventManager\EventsCapableInterface; +use Laminas\Http\Request; +use Laminas\Http\Response; use Laminas\Stdlib\RequestInterface; use Laminas\Stdlib\ResponseInterface; +use Psr\Container\ContainerInterface; /** * Main application class for invoking applications * - * Expects the user will provide a configured ServiceManager, configured with - * the following services: - * - * - EventManager - * - ModuleManager - * - Request - * - Response - * - RouteListener - * - Router - * - DispatchListener - * - ViewManager - * * The most common workflow is: * - * $services = new Laminas\ServiceManager\ServiceManager($servicesConfig); - * $app = new Application($appConfig, $services); + * $container = new Laminas\ServiceManager\ServiceManager($dependencies); + * $app = $container->get('Application'); * $app->bootstrap(); - * $response = $app->run(); - * $response->send(); + * $app->run(); * * - * bootstrap() opts in to the default route, dispatch, and view listeners, - * sets up the MvcEvent, and triggers the bootstrap event. This can be omitted - * if you wish to setup your own listeners and/or workflow; alternately, you - * can simply extend the class to override such behavior. + * bootstrap() sets up the MvcEvent and triggers the bootstrap event. Bootstrap event could be used to configure + * application. Most of the application and its dependencies preparation is expected to be handled by dependency + * injection container. Request specific configuration can be performed during prepare event triggered early in the + * run(). */ -class Application implements - ApplicationInterface, - EventManagerAwareInterface +class Application implements EventsCapableInterface { public const ERROR_CONTROLLER_CANNOT_DISPATCH = 'error-controller-cannot-dispatch'; public const ERROR_CONTROLLER_NOT_FOUND = 'error-controller-not-found'; @@ -49,82 +37,48 @@ class Application implements 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 - * - * @var MvcEvent */ - protected $event; - - /** @var EventManagerInterface */ - protected $events; - - /** @var RequestInterface */ - protected $request; + protected MvcEvent $event; - /** @var ResponseInterface */ - protected $response; - - public function __construct( - protected ServiceManager $serviceManager, - ?EventManagerInterface $events = null, - ?RequestInterface $request = null, - ?ResponseInterface $response = null - ) { - $this->setEventManager($events ?: $serviceManager->get('EventManager')); - $this->request = $request ?: $serviceManager->get('Request'); - $this->response = $response ?: $serviceManager->get('Response'); - - foreach ($this->defaultListeners as $listener) { - $serviceManager->get($listener)->attach($this->events); - } - } + protected EventManagerInterface $events; /** - * Retrieve the application configuration - * - * @return array|object + * @param Closure():Request $requestFactory + * @param Closure():Response $responseFactory */ - public function getConfig() - { - return $this->serviceManager->get('config'); + public function __construct( + protected ContainerInterface $container, + EventManagerInterface $events, + ApplicationListenersProvider $listenersProvider, + protected Closure $requestFactory, + protected Closure $responseFactory, + ) { + $this->setEventManager($events); + $listenersProvider->registerListeners($this); + $this->event = new MvcEvent(); + $this->event->setApplication($this); } /** * Bootstrap the application * - * Defines and binds the MvcEvent, and passes it the request, response, and - * router. Attaches the ViewManager as a listener. Triggers the bootstrap - * event. + * Defines and binds the MvcEvent, and passes it the router. Triggers the bootstrap event. * * @return Application */ public function bootstrap() { - $serviceManager = $this->serviceManager; - $events = $this->events; + $container = $this->container; + $events = $this->events; // Setup MVC Event - $this->event = $event = new MvcEvent(); + $event = $this->event; $event->setName(MvcEvent::EVENT_BOOTSTRAP); $event->setTarget($this); $event->setApplication($this); - $event->setRequest($this->request); - $event->setResponse($this->response); - $event->setRouter($serviceManager->get('Router')); + $event->setRouter($container->get('Router')); // Trigger bootstrap events $events->triggerEvent($event); @@ -135,66 +89,58 @@ public function bootstrap() /** * Retrieve the service manager * - * @return ServiceManager + * @deprecated Since 4.0.0 and will be removed in 5.0.0 */ - public function getServiceManager() + public function getServiceManager(): ContainerInterface { - return $this->serviceManager; + return $this->container; } /** * Get the request object * - * @return RequestInterface + * @deprecated Since 4.0.0 and will be removed in 5.0.0 + * + * @return null|RequestInterface */ public function getRequest() { - return $this->request; + return $this->event->getRequest(); } /** * Get the response object * - * @return ResponseInterface + * @deprecated Since 4.0.0 and will be removed in 5.0.0 + * + * @return null|ResponseInterface */ public function getResponse() { - return $this->response; + return $this->event->getResponse(); } /** * Get the MVC event instance - * - * @return MvcEvent */ - public function getMvcEvent() + public function getMvcEvent(): MvcEvent { return $this->event; } /** * Set the event manager instance - * - * @return Application */ - public function setEventManager(EventManagerInterface $eventManager) + protected function setEventManager(EventManagerInterface $eventManager): void { $eventManager->setIdentifiers([ self::class, static::class, ]); $this->events = $eventManager; - return $this; } - /** - * Retrieve the event manager - * - * Lazy-loads an EventManager instance if none registered. - * - * @return EventManagerInterface - */ - public function getEventManager() + public function getEventManager(): EventManagerInterface { return $this->events; } @@ -202,6 +148,8 @@ public function getEventManager() /** * Run the application * + * @triggers prepare(MvcEvent) + * Allows last minute request dependent preparation logic. * @triggers route(MvcEvent) * Routes the request, and sets the RouteMatch object in the event. * @triggers dispatch(MvcEvent) @@ -213,6 +161,8 @@ public function getEventManager() * discovered controller, and controller class (if known). * Typically, a handler should return a populated Response object * that can be returned immediately. + * @triggers finish(MvcEvent) + * Event for post-request cleanup. Responsible for emitting response via SendResponseListener. * @return self */ public function run() @@ -220,7 +170,16 @@ public function run() $events = $this->events; $event = $this->event; - // Define callback used to determine whether or not to short-circuit + $event->setRequest(($this->requestFactory)()); + $event->setResponse(($this->responseFactory)()); + + // Trigger prepare event + $event->setName(MvcEvent::EVENT_PREPARE); + $event->stopPropagation(false); + $event->setTarget($this); + $events->triggerEvent($event); + + // Define callback used to determine whether to short-circuit $shortCircuit = static function ($r) use ($event): bool { if ($r instanceof ResponseInterface) { return true; @@ -243,7 +202,6 @@ public function run() $event->setResponse($response); $event->stopPropagation(false); // Clear before triggering $events->triggerEvent($event); - $this->response = $response; return $this; } } @@ -265,12 +223,9 @@ public function run() $event->setResponse($response); $event->stopPropagation(false); // Clear before triggering $events->triggerEvent($event); - $this->response = $response; return $this; } - $response = $this->response; - $event->setResponse($response); return $this->completeRequest($event); } diff --git a/src/ApplicationInterface.php b/src/ApplicationInterface.php deleted file mode 100644 index 0210434f..00000000 --- a/src/ApplicationInterface.php +++ /dev/null @@ -1,41 +0,0 @@ - $listeners + */ + private function __construct(private readonly ContainerInterface $container, private readonly array $listeners) + { + } + + /** + * @param array $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 $extraListeners + */ + public static function withoutDefaultListeners(ContainerInterface $container, array $extraListeners): self + { + return new self( + $container, + array_unique($extraListeners, SORT_REGULAR) + ); + } + + /** + * @return array + */ + 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); + } + } +} diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 3f4164fa..05e55fb5 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -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; @@ -59,7 +60,10 @@ class ConfigProvider public function __invoke(): array { return [ - 'dependencies' => $this->getDependencies(), + 'dependencies' => $this->getDependencies(), + Application::class => [ + 'listeners' => [], + ], ]; } @@ -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, diff --git a/src/DispatchListener.php b/src/DispatchListener.php index cd1ea800..ef41bb3c 100644 --- a/src/DispatchListener.php +++ b/src/DispatchListener.php @@ -110,7 +110,7 @@ public function onDispatch(MvcEvent $e) } $request = $e->getRequest(); - $response = $application->getResponse(); + $response = $e->getResponse(); try { $return = $controller->dispatch($request, $response); diff --git a/src/MvcEvent.php b/src/MvcEvent.php index e4fdd916..4cb4288d 100644 --- a/src/MvcEvent.php +++ b/src/MvcEvent.php @@ -18,20 +18,21 @@ class MvcEvent extends Event * Mvc events triggered by eventmanager */ public const EVENT_BOOTSTRAP = 'bootstrap'; + public const EVENT_PREPARE = 'prepare'; + public const EVENT_ROUTE = 'route'; public const EVENT_DISPATCH = 'dispatch'; public const EVENT_DISPATCH_ERROR = 'dispatch.error'; - public const EVENT_FINISH = 'finish'; public const EVENT_RENDER = 'render'; public const EVENT_RENDER_ERROR = 'render.error'; - public const EVENT_ROUTE = 'route'; + public const EVENT_FINISH = 'finish'; - /** @var ApplicationInterface */ + /** @var Application */ protected $application; - /** @var Request */ + /** @var null|Request */ protected $request; - /** @var Response */ + /** @var null|Response */ protected $response; /** @var mixed */ @@ -51,7 +52,7 @@ class MvcEvent extends Event * * @return MvcEvent */ - public function setApplication(ApplicationInterface $application) + public function setApplication(Application $application) { $this->setParam('application', $application); $this->application = $application; @@ -61,7 +62,7 @@ public function setApplication(ApplicationInterface $application) /** * Get application instance * - * @return ApplicationInterface + * @return Application */ public function getApplication() { @@ -115,7 +116,8 @@ public function setRouteMatch(RouteMatch $matches) /** * Get request * - * @return Request + * @psalm-ignore-nullable-return Has actual request object most of the time except for bootstrap event + * @return null|Request */ public function getRequest() { @@ -137,7 +139,8 @@ public function setRequest(Request $request) /** * Get response * - * @return Response + * @psalm-ignore-nullable-return Has actual response object most of the time except for bootstrap event + * @return null|Response */ public function getResponse() { diff --git a/src/Service/ApplicationFactory.php b/src/Service/ApplicationFactory.php index 879b141b..4f1cac68 100644 --- a/src/Service/ApplicationFactory.php +++ b/src/Service/ApplicationFactory.php @@ -4,40 +4,23 @@ namespace Laminas\Mvc\Service; +use Laminas\EventManager\EventManagerInterface; +use Laminas\Http\Request; +use Laminas\Http\Response; 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('Request'), - $container->get('Response') + $container->get(EventManagerInterface::class), + $container->get(ApplicationListenersProvider::class), + static fn (): Request => $container->get('Request'), + static fn (): Response => $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; } } diff --git a/src/Service/ApplicationListenersProviderFactory.php b/src/Service/ApplicationListenersProviderFactory.php new file mode 100644 index 00000000..75a8f075 --- /dev/null +++ b/src/Service/ApplicationListenersProviderFactory.php @@ -0,0 +1,31 @@ +get('config'); + assert(is_array($config)); + + /** @psalm-var list $listeners */ + $listeners = $config[Application::class]['listeners'] ?? []; + return ApplicationListenersProvider::withDefaultListeners($container, $listeners); + } +} diff --git a/src/Service/HttpViewManagerFactory.php b/src/Service/HttpViewManagerFactory.php index 84a19d18..cbbe7363 100644 --- a/src/Service/HttpViewManagerFactory.php +++ b/src/Service/HttpViewManagerFactory.php @@ -19,6 +19,6 @@ class HttpViewManagerFactory implements FactoryInterface */ public function __invoke(ContainerInterface $container, $name, ?array $options = null) { - return new HttpViewManager(); + return new HttpViewManager($container); } } diff --git a/src/Service/ViewResolverFactory.php b/src/Service/ViewResolverFactory.php index af023b9e..90e0da95 100644 --- a/src/Service/ViewResolverFactory.php +++ b/src/Service/ViewResolverFactory.php @@ -24,11 +24,11 @@ public function __invoke(ContainerInterface $container, $name, ?array $options = { $resolver = new ViewResolver\AggregateResolver(); - /** @var ResolverInterface $mapResolver */ + /** @var ViewResolver\ResolverInterface $mapResolver */ $mapResolver = $container->get('ViewTemplateMapResolver'); - /** @var ResolverInterface $pathResolver */ + /** @var ViewResolver\ResolverInterface $pathResolver */ $pathResolver = $container->get('ViewTemplatePathStack'); - /** @var ResolverInterface $prefixPathStackResolver */ + /** @var ViewResolver\ResolverInterface $prefixPathStackResolver */ $prefixPathStackResolver = $container->get('ViewPrefixPathStackResolver'); $resolver diff --git a/src/View/Http/ViewManager.php b/src/View/Http/ViewManager.php index e1c0d6a6..15df1839 100644 --- a/src/View/Http/ViewManager.php +++ b/src/View/Http/ViewManager.php @@ -10,10 +10,10 @@ use Laminas\EventManager\ListenerAggregateInterface; use Laminas\Mvc\MvcEvent; use Laminas\Mvc\View\Http\InjectTemplateListener; -use Laminas\ServiceManager\ServiceManager; use Laminas\Stdlib\DispatchableInterface; use Laminas\View\Model\ModelInterface; use Laminas\View\View; +use Psr\Container\ContainerInterface; use Traversable; use function is_array; @@ -49,13 +49,18 @@ class ViewManager extends AbstractListenerAggregate /** @var MvcEvent */ protected $event; - /** @var ServiceManager */ - protected $services; + protected ContainerInterface $services; protected ?View $view = null; protected ?ModelInterface $viewModel = null; + public function __construct( + ContainerInterface $container + ) { + $this->services = $container; + } + /** * {@inheritDoc} */ @@ -70,26 +75,24 @@ public function attach(EventManagerInterface $events, $priority = 1) public function onBootstrap(MvcEvent $event): void { $application = $event->getApplication(); - $services = $application->getServiceManager(); - $config = $services->get('config'); + $config = $this->services->get('config'); $events = $application->getEventManager(); $sharedEvents = $events->getSharedManager(); - $this->config = isset($config['view_manager']) + $this->config = isset($config['view_manager']) && (is_array($config['view_manager']) || $config['view_manager'] instanceof ArrayAccess) ? $config['view_manager'] : []; - $this->services = $services; - $this->event = $event; + $this->event = $event; - $routeNotFoundStrategy = $services->get('HttpRouteNotFoundStrategy'); - $exceptionStrategy = $services->get('HttpExceptionStrategy'); - $mvcRenderingStrategy = $services->get('HttpDefaultRenderingStrategy'); + $routeNotFoundStrategy = $this->services->get('HttpRouteNotFoundStrategy'); + $exceptionStrategy = $this->services->get('HttpExceptionStrategy'); + $mvcRenderingStrategy = $this->services->get('HttpDefaultRenderingStrategy'); $this->injectViewModelIntoPlugin(); - $injectTemplateListener = $services->get(InjectTemplateListener::class); + $injectTemplateListener = $this->services->get(InjectTemplateListener::class); $createViewModelListener = new CreateViewModelListener(); $injectViewModelListener = new InjectViewModelListener(); diff --git a/test/Application/AllowsReturningEarlyFromRoutingTest.php b/test/Application/AllowsReturningEarlyFromRoutingTest.php index a56b3f86..f6215b16 100644 --- a/test/Application/AllowsReturningEarlyFromRoutingTest.php +++ b/test/Application/AllowsReturningEarlyFromRoutingTest.php @@ -20,8 +20,8 @@ public function testAllowsReturningEarlyFromRouting(): void $application->getEventManager()->attach(MvcEvent::EVENT_ROUTE, static fn($e): Response => $response); - $result = $application->run(); - $this->assertSame($application, $result); - $this->assertSame($response, $result->getResponse()); + $application->run(); + + $this->assertSame($response, $application->getMvcEvent()->getResponse()); } } diff --git a/test/Application/BadControllerTrait.php b/test/Application/BadControllerTrait.php index b80e4059..6f28de13 100644 --- a/test/Application/BadControllerTrait.php +++ b/test/Application/BadControllerTrait.php @@ -7,8 +7,9 @@ use Laminas\Http\PhpEnvironment\Request; use Laminas\Http\PhpEnvironment\Response; use Laminas\Mvc\Application; +use Laminas\Mvc\ConfigProvider; use Laminas\Mvc\Controller\ControllerManager; -use Laminas\Router\ConfigProvider; +use Laminas\Router; use Laminas\Router\Http\Literal; use Laminas\ServiceManager\ServiceManager; use Laminas\Stdlib\ArrayUtils; @@ -21,8 +22,8 @@ trait BadControllerTrait { public function prepareApplication(): Application { - $config = [ - 'router' => [ + $testConfig = [ + 'router' => [ 'routes' => [ 'path' => [ 'type' => Literal::class, @@ -36,14 +37,7 @@ public function prepareApplication(): Application ], ], ], - ]; - - $serviceConfig = ArrayUtils::merge( - ArrayUtils::merge( - (new \Laminas\Mvc\ConfigProvider())->getDependencies(), - (new ConfigProvider())->getDependencyConfig(), - ), - [ + 'dependencies' => [ 'aliases' => [ 'ControllerLoader' => ControllerManager::class, ], @@ -62,13 +56,19 @@ public function prepareApplication(): Application 'SendResponseListener' => MockSendResponseListener::class, 'BootstrapListener' => StubBootstrapListener::class, ], - 'services' => [ - 'config' => $config, - ], - ] + ], + ]; + + $config = ArrayUtils::merge( + ArrayUtils::merge( + (new ConfigProvider())(), + (new Router\ConfigProvider())(), + ), + $testConfig ); - $services = new ServiceManager($serviceConfig); - $application = $services->get('Application'); + $config['dependencies']['services']['config'] = $config; + $services = new ServiceManager($config['dependencies']); + $application = $services->get('Application'); $request = $services->get('Request'); $request->setUri('http://example.local/bad'); diff --git a/test/Application/ControllerIsDispatchedTest.php b/test/Application/ControllerIsDispatchedTest.php index dd340638..bdad606c 100644 --- a/test/Application/ControllerIsDispatchedTest.php +++ b/test/Application/ControllerIsDispatchedTest.php @@ -15,7 +15,9 @@ public function testControllerIsDispatchedDuringRun(): void { $application = $this->prepareApplication(); - $response = $application->run()->getResponse(); + $application->run(); + + $response = $application->getMvcEvent()->getResponse(); $this->assertStringContainsString('PathController', $response->getContent()); $this->assertStringContainsString(MvcEvent::EVENT_DISPATCH, $response->toString()); } diff --git a/test/Application/ExceptionsRaisedInDispatchableShouldRaiseDispatchErrorEventTest.php b/test/Application/ExceptionsRaisedInDispatchableShouldRaiseDispatchErrorEventTest.php index d2a498b4..0b0fe3df 100644 --- a/test/Application/ExceptionsRaisedInDispatchableShouldRaiseDispatchErrorEventTest.php +++ b/test/Application/ExceptionsRaisedInDispatchableShouldRaiseDispatchErrorEventTest.php @@ -4,6 +4,7 @@ namespace LaminasTest\Mvc\Application; +use Laminas\Http\Response; use Laminas\Mvc\MvcEvent; use Laminas\Stdlib\ResponseInterface; use PHPUnit\Framework\TestCase; @@ -19,16 +20,19 @@ public function testExceptionsRaisedInDispatchableShouldRaiseDispatchErrorEvent( { $application = $this->prepareApplication(); - $response = $application->getResponse(); - $events = $application->getEventManager(); - $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, function ($e) use ($response): ResponseInterface { + $events = $application->getEventManager(); + $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, function (MvcEvent $e): ResponseInterface { $exception = $e->getParam('exception'); $this->assertInstanceOf('Exception', $exception); + $response = $e->getResponse(); $response->setContent($exception->getMessage()); return $response; }); $application->run(); + + $response = $application->getMvcEvent()->getResponse(); + self::assertInstanceOf(Response::class, $response); $this->assertStringContainsString('Raised an exception', $response->getContent()); } } diff --git a/test/Application/InabilityToRetrieveControllerShouldTriggerDispatchErrorTest.php b/test/Application/InabilityToRetrieveControllerShouldTriggerDispatchErrorTest.php index bcc59ab3..455ac482 100644 --- a/test/Application/InabilityToRetrieveControllerShouldTriggerDispatchErrorTest.php +++ b/test/Application/InabilityToRetrieveControllerShouldTriggerDispatchErrorTest.php @@ -20,16 +20,19 @@ public function testInabilityToRetrieveControllerShouldTriggerDispatchError(): v { $application = $this->prepareApplication(); - $response = $application->getResponse(); - $events = $application->getEventManager(); - $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, static function ($e) use ($response): ResponseInterface { + $events = $application->getEventManager(); + $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, static function (MvcEvent $e): ResponseInterface { $error = $e->getError(); $controller = $e->getController(); + $response = $e->getResponse(); $response->setContent("Code: " . $error . '; Controller: ' . $controller); return $response; }); $application->run(); + + $response = $application->getMvcEvent()->getResponse(); + self::assertInstanceOf(ResponseInterface::class, $response); $this->assertStringContainsString(Application::ERROR_CONTROLLER_NOT_FOUND, $response->getContent()); $this->assertStringContainsString('bad', $response->getContent()); } diff --git a/test/Application/InabilityToRetrieveControllerShouldTriggerExceptionTest.php b/test/Application/InabilityToRetrieveControllerShouldTriggerExceptionTest.php index b7eb5c6e..3b39b6be 100644 --- a/test/Application/InabilityToRetrieveControllerShouldTriggerExceptionTest.php +++ b/test/Application/InabilityToRetrieveControllerShouldTriggerExceptionTest.php @@ -20,16 +20,19 @@ public function testInabilityToRetrieveControllerShouldTriggerExceptionError(): { $application = $this->prepareApplication(); - $response = $application->getResponse(); - $events = $application->getEventManager(); - $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, static function ($e) use ($response): ResponseInterface { + $events = $application->getEventManager(); + $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, static function (MvcEvent $e): ResponseInterface { $error = $e->getError(); $controller = $e->getController(); + $response = $e->getResponse(); $response->setContent("Code: " . $error . '; Controller: ' . $controller); return $response; }); $application->run(); + + $response = $application->getMvcEvent()->getResponse(); + self::assertInstanceOf(ResponseInterface::class, $response); $this->assertStringContainsString(Application::ERROR_CONTROLLER_NOT_FOUND, $response->getContent()); $this->assertStringContainsString('bad', $response->getContent()); } diff --git a/test/Application/InvalidControllerTypeShouldTriggerDispatchErrorTest.php b/test/Application/InvalidControllerTypeShouldTriggerDispatchErrorTest.php index 1195db59..b922cbbe 100644 --- a/test/Application/InvalidControllerTypeShouldTriggerDispatchErrorTest.php +++ b/test/Application/InvalidControllerTypeShouldTriggerDispatchErrorTest.php @@ -20,17 +20,20 @@ public function testInvalidControllerTypeShouldTriggerDispatchError(): void { $application = $this->prepareApplication(); - $response = $application->getResponse(); - $events = $application->getEventManager(); - $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, static function ($e) use ($response): ResponseInterface { + $events = $application->getEventManager(); + $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, static function (MvcEvent $e): ResponseInterface { $error = $e->getError(); $controller = $e->getController(); $class = $e->getControllerClass(); + $response = $e->getResponse(); $response->setContent("Code: " . $error . '; Controller: ' . $controller . '; Class: ' . $class); return $response; }); $application->run(); + + $response = $application->getMvcEvent()->getResponse(); + self::assertInstanceOf(ResponseInterface::class, $response); $this->assertStringContainsString(Application::ERROR_CONTROLLER_INVALID, $response->getContent()); $this->assertStringContainsString('bad', $response->getContent()); } diff --git a/test/Application/InvalidControllerTypeTrait.php b/test/Application/InvalidControllerTypeTrait.php index bbf0d240..fb050fee 100644 --- a/test/Application/InvalidControllerTypeTrait.php +++ b/test/Application/InvalidControllerTypeTrait.php @@ -7,8 +7,9 @@ use Laminas\Http\PhpEnvironment\Request; use Laminas\Http\PhpEnvironment\Response; use Laminas\Mvc\Application; +use Laminas\Mvc\ConfigProvider; use Laminas\Mvc\Controller\ControllerManager; -use Laminas\Router\ConfigProvider; +use Laminas\Router; use Laminas\Router\Http\Literal; use Laminas\ServiceManager\ServiceManager; use Laminas\Stdlib\ArrayUtils; @@ -21,8 +22,8 @@ trait InvalidControllerTypeTrait { public function prepareApplication(): Application { - $config = [ - 'router' => [ + $testConfig = [ + 'router' => [ 'routes' => [ 'path' => [ 'type' => Literal::class, @@ -36,14 +37,7 @@ public function prepareApplication(): Application ], ], ], - ]; - - $serviceConfig = ArrayUtils::merge( - ArrayUtils::merge( - (new \Laminas\Mvc\ConfigProvider())->getDependencies(), - (new ConfigProvider())->getDependencyConfig(), - ), - [ + 'dependencies' => [ 'aliases' => [ 'ControllerLoader' => 'ControllerManager', ], @@ -53,7 +47,6 @@ public function prepareApplication(): Application 'bad' => static fn(): stdClass => new stdClass(), ], ]), - 'Router' => static fn($services) => $services->get('HttpRouter'), ], 'invokables' => [ 'Request' => Request::class, @@ -62,13 +55,20 @@ public function prepareApplication(): Application 'SendResponseListener' => MockSendResponseListener::class, 'BootstrapListener' => StubBootstrapListener::class, ], - 'services' => [ - 'config' => $config, - ], - ] + ], + ]; + + $config = ArrayUtils::merge( + ArrayUtils::merge( + (new ConfigProvider())(), + (new Router\ConfigProvider())(), + ), + $testConfig ); - $services = new ServiceManager($serviceConfig); - $application = $services->get('Application'); + $config['dependencies']['services']['config'] = $config; + + $services = new ServiceManager($config['dependencies']); + $application = $services->get('Application'); $request = $services->get('Request'); $request->setUri('http://example.local/bad'); diff --git a/test/Application/MissingControllerTrait.php b/test/Application/MissingControllerTrait.php index 7340e2da..bd093a13 100644 --- a/test/Application/MissingControllerTrait.php +++ b/test/Application/MissingControllerTrait.php @@ -7,7 +7,8 @@ use Laminas\Http\PhpEnvironment\Request; use Laminas\Http\PhpEnvironment\Response; use Laminas\Mvc\Application; -use Laminas\Router\ConfigProvider; +use Laminas\Mvc\ConfigProvider; +use Laminas\Router; use Laminas\Router\Http\Literal; use Laminas\ServiceManager\ServiceManager; use Laminas\Stdlib\ArrayUtils; @@ -19,8 +20,8 @@ trait MissingControllerTrait { public function prepareApplication(): Application { - $config = [ - 'router' => [ + $testConfig = [ + 'router' => [ 'routes' => [ 'path' => [ 'type' => Literal::class, @@ -34,17 +35,7 @@ public function prepareApplication(): Application ], ], ], - ]; - - $serviceConfig = ArrayUtils::merge( - ArrayUtils::merge( - (new \Laminas\Mvc\ConfigProvider())->getDependencies(), - (new ConfigProvider())->getDependencyConfig(), - ), - [ - 'factories' => [ - 'Router' => static fn($services) => $services->get('HttpRouter'), - ], + 'dependencies' => [ 'invokables' => [ 'Request' => Request::class, 'Response' => Response::class, @@ -52,13 +43,21 @@ public function prepareApplication(): Application 'SendResponseListener' => MockSendResponseListener::class, 'BootstrapListener' => StubBootstrapListener::class, ], - 'services' => [ - 'config' => $config, - ], - ] + ], + ]; + + $config = ArrayUtils::merge( + ArrayUtils::merge( + (new ConfigProvider())(), + (new Router\ConfigProvider())(), + ), + $testConfig ); - $services = new ServiceManager($serviceConfig); - $application = $services->get('Application'); + + $config['dependencies']['services']['config'] = $config; + + $services = new ServiceManager($config['dependencies']); + $application = $services->get('Application'); $request = $services->get('Request'); $request->setUri('http://example.local/bad'); diff --git a/test/Application/PathControllerTrait.php b/test/Application/PathControllerTrait.php index 9d2d0eaf..625e2a7d 100644 --- a/test/Application/PathControllerTrait.php +++ b/test/Application/PathControllerTrait.php @@ -22,8 +22,8 @@ trait PathControllerTrait { public function prepareApplication(): Application { - $config = [ - 'router' => [ + $testConfig = [ + 'router' => [ 'routes' => [ 'path' => [ 'type' => Literal::class, @@ -36,14 +36,7 @@ public function prepareApplication(): Application ], ], ], - ]; - - $serviceConfig = ArrayUtils::merge( - ArrayUtils::merge( - (new ConfigProvider())->getDependencies(), - (new Router\ConfigProvider())->getDependencyConfig(), - ), - [ + 'dependencies' => [ 'aliases' => [ 'ControllerLoader' => ControllerManager::class, ], @@ -53,7 +46,6 @@ public function prepareApplication(): Application 'path' => static fn() => new TestAsset\PathController(), ], ]), - 'Router' => static fn($services) => $services->get('HttpRouter'), ], 'invokables' => [ 'Request' => Request::class, @@ -62,13 +54,20 @@ public function prepareApplication(): Application 'SendResponseListener' => MockSendResponseListener::class, 'BootstrapListener' => StubBootstrapListener::class, ], - 'services' => [ - 'config' => $config, - ], - ] + ], + ]; + + $config = ArrayUtils::merge( + ArrayUtils::merge( + (new ConfigProvider())(), + (new Router\ConfigProvider())(), + ), + $testConfig ); - $services = new ServiceManager($serviceConfig); - $application = $services->get('Application'); + $config['dependencies']['services']['config'] = $config; + + $services = new ServiceManager($config['dependencies']); + $application = $services->get('Application'); $request = $services->get('Request'); $request->setUri('http://example.local/path'); diff --git a/test/ApplicationListenersProviderTest.php b/test/ApplicationListenersProviderTest.php new file mode 100644 index 00000000..f2d1a93d --- /dev/null +++ b/test/ApplicationListenersProviderTest.php @@ -0,0 +1,234 @@ +getListeners(); + foreach (ApplicationListenersProvider::DEFAULT_LISTENERS as $listener) { + self::assertContains($listener, $listeners); + } + } + + public function testWithDefaultListenersAndExtraAddsDefaults(): void + { + $container = self::createStub(ContainerInterface::class); + + $listenerProvider = ApplicationListenersProvider::withDefaultListeners($container, [ + 'ExtraListener', + ]); + + $listeners = $listenerProvider->getListeners(); + foreach (ApplicationListenersProvider::DEFAULT_LISTENERS as $listener) { + self::assertContains($listener, $listeners); + } + + self::assertContains('ExtraListener', $listeners); + } + + public function testWithoutDefaultListenersDoesNotHaveDefaults(): void + { + $container = self::createStub(ContainerInterface::class); + $listenerProvider = ApplicationListenersProvider::withoutDefaultListeners($container, []); + + $listeners = $listenerProvider->getListeners(); + foreach (ApplicationListenersProvider::DEFAULT_LISTENERS as $listener) { + self::assertNotContains($listener, $listeners); + } + } + + public function testWithoutDefaultListenersAndWithExtraDoesNotHaveDefaults(): void + { + $container = self::createStub(ContainerInterface::class); + + $listenerProvider = ApplicationListenersProvider::withoutDefaultListeners($container, [ + 'ExtraListener', + ]); + + $listeners = $listenerProvider->getListeners(); + foreach (ApplicationListenersProvider::DEFAULT_LISTENERS as $listener) { + self::assertNotContains($listener, $listeners); + } + + self::assertContains('ExtraListener', $listeners); + } + + public function testRemovesDuplicatesWithDefaults(): void + { + $container = self::createStub(ContainerInterface::class); + $listenerAggregate = self::createStub(ListenerAggregateInterface::class); + + $listenerProvider = ApplicationListenersProvider::withDefaultListeners($container, [ + ApplicationListenersProvider::DEFAULT_LISTENERS[0], + 'ExtraListener', + $listenerAggregate, + 'ExtraListener', + $listenerAggregate, + ]); + + $listeners = $listenerProvider->getListeners(); + self::assertSame(array_unique($listeners, SORT_REGULAR), $listeners); + } + + public function testRemovesDuplicatesWithoutDefaults(): void + { + $container = self::createStub(ContainerInterface::class); + $listenerAggregate = self::createStub(ListenerAggregateInterface::class); + + $listenerProvider = ApplicationListenersProvider::withoutDefaultListeners($container, [ + 'ExtraListener', + $listenerAggregate, + 'ExtraListener', + $listenerAggregate, + ]); + + $listeners = $listenerProvider->getListeners(); + self::assertSame(array_unique($listeners, SORT_REGULAR), $listeners); + } + + public function testDoesNotPullFromContainerUntilAttaching(): void + { + $container = $this->createMock(ContainerInterface::class); + $container->expects(self::never()) + ->method('get'); + + $listenerProvider = ApplicationListenersProvider::withDefaultListeners($container, [ + 'ExtraListener', + ]); + self::assertNotEmpty($listenerProvider->getListeners()); + + $listenerProvider = ApplicationListenersProvider::withoutDefaultListeners($container, [ + 'ExtraListener', + ]); + self::assertNotEmpty($listenerProvider->getListeners()); + } + + public function testRegistersListeners(): void + { + $container = self::createStub(ContainerInterface::class); + + $application = self::createStub(Application::class); + $events = self::createStub(EventManagerInterface::class); + $application->method('getEventManager') + ->willReturn($events); + + $listenerMap = []; + foreach (ApplicationListenersProvider::DEFAULT_LISTENERS as $listener) { + $listenerMock = $this->createMock(ListenerAggregateInterface::class); + $listenerMock->expects(self::once()) + ->method('attach') + ->with($events); + $listenerMap[] = [$listener, $listenerMock]; + } + $listenerMock = $this->createMock(ListenerAggregateInterface::class); + $listenerMock->expects(self::once()) + ->method('attach') + ->with($events); + $listenerMap[] = ['ExtraListener', $listenerMock]; + $container->method('get') + ->willReturnMap($listenerMap); + + $listenerInstanceMock = $this->createMock(ListenerAggregateInterface::class); + $listenerInstanceMock->expects(self::once()) + ->method('attach') + ->with($events); + + $listenerProvider = ApplicationListenersProvider::withDefaultListeners($container, [ + 'ExtraListener', + $listenerInstanceMock, + ]); + + $listenerProvider->registerListeners($application); + } + + /** + * @return array + */ + public static function invalidListenersProvider(): array + { + return [ + 'closure' => [fn () => null], + 'object' => [new stdClass()], + 'int' => [1], + ]; + } + + /** + * @dataProvider invalidListenersProvider + */ + public function testRejectsInvalidListeners(mixed $listener): void + { + $container = self::createStub(ContainerInterface::class); + + $application = self::createStub(Application::class); + $events = self::createStub(EventManagerInterface::class); + $application->method('getEventManager') + ->willReturn($events); + + /** + * @psalm-suppress MixedArgumentTypeCoercion intentional check of invalid inputs + */ + $listenerProvider = ApplicationListenersProvider::withoutDefaultListeners($container, [ + $listener, + ]); + + self::expectException(DomainException::class); + self::expectExceptionMessage(sprintf( + 'Application listener expected to be instance of %s, %s given', + ListenerAggregateInterface::class, + get_debug_type($listener), + )); + $listenerProvider->registerListeners($application); + } + + /** + * @dataProvider invalidListenersProvider + */ + public function testRejectsInvalidListenersFromContainer(mixed $listener): void + { + $container = self::createStub(ContainerInterface::class); + $container->method('get') + ->willReturnMap([['InvalidListener', $listener]]); + + $application = self::createStub(Application::class); + $events = self::createStub(EventManagerInterface::class); + $application->method('getEventManager') + ->willReturn($events); + + $listenerProvider = ApplicationListenersProvider::withoutDefaultListeners($container, ['InvalidListener']); + + self::expectException(DomainException::class); + self::expectExceptionMessage(sprintf( + 'Application listener with container id "%s" expected to be instance of %s, %s given', + 'InvalidListener', + ListenerAggregateInterface::class, + get_debug_type($listener), + )); + $listenerProvider->registerListeners($application); + } +} diff --git a/test/ApplicationTest.php b/test/ApplicationTest.php index 5123f444..8403743f 100644 --- a/test/ApplicationTest.php +++ b/test/ApplicationTest.php @@ -10,12 +10,12 @@ use Laminas\Http\PhpEnvironment\Request; use Laminas\Http\PhpEnvironment\Response; use Laminas\Mvc\Application; +use Laminas\Mvc\ConfigProvider; use Laminas\Mvc\Controller\ControllerManager; use Laminas\Mvc\MvcEvent; -use Laminas\Router\ConfigProvider; +use Laminas\Router; use Laminas\Router\Http\Literal; use Laminas\Router\RouteMatch; -use Laminas\Router\RouterFactory; use Laminas\Router\SimpleRouteStack; use Laminas\ServiceManager\ServiceManager; use Laminas\Stdlib\ArrayUtils; @@ -32,11 +32,8 @@ use ReflectionProperty; use stdClass; -use function array_shift; use function array_values; -use function is_array; use function sprintf; -use function var_export; class ApplicationTest extends TestCase { @@ -50,12 +47,8 @@ class ApplicationTest extends TestCase public function setUp(): void { - $serviceConfig = ArrayUtils::merge( - ArrayUtils::merge( - (new \Laminas\Mvc\ConfigProvider())->getDependencies(), - (new ConfigProvider())->getDependencyConfig(), - ), - [ + $testConfig = [ + 'dependencies' => [ 'invokables' => [ 'Request' => Request::class, 'Response' => Response::class, @@ -63,31 +56,26 @@ public function setUp(): void 'SendResponseListener' => MockSendResponseListener::class, 'BootstrapListener' => StubBootstrapListener::class, ], - 'factories' => [ - 'Router' => RouterFactory::class, - ], 'services' => [ 'config' => [], ], - ] + ], + ]; + + $config = ArrayUtils::merge( + ArrayUtils::merge( + (new ConfigProvider())(), + (new Router\ConfigProvider())(), + ), + $testConfig ); - $this->serviceManager = new ServiceManager($serviceConfig); + $config['dependencies']['services']['config'] = $config; + + $this->serviceManager = new ServiceManager($config['dependencies']); $this->serviceManager->setAllowOverride(true); $this->application = $this->serviceManager->get('Application'); } - public function testRequestIsPopulatedFromServiceManager(): void - { - $request = $this->serviceManager->get('Request'); - $this->assertSame($request, $this->application->getRequest()); - } - - public function testResponseIsPopulatedFromServiceManager(): void - { - $response = $this->serviceManager->get('Response'); - $this->assertSame($response, $this->application->getResponse()); - } - public function testEventManagerIsPopulated(): void { $events = $this->serviceManager->get('EventManager'); @@ -111,17 +99,6 @@ public function testServiceManagerIsPopulated(): void $this->assertSame($this->serviceManager, $this->application->getServiceManager()); } - public function testConfigIsPopulated(): void - { - $smConfig = $this->serviceManager->get('config'); - $appConfig = $this->application->getConfig(); - $this->assertEquals( - $smConfig, - $appConfig, - sprintf('SM config: %s; App config: %s', var_export($smConfig, true), var_export($appConfig, true)) - ); - } - private function getIdentifiersFromSharedEventManager(SharedEventManager $events): array { $r = new ReflectionProperty($events, 'identifiers'); @@ -130,9 +107,9 @@ private function getIdentifiersFromSharedEventManager(SharedEventManager $events } /** - * @dataProvider bootstrapRegistersListenersProvider + * @dataProvider defaultApplicationListenersProvider */ - public function testBootstrapRegistersListeners(string $listenerServiceName, string $event, string $method): void + public function testHasRegisteredDefaultListeners(string $listenerServiceName, string $event, string $method): void { $listenerService = $this->serviceManager->get($listenerServiceName); $this->application->bootstrap(); @@ -142,7 +119,7 @@ public function testBootstrapRegistersListeners(string $listenerServiceName, str $this->assertContains([$listenerService, $method], $listeners); } - public function bootstrapRegistersListenersProvider(): array + public function defaultApplicationListenersProvider(): array { // @codingStandardsIgnoreStart // [ Service Name, Event, Method, isCustom ] @@ -156,53 +133,52 @@ public function bootstrapRegistersListenersProvider(): array // @codingStandardsIgnoreEnd } - public function testBootstrapAlwaysRegistersDefaultListeners(): void - { - $r = new ReflectionProperty($this->application, 'defaultListeners'); - $r->setAccessible(true); - $defaultListenersNames = $r->getValue($this->application); - $defaultListeners = []; - foreach ($defaultListenersNames as $defaultListenerName) { - $defaultListeners[] = $this->serviceManager->get($defaultListenerName); - } - - $this->application->bootstrap(); - $eventManager = $this->application->getEventManager(); - - $registeredListeners = []; - foreach ($this->getEventsFromEventManager($eventManager) as $event) { - foreach ($this->getListenersForEvent($event, $eventManager) as $listener) { - if (is_array($listener)) { - $listener = array_shift($listener); - } - $registeredListeners[] = $listener; - } - } - - foreach ($defaultListeners as $defaultListener) { - $this->assertContains($defaultListener, $registeredListeners); - } - } - public function testBootstrapRegistersConfiguredMvcEvent(): void { - $this->assertNull($this->application->getMvcEvent()); $this->application->bootstrap(); $event = $this->application->getMvcEvent(); $this->assertInstanceOf(MvcEvent::class, $event); - $request = $this->application->getRequest(); - $response = $this->application->getResponse(); - $router = $this->serviceManager->get('HttpRouter'); + $router = $this->serviceManager->get('HttpRouter'); $this->assertFalse($event->isError()); - $this->assertSame($request, $event->getRequest()); - $this->assertSame($response, $event->getResponse()); + $this->assertNull($event->getRequest()); + $this->assertNull($event->getResponse()); $this->assertSame($router, $event->getRouter()); $this->assertSame($this->application, $event->getApplication()); $this->assertSame($this->application, $event->getTarget()); } + public function testRequestIsPopulatedForPrepareEvent(): void + { + $this->application->bootstrap(); + $request = null; + + $this->application + ->getEventManager() + ->attach(MvcEvent::EVENT_PREPARE, static function (MvcEvent $e) use (&$request) { + $request = $e->getRequest(); + }); + + $this->application->run(); + $this->assertInstanceOf(Request::class, $request); + } + + public function testResponseIsPopulatedForPrepareEvent(): void + { + $this->application->bootstrap(); + $response = null; + + $this->application + ->getEventManager() + ->attach(MvcEvent::EVENT_PREPARE, static function (MvcEvent $e) use (&$response) { + $response = $e->getResponse(); + }); + + $this->application->run(); + $this->assertInstanceOf(Response::class, $response); + } + public function setupPathController(bool $addService = true): Application { $request = $this->serviceManager->get('Request'); @@ -295,7 +271,7 @@ public function testFinishEventIsTriggeredAfterDispatching(): void $application->run(); $this->assertStringContainsString( 'foobar', - $this->application->getResponse()->getBody(), + $application->getMvcEvent()->getResponse()->getBody(), 'The "finish" event was not triggered ("foobar" not in response)' ); } @@ -307,29 +283,30 @@ public function testRoutingFailureShouldTriggerDispatchError(): void $event = $application->getMvcEvent(); $event->setRouter($router); - $response = $application->getResponse(); - $events = $application->getEventManager(); - $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, static function ($e) use ($response): ResponseInterface { - $error = $e->getError(); + $events = $application->getEventManager(); + $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, static function (MvcEvent $e): ResponseInterface { + $error = $e->getError(); + $response = $e->getResponse(); $response->setContent("Code: " . $error); return $response; }); $application->run(); + + $response = $event->getResponse(); + self::assertInstanceOf(ResponseInterface::class, $response); $this->assertTrue($event->isError()); $this->assertStringContainsString(Application::ERROR_ROUTER_NO_MATCH, $response->getContent()); } public function testLocatorExceptionShouldTriggerDispatchError(): void { - $application = $this->setupPathController(false); - $controllerLoader = $application->getServiceManager()->get('ControllerManager'); - $response = new Response(); + $application = $this->setupPathController(false); + $response = new Response(); $application->getEventManager()->attach(MvcEvent::EVENT_DISPATCH_ERROR, static fn($e): Response => $response); - $result = $application->run(); - $this->assertSame($application, $result, $result::class); - $this->assertSame($response, $result->getResponse(), $result::class); + $application->run(); + $this->assertSame($response, $application->getMvcEvent()->getResponse()); } public function testFailureForRouteToReturnRouteMatchShouldPopulateEventError(): void @@ -339,10 +316,10 @@ public function testFailureForRouteToReturnRouteMatchShouldPopulateEventError(): $event = $application->getMvcEvent(); $event->setRouter($router); - $response = $application->getResponse(); - $events = $application->getEventManager(); - $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, static function ($e) use ($response): ResponseInterface { - $error = $e->getError(); + $events = $application->getEventManager(); + $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, static function (MvcEvent $e): ResponseInterface { + $error = $e->getError(); + $response = $e->getResponse(); $response->setContent("Code: " . $error); return $response; }); @@ -355,7 +332,7 @@ public function testFailureForRouteToReturnRouteMatchShouldPopulateEventError(): public function testFinishShouldRunEvenIfRouteEventReturnsResponse(): void { $this->application->bootstrap(); - $response = $this->application->getResponse(); + $response = new Response(); $events = $this->application->getEventManager(); $events->attach(MvcEvent::EVENT_ROUTE, static fn($e): ResponseInterface => $response, 100); @@ -372,7 +349,7 @@ public function testFinishShouldRunEvenIfRouteEventReturnsResponse(): void public function testFinishShouldRunEvenIfDispatchEventReturnsResponse(): void { $this->application->bootstrap(); - $response = $this->application->getResponse(); + $response = new Response(); $events = $this->application->getEventManager(); $events->clearListeners(MvcEvent::EVENT_ROUTE); $events->attach(MvcEvent::EVENT_DISPATCH, static fn($e): ResponseInterface => $response, 100); @@ -391,22 +368,23 @@ public function testApplicationShouldBeEventTargetAtFinishEvent(): void { $application = $this->setupActionController(); - $events = $application->getEventManager(); - $response = $application->getResponse(); - $events->attach(MvcEvent::EVENT_FINISH, static function ($e) use ($response): ResponseInterface { + $events = $application->getEventManager(); + $events->attach(MvcEvent::EVENT_FINISH, static function (MvcEvent $e): ResponseInterface { + $response = $e->getResponse(); $response->setContent("EventClass: " . $e->getTarget()::class); return $response; }); $application->run(); + $response = $application->getMvcEvent()->getResponse(); + self::assertInstanceOf(ResponseInterface::class, $response); $this->assertStringContainsString(Application::class, $response->getContent()); } public function testOnDispatchErrorEventPassedToTriggersShouldBeTheOriginalOne(): void { - $application = $this->setupPathController(false); - $controllerManager = $application->getServiceManager()->get('ControllerManager'); - $model = $this->createMock(ViewModel::class); + $application = $this->setupPathController(false); + $model = $this->createMock(ViewModel::class); $application->getEventManager()->attach( MvcEvent::EVENT_DISPATCH_ERROR, static function ($e) use ($model): void { @@ -423,7 +401,6 @@ public function testReturnsResponseFromListenerWhenRouteEventShortCircuits(): vo { $this->application->bootstrap(); $testResponse = new Response(); - $response = $this->application->getResponse(); $events = $this->application->getEventManager(); $events->clearListeners(MvcEvent::EVENT_DISPATCH); $events->attach(MvcEvent::EVENT_ROUTE, static function ($e) use ($testResponse): Response { @@ -445,7 +422,6 @@ public function testReturnsResponseFromListenerWhenDispatchEventShortCircuits(): { $this->application->bootstrap(); $testResponse = new Response(); - $response = $this->application->getResponse(); $events = $this->application->getEventManager(); $events->clearListeners(MvcEvent::EVENT_ROUTE); $events->attach(MvcEvent::EVENT_DISPATCH, static function ($e) use ($testResponse): Response { diff --git a/test/Controller/Plugin/ForwardTest.php b/test/Controller/Plugin/ForwardTest.php index 7cf4a9a9..942e6ea3 100644 --- a/test/Controller/Plugin/ForwardTest.php +++ b/test/Controller/Plugin/ForwardTest.php @@ -9,7 +9,7 @@ use Laminas\EventManager\SharedEventManagerInterface; use Laminas\Http\Request; use Laminas\Http\Response; -use Laminas\Mvc\ApplicationInterface; +use Laminas\Mvc\Application; use Laminas\Mvc\Controller\ControllerManager; use Laminas\Mvc\Controller\Plugin\Forward; use Laminas\Mvc\Controller\Plugin\Forward as ForwardPlugin; @@ -45,7 +45,7 @@ class ForwardTest extends TestCase public function setUp(): void { $eventManager = $this->createEventManager(new SharedEventManager()); - $mockApplication = $this->createMock(ApplicationInterface::class); + $mockApplication = $this->createMock(Application::class); $mockApplication->expects($this->any())->method('getEventManager')->will($this->returnValue($eventManager)); $event = new MvcEvent(); @@ -202,7 +202,7 @@ static function ($e): void { ]); // @codingStandardsIgnoreEnd $events = $this->createEventManager($sharedEvents); - $application = $this->createMock(ApplicationInterface::class); + $application = $this->createMock(Application::class); $application->method('getEventManager')->willReturn($events); $event = $this->controller->getEvent(); $event->setApplication($application); @@ -234,7 +234,7 @@ public function testProblemListenersAreDetachedAndReattachedWhenPluginDispatches ->willReturn([-50 => [$myCallback]]); $events = $this->createEventManager($sharedEvents); - $application = $this->createMock(ApplicationInterface::class); + $application = $this->createMock(Application::class); $application->method('getEventManager')->willReturn($events); $event = $this->controller->getEvent(); $event->setApplication($application); @@ -269,7 +269,7 @@ public function testInvokableProblemListenersAreDetachedAndReattachedWhenPluginD ->willReturn([-50 => [$myCallback]]); $events = $this->createEventManager($sharedEvents); - $application = $this->createMock(ApplicationInterface::class); + $application = $this->createMock(Application::class); $application->method('getEventManager')->willReturn($events); $event = $this->controller->getEvent(); $event->setApplication($application); diff --git a/test/DispatchListenerTest.php b/test/DispatchListenerTest.php index 17fdb729..e17a6338 100644 --- a/test/DispatchListenerTest.php +++ b/test/DispatchListenerTest.php @@ -29,7 +29,6 @@ class DispatchListenerTest extends TestCase public function createMvcEvent(string $controllerMatched): MvcEvent { - $response = new Response(); $routeMatch = $this->prophesize(RouteMatch::class); $routeMatch->getParam('controller', 'not-found')->willReturn($controllerMatched); @@ -37,11 +36,10 @@ public function createMvcEvent(string $controllerMatched): MvcEvent $application = $this->prophesize(Application::class); $application->getEventManager()->willReturn($eventManager); - $application->getResponse()->willReturn($response); $event = new MvcEvent(); $event->setRequest(new Request()); - $event->setResponse($response); + $event->setResponse(new Response()); $event->setApplication($application->reveal()); $event->setRouteMatch($routeMatch->reveal());