diff --git a/src/Symfony/Component/HttpKernel/HttpKernel.php b/src/Symfony/Component/HttpKernel/HttpKernel.php index 7474ca4a0e65..4a3df45686e5 100644 --- a/src/Symfony/Component/HttpKernel/HttpKernel.php +++ b/src/Symfony/Component/HttpKernel/HttpKernel.php @@ -30,7 +30,7 @@ * * @api */ -class HttpKernel implements HttpKernelInterface +class HttpKernel implements HttpKernelInterface, TerminableInterface { private $dispatcher; private $resolver; @@ -79,6 +79,19 @@ public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQ } } + /** + * Terminates a request/response cycle. + * + * Should be called after sending the response and before shutting down the kernel. + * + * @api + */ + public function terminate() + { + $event = new PostResponseEvent($this); + $this->dispatcher->dispatch(KernelEvents::TERMINATE, $event); + } + /** * Handles a request to convert it to a response. * diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index e9ebd844aff0..e8efa691da35 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -143,6 +143,13 @@ public function boot() */ public function terminate() { + if (false === $this->booted) { + return; + } + + if ($this->getHttpKernel() instanceof TerminableInterface) { + $this->getHttpKernel()->terminate(); + } } /** diff --git a/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php index 6771362d402b..18180f38fa2c 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/HttpCache/HttpCacheTest.php @@ -35,7 +35,7 @@ public function testTerminateDelegatesTerminationOnlyForTerminableInterface() $kernel = new HttpCache($kernelMock, $storeMock); $kernel->terminate(); - // does implement TerminableInterface + // implements TerminableInterface $kernelMock = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\Kernel') ->disableOriginalConstructor() ->setMethods(array('terminate', 'registerBundles', 'registerContainerConfiguration')) diff --git a/tests/Symfony/Tests/Component/HttpKernel/HttpKernelTest.php b/tests/Symfony/Tests/Component/HttpKernel/HttpKernelTest.php index 149cbeb8dea6..8954cee86bda 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/HttpKernelTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/HttpKernelTest.php @@ -164,6 +164,20 @@ public function testHandleWithAResponseListener() $this->assertEquals('foo', $kernel->handle(new Request())->getContent()); } + public function testTerminate() + { + $dispatcher = new EventDispatcher(); + $kernel = new HttpKernel($dispatcher, $this->getResolver()); + $dispatcher->addListener(KernelEvents::TERMINATE, function ($event) use (&$called, &$capturedKernel) { + $called = true; + $capturedKernel = $event->getKernel(); + }); + + $kernel->terminate(); + $this->assertTrue($called); + $this->assertEquals($kernel, $capturedKernel); + } + protected function getResolver($controller = null) { if (null === $controller) { diff --git a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php index 2572044b4336..fea6e1199dbb 100644 --- a/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php +++ b/tests/Symfony/Tests/Component/HttpKernel/KernelTest.php @@ -652,6 +652,66 @@ public function testInitializeBundleThrowsExceptionWhenABundleExtendsItself() $kernel->initializeBundles(); } + public function testTerminateReturnsSilentlyIfKernelIsNotBooted() + { + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel')) + ->getMock(); + + $kernel->expects($this->never()) + ->method('getHttpKernel'); + + $kernel->setIsBooted(false); + $kernel->terminate(); + } + + public function testTerminateDelegatesTerminationOnlyForTerminableInterface() + { + // does not implement TerminableInterface + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernelInterface') + ->disableOriginalConstructor() + ->getMock(); + + $httpKernelMock + ->expects($this->never()) + ->method('terminate'); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel')) + ->getMock(); + + $kernel->expects($this->once()) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->setIsBooted(true); + $kernel->terminate(); + + // implements TerminableInterface + $httpKernelMock = $this->getMockBuilder('Symfony\Component\HttpKernel\HttpKernel') + ->disableOriginalConstructor() + ->setMethods(array('terminate')) + ->getMock(); + + $httpKernelMock + ->expects($this->once()) + ->method('terminate'); + + $kernel = $this->getMockBuilder('Symfony\Tests\Component\HttpKernel\KernelForTest') + ->disableOriginalConstructor() + ->setMethods(array('getHttpKernel')) + ->getMock(); + + $kernel->expects($this->exactly(2)) + ->method('getHttpKernel') + ->will($this->returnValue($httpKernelMock)); + + $kernel->setIsBooted(true); + $kernel->terminate(); + } + protected function getBundle($dir = null, $parent = null, $className = null, $bundleName = null) { $bundle = $this