Browse Source

Restore changes from 11978

Revert "Revert "Merge branch 'issue-11978' into master.""

This reverts commit a48697ac1983a92ad070dd64cac6a0f9ae75ec13, which was
a revert to take changes out of master that were intended for 3.next
mark_story 8 years ago
parent
commit
6b80c86224

+ 15 - 2
src/Error/ExceptionRenderer.php

@@ -29,6 +29,7 @@ use Cake\Utility\Inflector;
 use Cake\View\Exception\MissingTemplateException;
 use Exception;
 use PDOException;
+use Psr\Http\Message\ServerRequestInterface;
 
 /**
  * Exception Renderer.
@@ -79,15 +80,25 @@ class ExceptionRenderer implements ExceptionRendererInterface
     public $method = '';
 
     /**
+     * If set, this will be request used to create the controller that will render
+     * the error.
+     *
+     * @var \Psr\Http\Message\ServerRequestInterface|null
+     */
+    protected $request = null;
+
+    /**
      * Creates the controller to perform rendering on the error response.
      * If the error is a Cake\Core\Exception\Exception it will be converted to either a 400 or a 500
      * code error depending on the code used to construct the error.
      *
      * @param \Exception $exception Exception.
+     * @param \Psr\Http\Message\ServerRequestInterface $request The request - if this is set it will be used instead of creating a new one
      */
-    public function __construct(Exception $exception)
+    public function __construct(Exception $exception, ServerRequestInterface $request = null)
     {
         $this->error = $exception;
+        $this->request = $request;
         $this->controller = $this->_getController();
     }
 
@@ -114,9 +125,11 @@ class ExceptionRenderer implements ExceptionRendererInterface
      */
     protected function _getController()
     {
-        if (!$request = Router::getRequest(true)) {
+        $request = $this->request ?: Router::getRequest(true);
+        if ($request === null) {
             $request = ServerRequestFactory::fromGlobals();
         }
+
         $response = new Response();
         $controller = null;
 

+ 5 - 4
src/Error/Middleware/ErrorHandlerMiddleware.php

@@ -113,7 +113,7 @@ class ErrorHandlerMiddleware
      */
     public function handleException($exception, $request, $response)
     {
-        $renderer = $this->getRenderer($exception);
+        $renderer = $this->getRenderer($exception, $request);
         try {
             $res = $renderer->render();
             $this->logException($request, $exception);
@@ -148,10 +148,11 @@ class ErrorHandlerMiddleware
      * Get a renderer instance
      *
      * @param \Exception $exception The exception being rendered.
+     * @param \Psr\Http\Message\ServerRequestInterface $request The request.
      * @return \Cake\Error\ExceptionRendererInterface The exception renderer.
      * @throws \Exception When the renderer class cannot be found.
      */
-    protected function getRenderer($exception)
+    protected function getRenderer($exception, $request)
     {
         if (!$this->exceptionRenderer) {
             $this->exceptionRenderer = $this->getConfig('exceptionRenderer') ?: ExceptionRenderer::class;
@@ -171,11 +172,11 @@ class ErrorHandlerMiddleware
                 ));
             }
 
-            return new $class($exception);
+            return new $class($exception, $request);
         }
         $factory = $this->exceptionRenderer;
 
-        return $factory($exception);
+        return $factory($exception, $request);
     }
 
     /**

+ 3 - 0
src/TestSuite/IntegrationTestCase.php

@@ -23,6 +23,7 @@ if (class_exists('PHPUnit_Runner_Version', false) && !interface_exists('PHPUnit\
 use Cake\Core\Configure;
 use Cake\Database\Exception as DatabaseException;
 use Cake\Http\ServerRequest;
+use Cake\Http\ServerRequestFactory;
 use Cake\Http\Session;
 use Cake\Routing\Router;
 use Cake\TestSuite\Stub\TestExceptionRenderer;
@@ -481,6 +482,7 @@ abstract class IntegrationTestCase extends TestCase
     protected function _sendRequest($url, $method, $data = [])
     {
         $dispatcher = $this->_makeDispatcher();
+        $psrRequest = null;
         try {
             $request = $this->_buildRequest($url, $method, $data);
             $response = $dispatcher->execute($request);
@@ -497,6 +499,7 @@ abstract class IntegrationTestCase extends TestCase
             throw $e;
         } catch (Exception $e) {
             $this->_exception = $e;
+            // Simulate the global exception handler being invoked.
             $this->_handleError($e);
         }
     }

+ 31 - 32
src/TestSuite/MiddlewareDispatcher.php

@@ -67,41 +67,10 @@ class MiddlewareDispatcher
     }
 
     /**
-     * Run a request and get the response.
-     *
-     * @param \Cake\Http\ServerRequest $request The request to execute.
-     * @return \Psr\Http\Message\ResponseInterface The generated response.
-     */
-    public function execute($request)
-    {
-        try {
-            $reflect = new ReflectionClass($this->_class);
-            $app = $reflect->newInstanceArgs($this->_constructorArgs);
-        } catch (ReflectionException $e) {
-            throw new LogicException(sprintf(
-                'Cannot load "%s" for use in integration testing.',
-                $this->_class
-            ));
-        }
-
-        // Spy on the controller using the initialize hook instead
-        // of the dispatcher hooks as those will be going away one day.
-        EventManager::instance()->on(
-            'Controller.initialize',
-            [$this->_test, 'controllerSpy']
-        );
-
-        $server = new Server($app);
-        $psrRequest = $this->_createRequest($request);
-
-        return $server->run($psrRequest);
-    }
-
-    /**
      * Create a PSR7 request from the request spec.
      *
      * @param array $spec The request spec.
-     * @return \Psr\Http\Message\RequestInterface
+     * @return \Psr\Http\Message\ServerRequestInterface
      */
     protected function _createRequest($spec)
     {
@@ -125,4 +94,34 @@ class MiddlewareDispatcher
 
         return $request;
     }
+
+    /**
+     * Run a request and get the response.
+     *
+     * @param array $requestSpec The request spec to execute.
+     * @return \Psr\Http\Message\ResponseInterface The generated response.
+     */
+    public function execute($requestSpec)
+    {
+        try {
+            $reflect = new ReflectionClass($this->_class);
+            $app = $reflect->newInstanceArgs($this->_constructorArgs);
+        } catch (ReflectionException $e) {
+            throw new LogicException(sprintf(
+                'Cannot load "%s" for use in integration testing.',
+                $this->_class
+            ));
+        }
+
+        // Spy on the controller using the initialize hook instead
+        // of the dispatcher hooks as those will be going away one day.
+        EventManager::instance()->on(
+            'Controller.initialize',
+            [$this->_test, 'controllerSpy']
+        );
+
+        $server = new Server($app);
+
+        return $server->run($this->_createRequest($requestSpec));
+    }
 }

