Issue #2739617 by dawehner, Wim Leers, neclimdul, chx: Make it easier to write on4xx() exception subscribers

8.3.x
Alex Pott 2017-01-05 13:07:02 +00:00
parent 3c789acc28
commit efb8f715f6
23 changed files with 26 additions and 307 deletions

View File

@ -3,7 +3,6 @@
namespace Drupal\Core\EventSubscriber;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
/**
@ -28,79 +27,15 @@ class ExceptionJsonSubscriber extends HttpExceptionSubscriberBase {
}
/**
* Handles a 400 error for JSON.
* Handles all 4xx errors for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on400(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_BAD_REQUEST);
$event->setResponse($response);
}
/**
* Handles a 403 error for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on403(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_FORBIDDEN);
$event->setResponse($response);
}
/**
* Handles a 404 error for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on404(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_NOT_FOUND);
$event->setResponse($response);
}
/**
* Handles a 405 error for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on405(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(array('message' => $event->getException()->getMessage()), Response::HTTP_METHOD_NOT_ALLOWED);
$event->setResponse($response);
}
/**
* Handles a 406 error for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on406(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(['message' => $event->getException()->getMessage()], Response::HTTP_NOT_ACCEPTABLE);
$event->setResponse($response);
}
/**
* Handles a 415 error for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on415(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(['message' => $event->getException()->getMessage()], Response::HTTP_UNSUPPORTED_MEDIA_TYPE);
$event->setResponse($response);
}
/**
* Handles a 422 error for JSON.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on422(GetResponseForExceptionEvent $event) {
$response = new JsonResponse(['message' => $event->getException()->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
public function on4xx(GetResponseForExceptionEvent $event) {
/** @var \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $exception */
$exception = $event->getException();
$response = new JsonResponse(['message' => $event->getException()->getMessage()], $exception->getStatusCode());
$event->setResponse($response);
}

View File

