From 27803da57cb17aea797b127e3b2e470b973634be Mon Sep 17 00:00:00 2001 From: Stefano Torresi Date: Sat, 21 Feb 2015 15:17:23 +0100 Subject: [PATCH 1/4] add Zend\Mvc\HttpMethodListener --- library/Zend/Mvc/HttpMethodListener.php | 127 ++++++++++++++++++ tests/ZendTest/Mvc/HttpMethodListenerTest.php | 107 +++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 library/Zend/Mvc/HttpMethodListener.php create mode 100644 tests/ZendTest/Mvc/HttpMethodListenerTest.php diff --git a/library/Zend/Mvc/HttpMethodListener.php b/library/Zend/Mvc/HttpMethodListener.php new file mode 100644 index 00000000000..0fc6dbc45be --- /dev/null +++ b/library/Zend/Mvc/HttpMethodListener.php @@ -0,0 +1,127 @@ +setEnabled($enabled); + + if (! empty($allowedMethods)) { + $this->setAllowedMethods($allowedMethods); + } + } + + /** + * {@inheritdoc} + */ + public function attach(EventManagerInterface $events) + { + if (! $this->isEnabled()) { + return; + } + + $this->listeners[] = $events->attach( + MvcEvent::EVENT_ROUTE, + array($this, 'onRoute'), + 10000 + ); + } + + /** + * @param MvcEvent $e + * @return void|HttpResponse + */ + public function onRoute(MvcEvent $e) + { + $request = $e->getRequest(); + $response = $e->getResponse(); + + if (! $request instanceof HttpRequest || ! $response instanceof HttpResponse) { + return; + } + + $method = $request->getMethod(); + + if (in_array($method, $this->getAllowedMethods())) { + return; + } + + $response->setStatusCode(405); + + return $response; + } + + /** + * @return array + */ + public function getAllowedMethods() + { + return $this->allowedMethods; + } + + /** + * @param array $allowedMethods + */ + public function setAllowedMethods(array $allowedMethods) + { + foreach ($allowedMethods as &$value) { + $value = strtoupper($value); + } + $this->allowedMethods = $allowedMethods; + } + + /** + * @return bool + */ + public function isEnabled() + { + return $this->enabled; + } + + /** + * @param bool $enabled + */ + public function setEnabled($enabled) + { + $this->enabled = (bool) $enabled; + } +} diff --git a/tests/ZendTest/Mvc/HttpMethodListenerTest.php b/tests/ZendTest/Mvc/HttpMethodListenerTest.php new file mode 100644 index 00000000000..6c1f18f02bd --- /dev/null +++ b/tests/ZendTest/Mvc/HttpMethodListenerTest.php @@ -0,0 +1,107 @@ +listener = new HttpMethodListener(); + } + + public function testConstructor() + { + $methods = array('foo', 'bar'); + $listener = new HttpMethodListener(false, $methods); + + $this->assertFalse($listener->isEnabled()); + $this->assertSame(array('FOO', 'BAR'), $listener->getAllowedMethods()); + + $listener = new HttpMethodListener(true, array()); + $this->assertNotEmpty($listener->getAllowedMethods()); + } + + public function testAttachesToRouteEvent() + { + $eventManager = $this->getMock('Zend\EventManager\EventManagerInterface'); + $eventManager->expects($this->atLeastOnce()) + ->method('attach') + ->with(MvcEvent::EVENT_ROUTE); + + $this->listener->attach($eventManager); + } + + public function testDoesntAttachIfDisabled() + { + $this->listener->setEnabled(false); + + $eventManager = $this->getMock('Zend\EventManager\EventManagerInterface'); + $eventManager->expects($this->never()) + ->method('attach'); + + $this->listener->attach($eventManager); + } + + public function testOnRouteDoesNothingIfNotHttpEnvironment() + { + $event = new MvcEvent(); + $event->setRequest(new Request()); + + $this->assertNull($this->listener->onRoute($event)); + + $event->setRequest(new HttpRequest()); + $event->setResponse(new Response()); + + $this->assertNull($this->listener->onRoute($event)); + } + + public function testOnRouteDoesNothingIfIfMethodIsAllowed() + { + $event = new MvcEvent(); + $request = new HttpRequest(); + $request->setMethod('foo'); + $event->setRequest($request); + $event->setResponse(new HttpResponse()); + + $this->listener->setAllowedMethods(array('foo')); + + $this->assertNull($this->listener->onRoute($event)); + } + + public function testOnRouteReturns405ResponseIfMethodNotAllowed() + { + $event = new MvcEvent(); + $request = new HttpRequest(); + $request->setMethod('foo'); + $event->setRequest($request); + $event->setResponse(new HttpResponse()); + + $response = $this->listener->onRoute($event); + + $this->assertInstanceOf('Zend\Http\Response', $response); + $this->assertSame(405, $response->getStatusCode()); + } +} From 78f56cbe6b0d153c4c3f672edfbc0497c6b568db Mon Sep 17 00:00:00 2001 From: Stefano Torresi Date: Sat, 21 Feb 2015 15:43:36 +0100 Subject: [PATCH 2/4] add Zend\Mvc\Service\HttpMethodListenerFactory --- library/Zend/Mvc/HttpMethodListener.php | 2 +- .../Mvc/Service/HttpMethodListenerFactory.php | 40 +++++++++++++ .../Service/HttpMethodListenerFactoryTest.php | 60 +++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 library/Zend/Mvc/Service/HttpMethodListenerFactory.php create mode 100644 tests/ZendTest/Mvc/Service/HttpMethodListenerFactoryTest.php diff --git a/library/Zend/Mvc/HttpMethodListener.php b/library/Zend/Mvc/HttpMethodListener.php index 0fc6dbc45be..0ba69843444 100644 --- a/library/Zend/Mvc/HttpMethodListener.php +++ b/library/Zend/Mvc/HttpMethodListener.php @@ -41,7 +41,7 @@ class HttpMethodListener extends AbstractListenerAggregate * @param bool $enabled * @param array $allowedMethods */ - public function __construct($enabled = true, array $allowedMethods = array()) + public function __construct($enabled = true, $allowedMethods = array()) { $this->setEnabled($enabled); diff --git a/library/Zend/Mvc/Service/HttpMethodListenerFactory.php b/library/Zend/Mvc/Service/HttpMethodListenerFactory.php new file mode 100644 index 00000000000..46ea25eeb4e --- /dev/null +++ b/library/Zend/Mvc/Service/HttpMethodListenerFactory.php @@ -0,0 +1,40 @@ +get('config'); + + if (! isset($config['http_methods_listener'])) { + return new HttpMethodListener(); + } + + $listenerConfig = $config['http_methods_listener']; + $enabled = array_key_exists('enabled', $listenerConfig) + ? $listenerConfig['enabled'] + : true; + $allowedMethods = (isset($listenerConfig['allowed_methods']) && is_array($listenerConfig['allowed_methods'])) + ? $listenerConfig['allowed_methods'] + : null; + + return new HttpMethodListener($enabled, $allowedMethods); + } +} diff --git a/tests/ZendTest/Mvc/Service/HttpMethodListenerFactoryTest.php b/tests/ZendTest/Mvc/Service/HttpMethodListenerFactoryTest.php new file mode 100644 index 00000000000..02a6a12207c --- /dev/null +++ b/tests/ZendTest/Mvc/Service/HttpMethodListenerFactoryTest.php @@ -0,0 +1,60 @@ +serviceLocator = $this->getMock('Zend\ServiceManager\ServiceLocatorInterface'); + } + + public function testCreateWithDefaults() + { + $factory = new HttpMethodListenerFactory(); + $listener = $factory->createService($this->serviceLocator); + $this->assertTrue($listener->isEnabled()); + $this->assertNotEmpty($listener->getAllowedMethods()); + } + + public function testCreateWithConfig() + { + $config['http_methods_listener'] = array( + 'enabled' => false, + 'allowed_methods' => array('FOO', 'BAR') + ); + + $this->serviceLocator->expects($this->atLeastOnce()) + ->method('get') + ->with('config') + ->willReturn($config); + + $factory = new HttpMethodListenerFactory(); + $listener = $factory->createService($this->serviceLocator); + + $listenerConfig = $config['http_methods_listener']; + + $this->assertSame($listenerConfig['enabled'], $listener->isEnabled()); + $this->assertSame($listenerConfig['allowed_methods'], $listener->getAllowedMethods()); + } +} From ee3d114dc88c9b4b60ff2a76b1132eec090e09a4 Mon Sep 17 00:00:00 2001 From: Stefano Torresi Date: Sat, 21 Feb 2015 16:21:37 +0100 Subject: [PATCH 3/4] make Application::bootstrap() ready for HttpMethodListener integration --- .../Mvc/Service/ServiceListenerFactory.php | 1 + tests/ZendTest/Mvc/ApplicationTest.php | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/library/Zend/Mvc/Service/ServiceListenerFactory.php b/library/Zend/Mvc/Service/ServiceListenerFactory.php index 84e6f56000a..0bf1ea09591 100644 --- a/library/Zend/Mvc/Service/ServiceListenerFactory.php +++ b/library/Zend/Mvc/Service/ServiceListenerFactory.php @@ -57,6 +57,7 @@ class ServiceListenerFactory implements FactoryInterface 'FormAnnotationBuilder' => 'Zend\Mvc\Service\FormAnnotationBuilderFactory', 'FormElementManager' => 'Zend\Mvc\Service\FormElementManagerFactory', 'HttpRouter' => 'Zend\Mvc\Service\RouterFactory', + 'HttpMethodListener' => 'Zend\Mvc\Service\HttpMethodListenerFactory', 'HttpViewManager' => 'Zend\Mvc\Service\HttpViewManagerFactory', 'HydratorManager' => 'Zend\Mvc\Service\HydratorManagerFactory', 'InjectTemplateListener' => 'Zend\Mvc\Service\InjectTemplateListenerFactory', diff --git a/tests/ZendTest/Mvc/ApplicationTest.php b/tests/ZendTest/Mvc/ApplicationTest.php index e96a7e0e0e7..151473402e5 100644 --- a/tests/ZendTest/Mvc/ApplicationTest.php +++ b/tests/ZendTest/Mvc/ApplicationTest.php @@ -185,6 +185,24 @@ public function testBootstrapRegistersViewManagerAsBootstrapListener() $this->assertSame(array($viewManager, 'onBootstrap'), $callback); } + public function testBootstrapCanRegisterHttpMethodListener() + { + $httpMethodListener = $this->serviceManager->get('HttpMethodListener'); + $this->application->bootstrap(array('HttpMethodListener')); + $events = $this->application->getEventManager(); + $listeners = $events->getListeners(MvcEvent::EVENT_ROUTE); + $foundListener = false; + foreach($listeners as $listener) { + $callback = $listener->getCallback(); + $foundListener = $callback === array($httpMethodListener, 'onRoute'); + if ($foundListener) { + break; + } + } + + $this->assertTrue($foundListener); + } + public function testBootstrapRegistersConfiguredMvcEvent() { $this->assertNull($this->application->getMvcEvent()); From ec5f1a66fa7020a0954f11b1572b642f4abe5bbb Mon Sep 17 00:00:00 2001 From: Stefano Torresi Date: Sat, 21 Feb 2015 17:39:43 +0100 Subject: [PATCH 4/4] refactor Application::bootstrap() tests previous tests assertions were too specific and prevented the introduction of additional default event listeners --- library/Zend/Mvc/Application.php | 1 + tests/ZendTest/Mvc/ApplicationTest.php | 122 ++++++++++--------------- 2 files changed, 50 insertions(+), 73 deletions(-) diff --git a/library/Zend/Mvc/Application.php b/library/Zend/Mvc/Application.php index d4d8f02ee9f..096b9cf8b0b 100644 --- a/library/Zend/Mvc/Application.php +++ b/library/Zend/Mvc/Application.php @@ -66,6 +66,7 @@ class Application implements protected $defaultListeners = array( 'RouteListener', 'DispatchListener', + 'HttpMethodListener', 'ViewManager', 'SendResponseListener', ); diff --git a/tests/ZendTest/Mvc/ApplicationTest.php b/tests/ZendTest/Mvc/ApplicationTest.php index 151473402e5..f390e7a569c 100644 --- a/tests/ZendTest/Mvc/ApplicationTest.php +++ b/tests/ZendTest/Mvc/ApplicationTest.php @@ -137,70 +137,68 @@ public function testEventsAreEmptyAtFirst() $this->assertAttributeEquals(array(), 'identifiers', $sharedEvents); } - public function testBootstrapRegistersRouteListener() + /** + * @param string $listenerServiceName + * @param string $event + * @param string $method + * + * @dataProvider bootstrapRegistersListenersProvider + */ + public function testBootstrapRegistersListeners($listenerServiceName, $event, $method, $isCustom = false) { - $routeListener = $this->serviceManager->get('RouteListener'); - $this->application->bootstrap(); + $listenerService = $this->serviceManager->get($listenerServiceName); + $this->application->bootstrap($isCustom ? (array) $listenerServiceName : array()); $events = $this->application->getEventManager(); - $listeners = $events->getListeners(MvcEvent::EVENT_ROUTE); - $this->assertEquals(1, count($listeners)); - $listener = $listeners->top(); - $callback = $listener->getCallback(); - $this->assertSame(array($routeListener, 'onRoute'), $callback); - } + $listeners = $events->getListeners($event); - public function testBootstrapRegistersDispatchListener() - { - $dispatchListener = $this->serviceManager->get('DispatchListener'); - $this->application->bootstrap(); - $events = $this->application->getEventManager(); - $listeners = $events->getListeners(MvcEvent::EVENT_DISPATCH); - $this->assertEquals(1, count($listeners)); - $listener = $listeners->top(); - $callback = $listener->getCallback(); - $this->assertSame(array($dispatchListener, 'onDispatch'), $callback); + $foundListener = false; + foreach ($listeners as $listener) { + $callback = $listener->getCallback(); + $foundListener = $callback === array($listenerService, $method); + if ($foundListener) { + break; + } + } + $this->assertTrue($foundListener); } - public function testBootstrapRegistersSendResponseListener() + public function bootstrapRegistersListenersProvider() { - $sendResponseListener = $this->serviceManager->get('SendResponseListener'); - $this->application->bootstrap(); - $events = $this->application->getEventManager(); - $listeners = $events->getListeners(MvcEvent::EVENT_FINISH); - $this->assertEquals(1, count($listeners)); - $listener = $listeners->top(); - $callback = $listener->getCallback(); - $this->assertSame(array($sendResponseListener, 'sendResponse'), $callback); + return array( + array('RouteListener', MvcEvent::EVENT_ROUTE, 'onRoute'), + array('DispatchListener', MvcEvent::EVENT_DISPATCH, 'onDispatch'), + array('SendResponseListener', MvcEvent::EVENT_FINISH, 'sendResponse'), + array('ViewManager', MvcEvent::EVENT_BOOTSTRAP, 'onBootstrap'), + array('HttpMethodListener', MvcEvent::EVENT_ROUTE, 'onRoute'), + array('BootstrapListener', MvcEvent::EVENT_BOOTSTRAP, 'onBootstrap', true), + ); } - public function testBootstrapRegistersViewManagerAsBootstrapListener() + public function testBootstrapAlwaysRegistersDefaultListeners() { - $viewManager = $this->serviceManager->get('ViewManager'); - $this->application->bootstrap(); - $events = $this->application->getEventManager(); - $listeners = $events->getListeners(MvcEvent::EVENT_BOOTSTRAP); - $this->assertEquals(1, count($listeners)); - $listener = $listeners->top(); - $callback = $listener->getCallback(); - $this->assertSame(array($viewManager, 'onBootstrap'), $callback); - } + $refl = new \ReflectionProperty($this->application, 'defaultListeners'); + $refl->setAccessible(true); + $defaultListenersNames = $refl->getValue($this->application); + $defaultListeners = array(); + foreach ($defaultListenersNames as $defaultListenerName) { + $defaultListeners[] = $this->serviceManager->get($defaultListenerName); + } - public function testBootstrapCanRegisterHttpMethodListener() - { - $httpMethodListener = $this->serviceManager->get('HttpMethodListener'); - $this->application->bootstrap(array('HttpMethodListener')); - $events = $this->application->getEventManager(); - $listeners = $events->getListeners(MvcEvent::EVENT_ROUTE); - $foundListener = false; - foreach($listeners as $listener) { - $callback = $listener->getCallback(); - $foundListener = $callback === array($httpMethodListener, 'onRoute'); - if ($foundListener) { - break; + $this->application->bootstrap(array('BootstrapListener')); + $eventManager = $this->application->getEventManager(); + + $registeredListeners = array(); + foreach ($eventManager->getEvents() as $event) { + $listeners = $eventManager->getListeners($event); + foreach ($listeners as $listener) { + $callback = $listener->getCallBack(); + $registeredListeners[] = $callback[0]; } } - $this->assertTrue($foundListener); + foreach ($defaultListeners as $defaultListener) { + $this->assertContains($defaultListener, $registeredListeners); + } } public function testBootstrapRegistersConfiguredMvcEvent() @@ -648,28 +646,6 @@ public function testCompleteRequestShouldReturnApplicationInstance() $this->assertSame($this->application, $result); } - - public function testCustomListener() - { - $this->application->bootstrap(array('BootstrapListener')); - - // must contain custom bootstrap listeners - $bootstrapListener = $this->serviceManager->get('BootstrapListener'); - $listeners = $this->application->getEventManager()->getListeners(MvcEvent::EVENT_BOOTSTRAP); - $bootstrapListeners = $bootstrapListener->getListeners(); - $this->assertTrue($listeners->contains($bootstrapListeners[0])); - - // must contain default listeners - $listeners = $this->application->getEventManager()->getListeners(MvcEvent::EVENT_DISPATCH); - $this->assertEquals(1, count($listeners)); - - $listeners = $this->application->getEventManager()->getListeners(MvcEvent::EVENT_ROUTE); - $this->assertEquals(1, count($listeners)); - - $listeners = $this->application->getEventManager()->getListeners(MvcEvent::EVENT_FINISH); - $this->assertEquals(1, count($listeners)); - } - public function testFailedRoutingShouldBePreventable() { $this->application->bootstrap();