+ 19 - 0
tests/TestCase/TestSuite/IntegrationTestCaseTest.php

@@ -18,6 +18,7 @@ use Cake\Core\Configure;
 use Cake\Event\EventManager;
 use Cake\Http\Response;
 use Cake\Routing\DispatcherFactory;
+use Cake\Routing\RouteBuilder;
 use Cake\Routing\Router;
 use Cake\Routing\Route\InflectedRoute;
 use Cake\TestSuite\IntegrationTestCase;
@@ -229,6 +230,24 @@ class IntegrationTestCaseTest extends IntegrationTestCase
         $this->assertEquals('5', $this->_getBodyAsString());
     }
 
+    public function testExceptionsInMiddlewareJsonView()
+    {
+        Router::reload();
+        Router::connect('/json_response/api_get_data', [
+            'controller' => 'JsonResponse',
+            'action' => 'apiGetData'
+        ]);
+
+        $this->configApplication(Configure::read('App.namespace') . '\ApplicationWithExceptionsInMiddleware', null);
+
+        $this->_request['headers'] = [ "Accept" => "application/json" ];
+        $this->get('/json_response/api_get_data');
+        $this->assertResponseCode(403);
+        $this->assertHeader('Content-Type', 'application/json; charset=UTF-8');
+        $this->assertResponseContains('"message": "Sample Message"');
+        $this->assertResponseContains('"code": 403');
+    }
+
     /**
      * Test sending head requests.
      *

+ 54 - 0
tests/test_app/TestApp/ApplicationWithExceptionsInMiddleware.php

@@ -0,0 +1,54 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link          https://cakephp.org CakePHP(tm) Project
+ * @since         3.6.2
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ */
+namespace TestApp;
+
+use Cake\Error\Middleware\ErrorHandlerMiddleware;
+use Cake\Http\BaseApplication;
+use Cake\Routing\Middleware\RoutingMiddleware;
+use TestApp\Middleware\ThrowsExceptionMiddleware;
+
+/**
+ * Simple Application class doing nothing that:
+ */
+class ApplicationWithExceptionsInMiddleware extends BaseApplication
+{
+    /**
+     * Bootstrap hook.
+     *
+     * Nerfed as this is for IntegrationTestCase testing.
+     *
+     * @return void
+     */
+    public function bootstrap()
+    {
+        // Do nothing.
+    }
+
+    public function middleware($middlewareQueue)
+    {
+        $middlewareQueue
+            // Catch any exceptions in the lower layers,
+            // and make an error page/response
+            ->add(ErrorHandlerMiddleware::class)
+
+            // Throw an error
+            ->add(ThrowsExceptionMiddleware::class)
+
+            // Add routing middleware.
+            ->add(new RoutingMiddleware($this));
+
+        return $middlewareQueue;
+    }
+}

+ 2 - 1
tests/test_app/TestApp/Error/TestAppsExceptionRenderer.php

@@ -17,7 +17,8 @@ class TestAppsExceptionRenderer extends ExceptionRenderer
      */
     protected function _getController()
     {
-        if (!$request = Router::getRequest(true)) {
+        $request = $this->request ?: Router::getRequest(true);
+        if ($request === null) {
             $request = new ServerRequest();
         }
         $response = new Response();

+ 28 - 0
tests/test_app/TestApp/Middleware/ThrowsExceptionMiddleware.php

@@ -0,0 +1,28 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
+ * @link          https://cakephp.org CakePHP(tm) Project
+ * @since         3.6.2
+ * @license       https://opensource.org/licenses/mit-license.php MIT License
+ */
+namespace TestApp\Middleware;
+
+use Cake\Http\Exception\ForbiddenException;
+
+/**
+ * Testing stub for middleware tests.
+ */
+class ThrowsExceptionMiddleware
+{
+    public function __invoke($req, $res, $next)
+    {
+        throw new ForbiddenException("Sample Message");
+    }
+}