@ -13,12 +13,20 @@ use Symfony\Component\HttpKernel\KernelEvents;
* A subscriber may extend this class and implement getHandledFormats() to
* indicate which request formats it will respond to. Then implement an on*()
* method for any error code (HTTP response code) that should be handled. For
* example, to handle 404 Not Found messages add a method:
* example, to handle a specific error code like 404 Not Found messages add the
* method:
*
* @code
* public function on404(GetResponseForExceptionEvent $event) {}
* @endcode
*
* To implement a fallback for the entire 4xx class of codes, implement the
* method:
*
* @code
* public function on4xx(GetResponseForExceptionEvent $event) {}
* @endcode
*
* That method should then call $event->setResponse() to set the response object
* for the exception. Alternatively, it may opt not to do so and then other
* listeners will have the opportunity to handle the exception.
@ -90,6 +98,9 @@ abstract class HttpExceptionSubscriberBase implements EventSubscriberInterface {
if ($exception instanceof HttpExceptionInterface && (empty($handled_formats) || in_array($format, $handled_formats))) {
$method = 'on' . $exception->getStatusCode();
// Keep just the leading number of the status code to produce either a
// on400 or a 500 method callback.
$method_fallback = 'on' . substr($exception->getStatusCode(), 0, 1) . 'xx';
// We want to allow the method to be called and still not set a response
// if it has additional filtering logic to determine when it will apply.
// It is therefore the method's responsibility to set the response on the
@ -97,6 +108,9 @@ abstract class HttpExceptionSubscriberBase implements EventSubscriberInterface {
if (method_exists($this, $method)) {
$this->$method($event);
}
elseif (method_exists($this, $method_fallback)) {
$this->$method_fallback($event);
}
}
}

View File

@ -2,7 +2,6 @@
namespace Drupal\Tests\hal\Functional\EntityResource\Block;
use Drupal\Tests\hal\Functional\HalJsonBasicAuthWorkaroundFor2805281Trait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\Block\BlockResourceTestBase;
@ -38,9 +37,4 @@ class BlockHalJsonBasicAuthTest extends BlockResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use HalJsonBasicAuthWorkaroundFor2805281Trait {
HalJsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -2,7 +2,6 @@
namespace Drupal\Tests\hal\Functional\EntityResource\Comment;
use Drupal\Tests\hal\Functional\HalJsonBasicAuthWorkaroundFor2805281Trait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
@ -22,9 +21,4 @@ class CommentHalJsonBasicAuthTest extends CommentHalJsonTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use HalJsonBasicAuthWorkaroundFor2805281Trait {
HalJsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -2,7 +2,6 @@
namespace Drupal\Tests\hal\Functional\EntityResource\ConfigTest;
use Drupal\Tests\hal\Functional\HalJsonBasicAuthWorkaroundFor2805281Trait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\ConfigTest\ConfigTestResourceTestBase;
@ -38,9 +37,4 @@ class ConfigTestHalJsonBasicAuthTest extends ConfigTestResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use HalJsonBasicAuthWorkaroundFor2805281Trait {
HalJsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -2,7 +2,6 @@
namespace Drupal\Tests\hal\Functional\EntityResource\EntityTest;
use Drupal\Tests\hal\Functional\HalJsonBasicAuthWorkaroundFor2805281Trait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
@ -22,9 +21,4 @@ class EntityTestHalJsonBasicAuthTest extends EntityTestHalJsonAnonTest {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use HalJsonBasicAuthWorkaroundFor2805281Trait {
HalJsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -2,7 +2,6 @@
namespace Drupal\Tests\hal\Functional\EntityResource\Node;
use Drupal\Tests\hal\Functional\HalJsonBasicAuthWorkaroundFor2805281Trait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
@ -22,9 +21,4 @@ class NodeHalJsonBasicAuthTest extends NodeHalJsonAnonTest {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use HalJsonBasicAuthWorkaroundFor2805281Trait {
HalJsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -2,7 +2,6 @@
namespace Drupal\Tests\hal\Functional\EntityResource\Role;
use Drupal\Tests\hal\Functional\HalJsonBasicAuthWorkaroundFor2805281Trait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\Role\RoleResourceTestBase;
@ -38,9 +37,4 @@ class RoleHalJsonBasicAuthTest extends RoleResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use HalJsonBasicAuthWorkaroundFor2805281Trait {
HalJsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -2,7 +2,6 @@
namespace Drupal\Tests\hal\Functional\EntityResource\Term;
use Drupal\Tests\hal\Functional\HalJsonBasicAuthWorkaroundFor2805281Trait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
@ -22,9 +21,4 @@ class TermHalJsonBasicAuthTest extends TermHalJsonAnonTest {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use HalJsonBasicAuthWorkaroundFor2805281Trait {
HalJsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -2,7 +2,6 @@
namespace Drupal\Tests\hal\Functional\EntityResource\User;
use Drupal\Tests\hal\Functional\HalJsonBasicAuthWorkaroundFor2805281Trait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
@ -22,9 +21,4 @@ class UserHalJsonBasicAuthTest extends UserHalJsonAnonTest {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use HalJsonBasicAuthWorkaroundFor2805281Trait {
HalJsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -2,7 +2,6 @@
namespace Drupal\Tests\hal\Functional\EntityResource\Vocabulary;
use Drupal\Tests\hal\Functional\HalJsonBasicAuthWorkaroundFor2805281Trait;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\Vocabulary\VocabularyResourceTestBase;
@ -38,9 +37,4 @@ class VocabularyHalJsonBasicAuthTest extends VocabularyResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use HalJsonBasicAuthWorkaroundFor2805281Trait {
HalJsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace Drupal\Tests\hal\Functional;
use Psr\Http\Message\ResponseInterface;
trait HalJsonBasicAuthWorkaroundFor2805281Trait {
/**
* {@inheritdoc}
*
* Note how the response claims it contains a application/hal+json body, but
* in reality it contains a text/plain body! Also, the correct error MIME type
* is application/json.
*
* @todo Fix in https://www.drupal.org/node/2805281: remove this trait.
*/
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
$this->assertSame(401, $response->getStatusCode());
// @todo this works fine locally, but on testbot it comes back with
// 'text/plain; charset=UTF-8'. WTF.
// $this->assertSame(['application/hal+json'], $response->getHeader('Content-Type'));
$this->assertSame('No authentication credentials provided.', (string) $response->getBody());
}
}

View File

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Block;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -37,9 +36,4 @@ class BlockJsonBasicAuthTest extends BlockResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Comment;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -37,9 +36,4 @@ class CommentJsonBasicAuthTest extends CommentResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\ConfigTest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -37,9 +36,4 @@ class ConfigTestJsonBasicAuthTest extends ConfigTestResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\EntityTest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -37,9 +36,4 @@ class EntityTestJsonBasicAuthTest extends EntityTestResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Node;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -37,9 +36,4 @@ class NodeJsonBasicAuthTest extends NodeResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Role;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Psr\Http\Message\ResponseInterface;
/**
* @group rest
@ -37,12 +36,4 @@ class RoleJsonBasicAuthTest extends RoleResourceTestBase {
*/
protected static $auth = 'basic_auth';
/**
* {@inheritdoc}
*/
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
$this->assertSame(401, $response->getStatusCode());
$this->assertSame('{"message":"A fatal error occurred: No authentication credentials provided."}', (string) $response->getBody());
}
}

View File

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Term;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -37,9 +36,4 @@ class TermJsonBasicAuthTest extends TermResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\User;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -37,9 +36,4 @@ class UserJsonBasicAuthTest extends UserResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -3,7 +3,6 @@
namespace Drupal\Tests\rest\Functional\EntityResource\Vocabulary;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\JsonBasicAuthWorkaroundFor2805281Trait;
/**
* @group rest
@ -37,9 +36,4 @@ class VocabularyJsonBasicAuthTest extends VocabularyResourceTestBase {
*/
protected static $auth = 'basic_auth';
// @todo Fix in https://www.drupal.org/node/2805281: remove this trait usage.
use JsonBasicAuthWorkaroundFor2805281Trait {
JsonBasicAuthWorkaroundFor2805281Trait::assertResponseWhenMissingAuthentication insteadof BasicAuthResourceTestTrait;
}
}

View File

@ -1,25 +0,0 @@
<?php
namespace Drupal\Tests\rest\Functional;
use Psr\Http\Message\ResponseInterface;
trait JsonBasicAuthWorkaroundFor2805281Trait {
/**
* {@inheritdoc}
*
* Note that strange 'A fatal error occurred: ' prefix, that should not exist.
*
* @todo Fix in https://www.drupal.org/node/2805281: remove this trait.
*/
protected function assertResponseWhenMissingAuthentication(ResponseInterface $response) {
$this->assertSame(401, $response->getStatusCode());
$this->assertSame([static::$expectedErrorMimeType], $response->getHeader('Content-Type'));
// Note that strange 'A fatal error occurred: ' prefix, that should not
// exist.
// @todo Fix in https://www.drupal.org/node/2805281.
$this->assertSame('{"message":"A fatal error occurred: No authentication credentials provided."}', (string) $response->getBody());
}
}

View File

@ -56,88 +56,20 @@ class DefaultExceptionSubscriber extends HttpExceptionSubscriberBase {
}
/**
* Handles a 400 error for HTTP.
* Handles all 4xx errors for all serialization failures.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on400(GetResponseForExceptionEvent $event) {
$this->setEventResponse($event, Response::HTTP_BAD_REQUEST);
}
public function on4xx(GetResponseForExceptionEvent $event) {
/** @var \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $exception */
$exception = $event->getException();
/**
* Handles a 403 error for HTTP.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on403(GetResponseForExceptionEvent $event) {
$this->setEventResponse($event, Response::HTTP_FORBIDDEN);
}
/**
* Handles a 404 error for HTTP.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on404(GetResponseForExceptionEvent $event) {
$this->setEventResponse($event, Response::HTTP_NOT_FOUND);
}
/**
* Handles a 405 error for HTTP.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on405(GetResponseForExceptionEvent $event) {
$this->setEventResponse($event, Response::HTTP_METHOD_NOT_ALLOWED);
}
/**
* Handles a 406 error for HTTP.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on406(GetResponseForExceptionEvent $event) {
$this->setEventResponse($event, Response::HTTP_NOT_ACCEPTABLE);
}
/**
* Handles a 422 error for HTTP.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on422(GetResponseForExceptionEvent $event) {
$this->setEventResponse($event, Response::HTTP_UNPROCESSABLE_ENTITY);
}
/**
* Handles a 429 error for HTTP.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The event to process.
*/
public function on429(GetResponseForExceptionEvent $event) {
$this->setEventResponse($event, Response::HTTP_TOO_MANY_REQUESTS);
}
/**
* Sets the Response for the exception event.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The current exception event.
* @param int $status
* The HTTP status code to set for the response.
*/
protected function setEventResponse(GetResponseForExceptionEvent $event, $status) {
$format = $event->getRequest()->getRequestFormat();
$content = ['message' => $event->getException()->getMessage()];
$encoded_content = $this->serializer->serialize($content, $format);
$response = new Response($encoded_content, $status);
$response = new Response($encoded_content, $exception->getStatusCode());
$event->setResponse($response);
}