diff --git a/README.md b/README.md index ab01a11b8a4b4d980dc99ef8dfc1fc23c2a6694b..9ab02e0b4727e0ba73176afa6dc7f63458e7f537 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,12 @@ # JSON API The jsonapi module exposes a [JSON API](http://jsonapi.org/) implementation for data stored in Drupal. +The JSON API specification supports [_extensions_](http://jsonapi.org/extensions/). The following extensions are +supported in this JSON API implementation: + +1. [Partial Success](https://gist.github.com/e0ipso/732712c3e573a6af1d83b25b9f0269c8), for when a resource collection is retrieved and only a subset of resources is accessible. +2. [Fancy Filters](https://gist.github.com/e0ipso/efcc4e96ca2aed58e32948e4f70c2460), to specify the filter strategy exposed by this module. + ## Installation Install the module as every other module. diff --git a/jsonapi.services.yml b/jsonapi.services.yml index c111a8e265615085f0c57b251ae9319845d39fd8..3a97096ea832af5e2285c249e5940ff61d9e1119 100644 --- a/jsonapi.services.yml +++ b/jsonapi.services.yml @@ -94,8 +94,6 @@ services: # Priority 10, to ensure it runs before @paramconverter.entity. - { name: paramconverter, priority: 10 } arguments: ['@entity.manager'] - jsonapi.error_handler: - class: Drupal\jsonapi\Error\ErrorHandler jsonapi.exception_subscriber: class: Drupal\jsonapi\EventSubscriber\DefaultExceptionSubscriber tags: diff --git a/src/Context/FieldResolver.php b/src/Context/FieldResolver.php index 9eab90d8d220dab702c481f1114bc19d90139c11..b0e386c6f2c7dde7010b22e85d287946f2970af9 100644 --- a/src/Context/FieldResolver.php +++ b/src/Context/FieldResolver.php @@ -3,7 +3,7 @@ namespace Drupal\jsonapi\Context; use Drupal\Core\Entity\EntityFieldManagerInterface; -use Drupal\jsonapi\Exception\SerializableHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; /** * Service which resolves public field names to and from Drupal field names. @@ -70,7 +70,7 @@ class FieldResolver { */ public function resolveInternal($external_field_name) { if (empty($external_field_name)) { - throw new SerializableHttpException(400, 'No field name was provided for the filter.'); + throw new BadRequestHttpException('No field name was provided for the filter.'); } // Right now we are exposing all the fields with the name they have in // the Drupal backend. But this may change in the future. @@ -85,10 +85,10 @@ class FieldResolver { $reference_breadcrumbs = []; while ($field_name = array_shift($parts)) { if (!$definitions = $this->fieldManager->getFieldStorageDefinitions($entity_type_id)) { - throw new SerializableHttpException(400, sprintf('Invalid nested filtering. There is no entity type "%s".', $entity_type_id)); + throw new BadRequestHttpException(sprintf('Invalid nested filtering. There is no entity type "%s".', $entity_type_id)); } if (empty($definitions[$field_name])) { - throw new SerializableHttpException(400, sprintf('Invalid nested filtering. Invalid entity reference "%s".', $field_name)); + throw new BadRequestHttpException(sprintf('Invalid nested filtering. Invalid entity reference "%s".', $field_name)); } array_push($reference_breadcrumbs, $field_name); // Update the entity type with the referenced type. diff --git a/src/Controller/EntityResource.php b/src/Controller/EntityResource.php index 363a6223dd4654d81e31acbce95c3935b0dd9fca..dfd7feea3d3ef96a4840c50f3cee0a171812281f 100644 --- a/src/Controller/EntityResource.php +++ b/src/Controller/EntityResource.php @@ -20,7 +20,6 @@ use Drupal\jsonapi\Exception\EntityAccessDeniedHttpException; use Drupal\jsonapi\Resource\EntityCollection; use Drupal\jsonapi\Resource\JsonApiDocumentTopLevel; use Drupal\jsonapi\ResourceType\ResourceType; -use Drupal\jsonapi\Exception\SerializableHttpException; use Drupal\jsonapi\Exception\UnprocessableHttpEntityException; use Drupal\jsonapi\Query\QueryBuilder; use Drupal\jsonapi\Context\CurrentContext; @@ -29,6 +28,11 @@ use Drupal\jsonapi\Routing\Param\JsonApiParamBase; use Drupal\jsonapi\Routing\Param\OffsetPage; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\ConflictHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; /** * @see \Drupal\jsonapi\Controller\RequestHandler @@ -131,7 +135,7 @@ class EntityResource { * @param \Drupal\Core\Entity\EntityInterface $entity * The entity object. * - * @throws \Drupal\jsonapi\Exception\SerializableHttpException + * @throws \Drupal\jsonapi\Exception\EntityAccessDeniedHttpException * If validation errors are found. */ protected function validate(EntityInterface $entity) { @@ -199,7 +203,7 @@ class EntityResource { $body = Json::decode($request->getContent()); $data = $body['data']; if ($data['id'] != $entity->uuid()) { - throw new SerializableHttpException(400, sprintf( + throw new BadRequestHttpException(sprintf( 'The selected entity (%s) does not match the ID in the payload (%s).', $entity->uuid(), $data['id'] @@ -296,7 +300,7 @@ class EntityResource { public function getRelated(EntityInterface $entity, $related_field, Request $request) { /* @var $field_list \Drupal\Core\Field\FieldItemListInterface */ if (!($field_list = $entity->get($related_field)) || !$this->isRelationshipField($field_list)) { - throw new SerializableHttpException(404, sprintf('The relationship %s is not present in this resource.', $related_field)); + throw new NotFoundHttpException(sprintf('The relationship %s is not present in this resource.', $related_field)); } $is_multiple = $field_list ->getDataDefinition() @@ -346,7 +350,7 @@ class EntityResource { */ public function getRelationship(EntityInterface $entity, $related_field, Request $request, $response_code = 200) { if (!($field_list = $entity->get($related_field)) || !$this->isRelationshipField($field_list)) { - throw new SerializableHttpException(404, sprintf('The relationship %s is not present in this resource.', $related_field)); + throw new NotFoundHttpException(sprintf('The relationship %s is not present in this resource.', $related_field)); } $response = $this->buildWrappedResponse($field_list, $response_code); return $response; @@ -384,7 +388,7 @@ class EntityResource { ->getFieldStorageDefinition() ->isMultiple(); if (!$is_multiple) { - throw new SerializableHttpException(409, sprintf('You can only POST to to-many relationships. %s is a to-one relationship.', $related_field)); + throw new ConflictHttpException(sprintf('You can only POST to to-many relationships. %s is a to-one relationship.', $related_field)); } $field_access = $field_list->access('edit', NULL, TRUE); @@ -450,7 +454,7 @@ class EntityResource { */ protected function doPatchIndividualRelationship(EntityInterface $entity, EntityReferenceFieldItemListInterface $parsed_field_list) { if ($parsed_field_list->count() > 1) { - throw new SerializableHttpException(400, sprintf('Provide a single relationship so to-one relationship fields (%s).', $parsed_field_list->getName())); + throw new BadRequestHttpException(sprintf('Provide a single relationship so to-one relationship fields (%s).', $parsed_field_list->getName())); } $this->doPatchMultipleRelationship($entity, $parsed_field_list); } @@ -497,7 +501,7 @@ class EntityResource { } if ($parsed_field_list instanceof Request) { // This usually means that there was not body provided. - throw new SerializableHttpException(400, sprintf('You need to provide a body for DELETE operations on a relationship (%s).', $related_field)); + throw new BadRequestHttpException(sprintf('You need to provide a body for DELETE operations on a relationship (%s).', $related_field)); } /* @var \Drupal\Core\Field\EntityReferenceFieldItemListInterface $parsed_field_list */ $this->relationshipAccess($entity, $related_field); @@ -513,7 +517,7 @@ class EntityResource { ->getFieldStorageDefinition() ->isMultiple(); if (!$is_multiple) { - throw new SerializableHttpException(409, sprintf('You can only DELETE from to-many relationships. %s is a to-one relationship.', $related_field)); + throw new ConflictHttpException(sprintf('You can only DELETE from to-many relationships. %s is a to-one relationship.', $related_field)); } // Compute the list of current values and remove the ones in the payload. @@ -631,7 +635,7 @@ class EntityResource { throw new EntityAccessDeniedHttpException($entity, $entity_access, $related_field, 'The current user is not allowed to update the selected resource.'); } if (!($field_list = $entity->get($related_field)) || !$this->isRelationshipField($field_list)) { - throw new SerializableHttpException(404, sprintf('The relationship %s is not present in this resource.', $related_field)); + throw new NotFoundHttpException(sprintf('The relationship %s is not present in this resource.', $related_field)); } } @@ -653,7 +657,7 @@ class EntityResource { $destination_field_list = $destination->get($field_name); } catch (\Exception $e) { - throw new SerializableHttpException(400, sprintf('The provided field (%s) does not exist in the entity with ID %s.', $field_name, $destination->uuid())); + throw new BadRequestHttpException(sprintf('The provided field (%s) does not exist in the entity with ID %s.', $field_name, $destination->uuid())); } $origin_field_list = $origin->get($field_name); @@ -670,7 +674,7 @@ class EntityResource { $destination->set($field_name, $origin->get($field_name)); } else { - throw new SerializableHttpException(400, 'The serialized entity and the destination entity are of different types.'); + throw new BadRequestHttpException('The serialized entity and the destination entity are of different types.'); } } diff --git a/src/Controller/RequestHandler.php b/src/Controller/RequestHandler.php index 4c4fb5a665a812f942e7953adc685a37bb9623b5..43d767f7ebfd37e09cb82e430a4bb8526ce40cf5 100644 --- a/src/Controller/RequestHandler.php +++ b/src/Controller/RequestHandler.php @@ -7,14 +7,13 @@ use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Render\RenderContext; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\jsonapi\Context\CurrentContext; -use Drupal\jsonapi\Error\ErrorHandler; -use Drupal\jsonapi\Exception\SerializableHttpException; use Drupal\jsonapi\ResourceResponse; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; use Symfony\Component\Routing\Route; use Symfony\Component\Serializer\Exception\UnexpectedValueException; use Symfony\Component\Serializer\SerializerInterface; @@ -85,9 +84,6 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter // Only add the unserialized data if there is something there. $extra_parameters = $unserialized ? [$unserialized, $request] : [$request]; - /** @var \Drupal\jsonapi\Error\ErrorHandler $error_handler */ - $error_handler = $this->container->get('jsonapi.error_handler'); - $error_handler->register(); // Execute the request in context so the cacheable metadata from the entity // grants system is caught and added to the response. This is surfaced when // executing the underlying entity query. @@ -100,9 +96,8 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter if (!$context->isEmpty()) { $response->addCacheableDependency($context->pop()); } - $error_handler->restore(); - return $this->renderJsonApiResponse($request, $response, $serializer, $format, $error_handler); + return $this->renderJsonApiResponse($request, $response, $serializer, $format); } /** @@ -122,13 +117,11 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter * The serializer to use. * @param string $format * The response format. - * @param \Drupal\jsonapi\Error\ErrorHandler $error_handler - * The error handler service. * * @return \Drupal\Core\Cache\CacheableResponseInterface * The altered response. */ - protected function renderJsonApiResponse(Request $request, ResourceResponse $response, SerializerInterface $serializer, $format, ErrorHandler $error_handler) { + protected function renderJsonApiResponse(Request $request, ResourceResponse $response, SerializerInterface $serializer, $format) { $data = $response->getResponseData(); $context = new RenderContext(); @@ -139,8 +132,6 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter // HTML generation. $cacheable_metadata->addCacheContexts(static::$requiredCacheContexts); - // Make sure that any PHP error is surfaced as a serializable exception. - $error_handler->register(); $output = $this->container->get('renderer') ->executeInRenderContext($context, function () use ( $serializer, @@ -155,7 +146,6 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter // updating the response object's cacheability. return $serializer->serialize($data, $format, ['request' => $request, 'cacheable_metadata' => $cacheable_metadata]); }); - $error_handler->restore(); $response->setContent($output); if (!$context->isEmpty()) { $response->addCacheableDependency($context->pop()); @@ -200,8 +190,7 @@ class RequestHandler implements ContainerAwareInterface, ContainerInjectionInter ]); } catch (UnexpectedValueException $e) { - throw new SerializableHttpException( - 422, + throw new UnprocessableEntityHttpException( sprintf('There was an error un-serializing the data. Message: %s.', $e->getMessage()), $e ); diff --git a/src/Error/ErrorHandler.php b/src/Error/ErrorHandler.php deleted file mode 100644 index a51f6466b0c1449d00fa0de1e22f8c6f1089ff87..0000000000000000000000000000000000000000 --- a/src/Error/ErrorHandler.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php - -namespace Drupal\jsonapi\Error; - -/** - * @see http://jsonapi.org/format/#errors - * - * @see \Drupal\jsonapi\Controller\RequestHandler::renderJsonApiResponse - * @internal - */ -class ErrorHandler { - - /** - * Register the handler. - */ - public function register() { - set_error_handler(get_called_class() . '::handle'); - } - - /** - * Go back to normal and restore the previous error handler. - */ - public function restore() { - restore_error_handler(); - } - - /** - * Handle the PHP error with custom business logic. - * - * @param $error_level - * The level of the error raised. - * @param $message - * The error message. - * @param $filename - * The filename that the error was raised in. - * @param $line - * The line number the error was raised at. - * @param $context - * An array that points to the active symbol table at the point the error - * occurred. - */ - public static function handle($error_level, $message, $filename, $line, $context) { - $message = 'Unexpected PHP error: ' . $message; - _drupal_error_handler($error_level, $message, $filename, $line, $context); - $types = drupal_error_levels(); - list($severity_msg, $severity_level) = $types[$error_level]; - // Only halt execution if the error is more severe than a warning. - if ($severity_level < 4) { - throw new SerializableHttpException(500, sprintf('[%s] %s', $severity_msg, $message), NULL, [], $error_level); - } - } - -} diff --git a/src/EventSubscriber/DefaultExceptionSubscriber.php b/src/EventSubscriber/DefaultExceptionSubscriber.php index 1d2f71a63617d7d9fd21a7d00282aa48cc3c21a3..ea87b351aba03e98ccd11fe6f0d185a4e685507c 100644 --- a/src/EventSubscriber/DefaultExceptionSubscriber.php +++ b/src/EventSubscriber/DefaultExceptionSubscriber.php @@ -2,7 +2,6 @@ namespace Drupal\jsonapi\EventSubscriber; -use Drupal\jsonapi\Exception\SerializableHttpException; use Drupal\serialization\EventSubscriber\DefaultExceptionSubscriber as SerializationDefaultExceptionSubscriber; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; @@ -38,7 +37,7 @@ class DefaultExceptionSubscriber extends SerializationDefaultExceptionSubscriber return; } if (!$exception instanceof HttpException) { - $exception = new SerializableHttpException(500, $exception->getMessage(), $exception); + $exception = new HttpException(500, $exception->getMessage(), $exception); $event->setException($exception); } diff --git a/src/Exception/EntityAccessDeniedHttpException.php b/src/Exception/EntityAccessDeniedHttpException.php index 4db9698b3bf12448d8f861a06031ac67b89769a3..c37d0c6e3a5474849f742ea2f257a90767f91a6b 100644 --- a/src/Exception/EntityAccessDeniedHttpException.php +++ b/src/Exception/EntityAccessDeniedHttpException.php @@ -4,9 +4,13 @@ namespace Drupal\jsonapi\Exception; use Drupal\Core\Access\AccessResultInterface; use Drupal\Core\Access\AccessResultReasonInterface; +use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Entity\EntityInterface; +use Symfony\Component\HttpKernel\Exception\HttpException; -class EntityAccessDeniedHttpException extends SerializableHttpException { +class EntityAccessDeniedHttpException extends HttpException { + + use DependencySerializationTrait; /** * The error which caused the 403. diff --git a/src/Exception/SerializableHttpException.php b/src/Exception/SerializableHttpException.php deleted file mode 100644 index 3aaa117e60066d84ebb96081817fabe660ae6419..0000000000000000000000000000000000000000 --- a/src/Exception/SerializableHttpException.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -namespace Drupal\jsonapi\Exception; - -use Drupal\Core\DependencyInjection\DependencySerializationTrait; -use Symfony\Component\HttpKernel\Exception\HttpException; - -/** - * @internal - */ -class SerializableHttpException extends HttpException { - - use DependencySerializationTrait; - -} diff --git a/src/Exception/UnprocessableHttpEntityException.php b/src/Exception/UnprocessableHttpEntityException.php index 7730f5f12a2bd05e8c4d461a18554bb5082193b4..e0953c80664a23618318d6fcb6a10647c7738704 100644 --- a/src/Exception/UnprocessableHttpEntityException.php +++ b/src/Exception/UnprocessableHttpEntityException.php @@ -3,11 +3,15 @@ namespace Drupal\jsonapi\Exception; use Drupal\Core\Entity\EntityConstraintViolationListInterface; +use Drupal\Core\DependencyInjection\DependencySerializationTrait; +use Symfony\Component\HttpKernel\Exception\HttpException; /** * @internal */ -class UnprocessableHttpEntityException extends SerializableHttpException { +class UnprocessableHttpEntityException extends HttpException { + + use DependencySerializationTrait; /** * The constraint violations associated with this exception. diff --git a/src/LinkManager/LinkManager.php b/src/LinkManager/LinkManager.php index b3f6d03fa170850cd01572c30199267a2929ecdc..2ea7bd1383da97541bf083b4a88723d62eadab31 100644 --- a/src/LinkManager/LinkManager.php +++ b/src/LinkManager/LinkManager.php @@ -4,10 +4,10 @@ namespace Drupal\jsonapi\LinkManager; use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\jsonapi\ResourceType\ResourceType; -use Drupal\jsonapi\Exception\SerializableHttpException; use Drupal\jsonapi\Routing\Param\OffsetPage; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\Routing\Matcher\RequestMatcherInterface; /** @@ -97,7 +97,7 @@ class LinkManager { * @param array $link_context * An associative array with extra data to build the links. * - * @throws \Drupal\jsonapi\Exception\SerializableHttpException + * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException * When the offset and size are invalid. * * @return string[] @@ -118,7 +118,7 @@ class LinkManager { $size = OffsetPage::$maxSize; } if ($size <= 0) { - throw new SerializableHttpException(400, sprintf('The page size needs to be a positive integer.')); + throw new BadRequestHttpException(sprintf('The page size needs to be a positive integer.')); } $query = (array) $request->query->getIterator(); $links = []; diff --git a/src/Normalizer/EntityNormalizer.php b/src/Normalizer/EntityNormalizer.php index 7958248e8cbe1f1d0f47facaa51d9a2d712bc6e8..de76f477cddcf503f65253707de7bb835e0f633e 100644 --- a/src/Normalizer/EntityNormalizer.php +++ b/src/Normalizer/EntityNormalizer.php @@ -9,10 +9,10 @@ use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\EntityReferenceFieldItemList; use Drupal\jsonapi\Normalizer\Value\EntityNormalizerValue; use Drupal\jsonapi\ResourceType\ResourceType; -use Drupal\jsonapi\Exception\SerializableHttpException; use Drupal\jsonapi\LinkManager\LinkManager; use Drupal\jsonapi\Normalizer\Value\NullFieldNormalizerValue; use Drupal\jsonapi\ResourceType\ResourceTypeRepository; +use Symfony\Component\HttpKernel\Exception\PreconditionFailedHttpException; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; /** @@ -131,7 +131,7 @@ class EntityNormalizer extends NormalizerBase implements DenormalizerInterface { */ public function denormalize($data, $class, $format = NULL, array $context = array()) { if (empty($context['resource_type']) || !$context['resource_type'] instanceof ResourceType) { - throw new SerializableHttpException(412, 'Missing context during denormalization.'); + throw new PreconditionFailedHttpException('Missing context during denormalization.'); } /* @var \Drupal\jsonapi\ResourceType\ResourceType $resource_type */ $resource_type = $context['resource_type']; diff --git a/src/Normalizer/EntityReferenceFieldNormalizer.php b/src/Normalizer/EntityReferenceFieldNormalizer.php index 2b21527c12edf94616714392c8311f1849f958a1..024cf6c22981782df3ca9375ed9b5747355505a5 100644 --- a/src/Normalizer/EntityReferenceFieldNormalizer.php +++ b/src/Normalizer/EntityReferenceFieldNormalizer.php @@ -9,9 +9,9 @@ use Drupal\Core\Field\FieldTypePluginManagerInterface; use Drupal\Core\Field\TypedData\FieldItemDataDefinition; use Drupal\jsonapi\ResourceType\ResourceTypeRepository; use Drupal\jsonapi\Resource\EntityCollection; -use Drupal\jsonapi\Exception\SerializableHttpException; use Drupal\jsonapi\LinkManager\LinkManager; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; /** * Normalizer class specific for entity reference field objects. @@ -112,7 +112,7 @@ class EntityReferenceFieldNormalizer extends FieldNormalizer implements Denormal $resource_type->getBundle() ); if (empty($context['related']) || empty($field_definitions[$context['related']])) { - throw new SerializableHttpException(400, 'Invalid or missing related field.'); + throw new BadRequestHttpException('Invalid or missing related field.'); } /* @var \Drupal\field\Entity\FieldConfig $field_definition */ $field_definition = $field_definitions[$context['related']]; @@ -127,7 +127,7 @@ class EntityReferenceFieldNormalizer extends FieldNormalizer implements Denormal // Make sure that the provided type is compatible with the targeted // resource. if (!in_array($value['type'], $target_resources)) { - throw new SerializableHttpException(400, sprintf( + throw new BadRequestHttpException(sprintf( 'The provided type (%s) does not mach the destination resource types (%s).', $value['type'], implode(', ', $target_resources) @@ -159,14 +159,14 @@ class EntityReferenceFieldNormalizer extends FieldNormalizer implements Denormal protected function massageRelationshipInput($data, $is_multiple) { if ($is_multiple) { if (!is_array($data['data'])) { - throw new SerializableHttpException(400, 'Invalid body payload for the relationship.'); + throw new BadRequestHttpException('Invalid body payload for the relationship.'); } // Leave the invalid elements. $invalid_elements = array_filter($data['data'], function ($element) { return empty($element['type']) || empty($element['id']); }); if ($invalid_elements) { - throw new SerializableHttpException(400, 'Invalid body payload for the relationship.'); + throw new BadRequestHttpException('Invalid body payload for the relationship.'); } } else { @@ -175,7 +175,7 @@ class EntityReferenceFieldNormalizer extends FieldNormalizer implements Denormal return ['data' => []]; } if (empty($data['data']['type']) || empty($data['data']['id'])) { - throw new SerializableHttpException(400, 'Invalid body payload for the relationship.'); + throw new BadRequestHttpException('Invalid body payload for the relationship.'); } $data['data'] = [$data['data']]; } diff --git a/src/Normalizer/HttpExceptionNormalizer.php b/src/Normalizer/HttpExceptionNormalizer.php index 529f8623c9a7f87bc5f0ea4f216c4f6f7e89425b..2468c1ce7a706784ea99ea001aad05961a51fb8a 100644 --- a/src/Normalizer/HttpExceptionNormalizer.php +++ b/src/Normalizer/HttpExceptionNormalizer.php @@ -6,7 +6,6 @@ use Drupal\Core\Field\FieldStorageDefinitionInterface; use Drupal\Core\Session\AccountProxyInterface; use Drupal\jsonapi\Normalizer\Value\FieldItemNormalizerValue; use Drupal\jsonapi\Normalizer\Value\HttpExceptionNormalizerValue; -use Drupal\serialization\Normalizer\NormalizerBase as SerializationNormalizerBase; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -16,7 +15,7 @@ use Symfony\Component\HttpKernel\Exception\HttpException; * * @see http://jsonapi.org/format/#error-objects */ -class HttpExceptionNormalizer extends SerializationNormalizerBase { +class HttpExceptionNormalizer extends NormalizerBase { /** * The interface or class that this Normalizer supports. diff --git a/src/Query/QueryBuilder.php b/src/Query/QueryBuilder.php index 0e84c0388c2ea981311cecb76eb4c1cde7d885b4..252cbd2e41b8a6266fcae3fde47f929a399b2398 100644 --- a/src/Query/QueryBuilder.php +++ b/src/Query/QueryBuilder.php @@ -4,7 +4,6 @@ namespace Drupal\jsonapi\Query; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\jsonapi\Exception\SerializableHttpException; use Drupal\jsonapi\Routing\Param\OffsetPage; use Drupal\jsonapi\Routing\Param\Filter; use Drupal\jsonapi\Routing\Param\JsonApiParamInterface; @@ -169,8 +168,7 @@ class QueryBuilder { break; default: - throw new SerializableHttpException( - 400, + throw new BadRequestHttpException( sprintf('Invalid syntax in the filter parameter: %s.', $filter_index) ); }; diff --git a/src/ResourceResponse.php b/src/ResourceResponse.php index 942d0657cdc202dda9acb735b78657fc4d305f88..6cd61a2475ad5a0ebb4741690aa6eb3727661a9f 100644 --- a/src/ResourceResponse.php +++ b/src/ResourceResponse.php @@ -54,4 +54,10 @@ class ResourceResponse extends Response implements CacheableResponseInterface { return $this->responseData; } + // @todo Remove this in https://www.drupal.org/node/2855693 + public function __sleep() { + $this->responseData = NULL; + return array_keys(get_object_vars($this)); + } + } diff --git a/src/Routing/Param/Filter.php b/src/Routing/Param/Filter.php index 2033473229dc88c3c4af503c78fed5bcfd5f9b2a..4a789d5107d02a4626271f2ef349353eced49ffa 100644 --- a/src/Routing/Param/Filter.php +++ b/src/Routing/Param/Filter.php @@ -1,7 +1,8 @@ <?php namespace Drupal\jsonapi\Routing\Param; -use Drupal\jsonapi\Exception\SerializableHttpException; + +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; /** * @internal @@ -68,7 +69,7 @@ class Filter extends JsonApiParamBase { protected function expand() { // We should always get an array for the filter. if (!is_array($this->original)) { - throw new SerializableHttpException(400, 'Incorrect value passed to the filter parameter.'); + throw new BadRequestHttpException('Incorrect value passed to the filter parameter.'); } $expanded = []; diff --git a/src/Routing/Param/OffsetPage.php b/src/Routing/Param/OffsetPage.php index e2b3feb5ef9ea52ec4b5695cf6c943b415ba0a3c..238d5fd3d1955531015bfb9570368955cb48010d 100644 --- a/src/Routing/Param/OffsetPage.php +++ b/src/Routing/Param/OffsetPage.php @@ -1,7 +1,8 @@ <?php namespace Drupal\jsonapi\Routing\Param; -use Drupal\jsonapi\Exception\SerializableHttpException; + +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; /** * @internal @@ -41,7 +42,7 @@ class OffsetPage extends JsonApiParamBase { */ protected function expand() { if (!is_array($this->original)) { - throw new SerializableHttpException(400, 'The page parameter needs to be an array.'); + throw new BadRequestHttpException('The page parameter needs to be an array.'); } $output = $this->original + ['limit' => static::$maxSize]; $output['limit'] = $output['limit'] > static::$maxSize ? diff --git a/src/Routing/Param/Sort.php b/src/Routing/Param/Sort.php index e0aac1030cc4d25dbea7fb08d26f7edf860c4a83..eee57d9c40a1134135919d7897640828167d5ca3 100644 --- a/src/Routing/Param/Sort.php +++ b/src/Routing/Param/Sort.php @@ -2,7 +2,7 @@ namespace Drupal\jsonapi\Routing\Param; -use Drupal\jsonapi\Exception\SerializableHttpException; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; /** * @internal @@ -48,7 +48,7 @@ class Sort extends JsonApiParamBase { $sort = $this->original; if (empty($sort)) { - throw new SerializableHttpException(400, 'You need to provide a value for the sort parameter.'); + throw new BadRequestHttpException('You need to provide a value for the sort parameter.'); } // Expand a JSON API compliant sort into a more expressive sort parameter. @@ -109,7 +109,7 @@ class Sort extends JsonApiParamBase { ]; if (!isset($sort_item[static::FIELD_KEY])) { - throw new SerializableHttpException(400, 'You need to provide a field name for the sort parameter.'); + throw new BadRequestHttpException('You need to provide a field name for the sort parameter.'); } $expected_keys = [ @@ -122,7 +122,7 @@ class Sort extends JsonApiParamBase { // Verify correct sort keys. if (count(array_diff($expected_keys, array_keys($expanded))) > 0) { - throw new SerializableHttpException(400, 'You have provided an invalid set of sort keys.'); + throw new BadRequestHttpException('You have provided an invalid set of sort keys.'); } return $expanded; diff --git a/src/Routing/RouteEnhancer.php b/src/Routing/RouteEnhancer.php index 9ee019dffe3141bf77b79fe5bb4b07befe6273a9..253960295863ead8d02ecf23d65f97dccf5e68d0 100644 --- a/src/Routing/RouteEnhancer.php +++ b/src/Routing/RouteEnhancer.php @@ -3,9 +3,9 @@ namespace Drupal\jsonapi\Routing; use Drupal\Core\Routing\Enhancer\RouteEnhancerInterface; -use Drupal\jsonapi\Exception\SerializableHttpException; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Route; /** @@ -35,7 +35,7 @@ class RouteEnhancer implements RouteEnhancerInterface { // If the bundle in the loaded entity does not match the bundle in the // route (which is set based on the corresponding ResourceType), then // throw an exception. - throw new SerializableHttpException(404, sprintf('The loaded entity bundle (%s) does not match the configured resource (%s).', $retrieved_bundle, $configured_bundle)); + throw new NotFoundHttpException(sprintf('The loaded entity bundle (%s) does not match the configured resource (%s).', $retrieved_bundle, $configured_bundle)); } return $defaults; } diff --git a/tests/src/Kernel/Controller/EntityResourceTest.php b/tests/src/Kernel/Controller/EntityResourceTest.php index d37dfd366fc665a2f3c466fa3e3b5117af32b7f6..3fe187653e13ff55da9598d32901a2b6c8e6fd41 100644 --- a/tests/src/Kernel/Controller/EntityResourceTest.php +++ b/tests/src/Kernel/Controller/EntityResourceTest.php @@ -166,7 +166,7 @@ class EntityResourceTest extends JsonapiKernelTestBase { /** * @covers ::getIndividual - * @expectedException \Drupal\jsonapi\Exception\SerializableHttpException + * @expectedException \Drupal\jsonapi\Exception\EntityAccessDeniedHttpException */ public function testGetIndividualDenied() { $role = Role::load(RoleInterface::ANONYMOUS_ID); diff --git a/tests/src/Unit/Context/FieldResolverTest.php b/tests/src/Unit/Context/FieldResolverTest.php index 0fe7e6c1969ad7beabbe57caedf62f8cd386bd0d..488ecea18e0558120b76501616500d54abe405dc 100644 --- a/tests/src/Unit/Context/FieldResolverTest.php +++ b/tests/src/Unit/Context/FieldResolverTest.php @@ -97,7 +97,7 @@ class FieldResolverTest extends UnitTestCase { * * @covers ::resolveInternal * - * @expectedException \Drupal\jsonapi\Exception\SerializableHttpException + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException */ public function testResolveInternalError() { $field_manager = $this->prophesize(EntityFieldManagerInterface::class); diff --git a/tests/src/Unit/LinkManager/LinkManagerTest.php b/tests/src/Unit/LinkManager/LinkManagerTest.php index 996e59c90c4f6be6e8f0dfe0313312b33b54d01d..eef33fab0440caada4412bafa69c15802b8449c3 100644 --- a/tests/src/Unit/LinkManager/LinkManagerTest.php +++ b/tests/src/Unit/LinkManager/LinkManagerTest.php @@ -120,7 +120,7 @@ class LinkManagerTest extends UnitTestCase { * Test errors. * * @covers ::getPagerLinks - * @expectedException \Drupal\jsonapi\Exception\SerializableHttpException + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException * @dataProvider getPagerLinksErrorProvider */ public function testGetPagerLinksError($offset, $size, $total, array $pages) { diff --git a/tests/src/Unit/Normalizer/EntityReferenceFieldNormalizerTest.php b/tests/src/Unit/Normalizer/EntityReferenceFieldNormalizerTest.php index 2f27c5d579f5d97ad3f9ec12933296c92c2c7c5c..4bf2490d9a128cbcb8708bf75e6f5cbeba0c5c1e 100644 --- a/tests/src/Unit/Normalizer/EntityReferenceFieldNormalizerTest.php +++ b/tests/src/Unit/Normalizer/EntityReferenceFieldNormalizerTest.php @@ -130,7 +130,7 @@ class EntityReferenceFieldNormalizerTest extends UnitTestCase { /** * @covers ::denormalize - * @expectedException \Drupal\jsonapi\Exception\SerializableHttpException + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException * @dataProvider denormalizeInvalidResourceProvider */ public function testDenormalizeInvalidResource($data, $field_name) { diff --git a/tests/src/Unit/Routing/Param/FilterTest.php b/tests/src/Unit/Routing/Param/FilterTest.php index 15b19283df9419c4f292aa696809a39bd3e5ae7e..176371170f0ef4bd5b20f5601861224d21d0ffb8 100644 --- a/tests/src/Unit/Routing/Param/FilterTest.php +++ b/tests/src/Unit/Routing/Param/FilterTest.php @@ -87,7 +87,7 @@ class FilterTest extends UnitTestCase { /** * @covers ::get - * @expectedException \Drupal\jsonapi\Exception\SerializableHttpException + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException */ public function testGetFail() { $pager = new Filter( diff --git a/tests/src/Unit/Routing/Param/OffsetPageTest.php b/tests/src/Unit/Routing/Param/OffsetPageTest.php index c23e5e05fe9b94971f8909fde9558a669f452a5c..c4afea57d89c501e11e4558fface4a8263abfceb 100644 --- a/tests/src/Unit/Routing/Param/OffsetPageTest.php +++ b/tests/src/Unit/Routing/Param/OffsetPageTest.php @@ -35,7 +35,7 @@ class OffsetPageTest extends UnitTestCase { /** * @covers ::get - * @expectedException \Drupal\jsonapi\Exception\SerializableHttpException + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException */ public function testGetFail() { $pager = new OffsetPage('lorem'); diff --git a/tests/src/Unit/Routing/Param/SortTest.php b/tests/src/Unit/Routing/Param/SortTest.php index a407ea5ab8309f1b698c376518af1d97439a1542..171f62c1e80bd3185aa0a642475dfb1f1407ce15 100644 --- a/tests/src/Unit/Routing/Param/SortTest.php +++ b/tests/src/Unit/Routing/Param/SortTest.php @@ -52,7 +52,7 @@ class SortTest extends UnitTestCase { /** * @covers ::get * @dataProvider getFailProvider - * @expectedException \Drupal\jsonapi\Exception\SerializableHttpException + * @expectedException \Symfony\Component\HttpKernel\Exception\BadRequestHttpException */ public function testGetFail($input) { $sort = new Sort($input);