Skip to content

Add Exception configuration documentation #186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions core/errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Error Handling

API Platform comes with a powerful error system. It handles excepted (such as faulty JSON documents sent by the
client or validation errors) as well as unexpected errors (PHP exceptions and errors).
API Platform automatically send the appropriate HTTP status code to the client: `400` for expected errors, `500` for
unexpected ones. It also provides a description of the respecting [the Hydra specification](http://www.hydra-cg.com/spec/latest/core/#description-of-http-status-codes-and-errors)
or the [RFC 7807](https://tools.ietf.org/html/rfc7807) depending of the format selected during the [content negotiation](content-negotiation.md).

## Converting PHP Exceptions to HTTP Errors

The framework also allows to configure the HTTP status code sent to the clients when custom exceptions are thrown.

In the following example, we will throw explain to throw a domain exception from the business layer of the application and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[...] we will throw explain to throw a domain exception [...]

configure API Platform to convert it to a `404 Not Found` error. Let's create a this domain exception and the service throwing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[...] Let's create a this domain exception [...]

it:

```php
<?php
// src/AppBundle/Exception/ProductNotFoundException.php

namespace AppBundle\Exception;

final class ProductNotFoundException extends \Exception
{
}
```

```php
<?php
// src/AppBundle/EventSubscriber/CartManager.php

namespace AppBundle\EventSubscriber;

use ApiPlatform\Core\EventListener\EventPriorities;
use AppBundle\Entity\Product;
use AppBundle\Exception\ProductNotFoundException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\KernelEvents;

final class ProductManager implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['checkProductAvailability', EventPriorities::POST_DESERIALIZE],
];
}

public function checkProductAvailability(GetResponseForControllerResultEvent $event): void
{
$product = $event->getControllerResult();
if (!$product instanceof Product || !$event->getRequest()->isMethodSafe(false)) {
return;
}

if (!$product->isPubliclyAvailable()) {
// Using internal codes for a better understanding of what's going on
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally there isn't internal code used here... Maybe remove the comment ?

throw new ProductNotFoundException(sprintf('The product "%s" does not exist.', $product->getId()));
}
}
}
```

If you use the standard distribution of API Platform, the event listener will be automatically registered. If you use a
custom installation, [learn how to register listeners](events.md).

Then, configure the framework to catch `AppBundle\Exception\ProductNotFoundException` exceptions and convert them in `404`
errors:

```yaml
# app/config/config.yml
api_platform:
# ...
exception_to_status:
# The 2 following handlers are registered by default, keep those lines to prevent unexpected side effects
Symfony\Component\Serializer\Exception\ExceptionInterface: 400 # Use a raw status code (recommended)
ApiPlatform\Core\Exception\InvalidArgumentException: 'HTTP_BAD_REQUEST' # Or a `Symfony\Component\HttpFoundation\Response`'s constant

AppBundle\Exception\ProductNotFoundException: 404 # Here is the handler for our custom exception
```

Any type of `Exception` can be thrown, API Platform will convert it to a Symfony's `HttpException`. The framework also takes
care to serialize the error description according to the request format. For instance, if the API should respond in JSON-LD,
the error will be returned in this format as well:

`GET /products/1234`

```json
{
"@context": "/contexts/Error",
"@type": "Error",
"hydra:title": "An error occurred",
"hydra:description": "The product \"1234\" does not exist."
}
```

Previous chapter: [Pagination](pagination.md)

Next chapter: [The Event System](events.md)
2 changes: 1 addition & 1 deletion core/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,6 @@ Constant | Event | Priority |
`PRE_RESPOND` | `kernel.view` | 9 |
`POST_RESPOND` | `kernel.response` | 0 |

Previous chapter: [Pagination](pagination.md)
Previous chapter: [Error Handling](errors.md)

Next chapter: [Content Negotiation](content-negotiation.md)
2 changes: 1 addition & 1 deletion core/pagination.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,4 @@ class Book

Previous chapter: [Validation](validation.md)

Next chapter: [The Event System](events.md)
Next chapter: [Error Handling](errors.md)
130 changes: 125 additions & 5 deletions core/validation.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,134 @@
# Validation

API Platform Core uses the [Symfony Validator component](http://symfony.com/doc/current/book/validation.html) to validate
entities.
API Platform take care of validating data sent to the API by the client (usually user data entered through forms).
By default, the framework relies on [the powerful Symfony Validator Component](http://symfony.com/doc/current/validation.html)
for this task, but you can replace it by your preferred validation library such as [the PHP filter extension](http://php.net/manual/en/intro.filter.php)
if you want to.

Without specific configuration, it uses the default validation group, but this behavior is customizable.
## Validating Submitted Data

Validating submitted data is simple as adding [Symfony's builtin constraints](http://symfony.com/doc/current/reference/constraints.html)
or [custom constraints](http://symfony.com/doc/current/validation/custom_constraint.html) directly in classes marked with
the `@ApiResource` annotation:

```php
<?php
// src/AppBundle/Entity/Product.php

namespace AppBundle\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert; // Symfony's builtin constraints
use AppBundle\Validator\Constraints\MinimalProperties; // A custom constraint

/**
* A product.
*
* @ApiResource
* @ORM\Entity
*/
class Product
{
/**
* @var int The id of this product.
*
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;

/**
* @var string The name of the product
*
* @Assert\NotBlank
* @ORM\Column
*/
private name;

/**
* @var string[] Describe the product
*
* @MinimalProperties
* @ORM\Column(type="json")
*/
private $properties;

// Getters and setters...
}
```

Here is a custom constraint and the related validator:

```php
<?php
// src/AppBundle/Validator/Constraints/MinimalProperties.php

namespace AppBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

/**
* @Annotation
*/
class MinimalProperties extends Constraint
{
public $message = 'The product must have the minimal properties required ("description", "price")';
}
```

```php
<?php
// src/AppBundle/Validator/Constraints/MinimalPropertiesValidator.php

namespace AppBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

/**
* @Annotation
*/
final class MinimalPropertiesValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint): void
{
if (!array_diff(['description', 'price'], $value)) {
$this->context->buildViolation($constraint->message)->addViolation();
}
}
}
```

If the data submitted by the client is invalid, the HTTP status code will be set to `400 Bad Request` and the response's
body will contain the list of violations serialized in a format compliant with the requested one. For instance, a validation
error will look like the following if the requested format is JSON-LD (the default):

```json
{
"@context": "/contexts/ConstraintViolationList",
"@type": "ConstraintViolationList",
"hydra:title": "An error occurred",
"hydra:description": "properties: The product must have the minimal properties required (\"description\", \"price\")",
"violations": [
{
"propertyPath": "properties",
"message": "The product must have the minimal properties required (\"description\", \"price\")"
}
]
}
```

Take a look at the [Errors Handling guide](errors.md) to learn how API Platform converts PHP exceptions like validation
errors to HTTP errors.

## Using Validation Groups

Built-in actions are able to leverage Symfony's [validation groups](http://symfony.com/doc/current/book/validation.html#validation-groups).
Without specific configuration, the default validation group is always used, but this behavior is customizable: the framework
is able to leverage Symfony's [validation groups](http://symfony.com/doc/current/book/validation.html#validation-groups).

You can customize them by editing the resource configuration and add the groups you want to use when the validation occurs:
You can configure the groups you want to use when the validation occurs directly through the `ApiResource` annotation:

```php
<?php
Expand All @@ -21,6 +140,7 @@ use Symfony\Component\Validator\Constraints as Assert;

/**
* @ApiResource(attributes={"validation_groups"={"a", "b"}})
* ...
*/
class Book
{
Expand Down
19 changes: 11 additions & 8 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,25 @@
6. [Writable Entity Identifier](core/serialization-groups-and-relations.md#writable-entity-identifier)
7. [Embedding the Context](core/serialization-groups-and-relations.md#embedding-the-context)
7. [Validation](core/validation.md)
1. [Using Validation Groups](core/validation.md#using-validation-groups)
2. [Dynamic Validation Groups](core/validation.md#dynamic-validation-groups)
1. [Validating Submitted Data](core/validation.md#validating-submitted-data)
2. [Using Validation Groups](core/validation.md#using-validation-groups)
3. [Dynamic Validation Groups](core/validation.md#dynamic-validation-groups)
8. [Pagination](core/pagination.md)
1. [Disabling the Pagination](core/pagination.md#disabling-the-pagination)
2. [Changing the Number of Items per Page](core/pagination.md#changing-the-number-of-items-per-page)
9. [The Event System](core/events.md)
10. [Content Negotiation](core/content-negotiation.md)
9. [Error Handling](core/errors.md)
1. [Converting PHP Exceptions to HTTP Errors](core/errors.md#converting-php-exceptions-to-http-errors)
10. [The Event System](core/events.md)
11. [Content Negotiation](core/content-negotiation.md)
1. [Enabling Several Formats](core/content-negotiation.md#enabling-several-formats)
2. [Registering a Custom Serializer](core/content-negotiation.md#registering-a-custom-serializer)
3. [Creating a Responder](core/content-negotiation.md#creating-a-responder)
11. [Using External JSON-LD Vocabularies](core/external-vocabularies.md)
12. [Extending JSON-LD context](core/extending-jsonld-context.md)
13. [Data Providers](core/data-providers.md)
12. [Using External JSON-LD Vocabularies](core/external-vocabularies.md)
13. [Extending JSON-LD context](core/extending-jsonld-context.md)
14. [Data Providers](core/data-providers.md)
1. [Custom Collection Data Provider](core/data-providers.md#creating-a-custom-data-provider#custom-collection-data-provider)
2. [Custom Item Data Provider](core/data-providers.md#returning-a-paged-collection#custom-item-data-provider)
14. [Extensions](core/extensions.md)
15. [Extensions](core/extensions.md)
1. [Custom Extension](core/extensions.md#custom-extension)
2. [Filter upon the current user](core/extensions.md#example)
16. [Security](core/security.md)
Expand Down