Skip to content

Fixes #424 - Wrong Kernel Event / EventPriorities in Errors Example #505

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
wants to merge 16 commits into from
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
3 changes: 3 additions & 0 deletions core/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ api_platform:
# URLs of the Varnish servers to purge using cache tags when a resource is updated.
varnish_urls: []

# To pass options to the client charged with the request.
request_options: []

# The list of exceptions mapped to their HTTP status code.
exception_to_status:
# With a status code.
Expand Down
4 changes: 2 additions & 2 deletions core/data-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ final class BlogPostItemDataProvider implements ItemDataProviderInterface, Restr
return BlogPost::class === $resourceClass;
}

public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?BlogPost
public function getItem(string $resourceClass, $identifiers, string $operationName = null, array $context = []): ?BlogPost
{
// Retrieve the blog post item from somewhere then return it or null if not found
return new BlogPost($id);
return new BlogPost($identifiers['id']);
}
}
```
Expand Down
2 changes: 1 addition & 1 deletion core/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ final class ProductManager implements EventSubscriberInterface
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['checkProductAvailability', EventPriorities::POST_DESERIALIZE],
KernelEvents::VIEW => ['checkProductAvailability', EventPriorities::PRE_VALIDATE],
];
}

Expand Down
19 changes: 19 additions & 0 deletions core/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,25 @@ resources:
pagination_items_per_page: 25 # optional
```

If you prefer to use XML or YAML files instead of annotations, you must configure API Platform to load the appropriate files:

```yaml
# api/config/packages/api_platform.yaml
api_platform:
mapping:
paths:
- '%kernel.project_dir%/src/Entity' # default configuration for annotations
- '%kernel.project_dir%/config/api_platform' # yaml or xml directory configuration
```

The API Platform's configuration (annotations, `YAML` or `XML`) only allow to configure the context passed to the Symfony Serializer:

* The `normalization_context` key will be passed as 3rd argument of [the `Serializer::serialize()` method](https://api.symfony.com/master/Symfony/Component/Serializer/SerializerInterface.html#method_serialize)
* The `denormalization_context` key will be passed as 4th argument of [the `Serializer::deserialize()` method](https://api.symfony.com/master/Symfony/Component/Serializer/SerializerInterface.html#method_deserialize)


To configure the serialization groups of classes's properties, you must use directly [the Symfony Serializer's configuration files or annotations]( https://symfony.com/doc/current/components/serializer.html#attributes-groups).

**You're done!**

You now have a fully featured API exposing your entities.
Expand Down
123 changes: 123 additions & 0 deletions core/identifiers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Identifiers

Every item operation has an identifier in it's URL. Although this identifier is usually a number, it can also be an `UUID`, a date, or the type of your choice.
To help with your development experience, we introduced an identifier normalization process.

## Custom identifier normalizer

> In the following chapter, we're assuming that `App\Uuid` is a project-owned class that manages a time-based UUID.

Let's say you have the following class, which is identified by a `UUID` type. In this example, `UUID` is not a simple string but it's an object with many attributes.

```php
<?php
namespace App\Entity;

use App\Uuid;

/**
* @ApiResource
*/
final class Person {
/**
* @type Uuid
* @ApiProperty(identifier=true)
*/
public $code;
}
```

Once registered as an `ApiResource`, having an existing person, it will be accessible through the following URL: `/people/110e8400-e29b-11d4-a716-446655440000`.
Note that the property identifying our resource is named `code`.

Let's create a `DataProvider` for the `Person` entity:

```php
<?php
namespace App\DataProvider;

use App\Entity\Person;
use App\Uuid;

final class PersonDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface {

/**
* {@inheritdoc}
*/
public function getItem(string $resourceClass, $identifiers, string $operationName = null, array $context = [])
{
// Our identifier is:
// $identifiers['code']
// although it's a string, it's not an instance of Uuid and we wanted to retrieve the timestamp of our time-based uuid:
// $identifiers['code']->getTimestamp()
}

/**
* {@inheritdoc}
*/
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return $resourceClass === Person::class;
}
}
```

To cover this use case, we need to `denormalize` the identifier to an instance of our `App\Uuid` class. This case is covered by an identifier denormalizer:

```php
<?php
namespace App\Identifier;

use ApiPlatform\Core\Exception\InvalidIdentifierException;
use App\Uuid;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

final class UuidNormalizer implements DenormalizerInterface
{
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = null, array $context = [])
{
try {
return Uuid::fromString($data);
} catch (InvalidUuidStringException $e) {
throw new InvalidIdentifierException($e->getMessage());
}
}

/**
* {@inheritdoc}
*/
public function supportsDenormalization($data, $type, $format = null)
{
return is_a($type, Uuid::class, true);
}
}
```

Tag this service as a `api_platform.identifier.normalizer`:

```xml
<service id="App\identifier\UuidNormalizer" class="App\identifier\UuidNormalizer" public="false">
<tag name="api_platform.identifier.normalizer" />
</service>
```

```yaml
services:
App\identifier\UuidNormalizer:
tags:
- { name: api_platform.identifier.normalizer }
```

Your `PersonDataProvider` will now work as expected!


## Supported identifiers

ApiPlatform supports the following identifier types:

- `scalar` (string, integer)
- `\DateTime` (uses the symfony `DateTimeNormalizer` internally, see [DateTimeIdentifierNormalizer](https://github.com/api-platform/core/blob/master/src/Identifier/Normalizer/DateTimeIdentifierNormalizer.php))
- `\Ramsey\Uuid\Uuid` (see [UuidNormalizer](https://github.com/api-platform/core/blob/master/src/Bridge/RamseyUuid/Identifier/Normalizer/UuidNormalizer.php))
35 changes: 32 additions & 3 deletions core/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,28 @@ Or in XML:

In all the previous examples, you can safely remove the `method` because the method name always match the operation name.

### Prefixing All Routes of All Operations

Sometimes it's also useful to put a whole resource into its own "namespace" regarding the URI. Let's say you want to
put everything that's related to a `Book` into the `library` so that URIs become `library/book/{id}`. In that case
you don't need to override all the operations to set the path but configure a `routePrefix` for the whole entity instead:

```php
<?php
// api/src/Entity/Book.php

use ApiPlatform\Core\Annotation\ApiResource;

/**
* ...
* @ApiResource("attributes"={"routePrefix"="/library"})
*/
class Book
{
//...
}
```

## Subresources

Since ApiPlatform 2.1, you can declare subresources. A subresource is a collection or an item that belongs to another resource.
Expand Down Expand Up @@ -486,9 +508,12 @@ automatically instantiated and injected, without having to declare it explicitly
In the following examples, the built-in `GET` operation is registered as well as a custom operation called `special`.
The `special` operation reference the Symfony route named `book_special`.

Note: By default, API Platform uses the first `GET` operation defined in `itemOperations` to generate the IRI of an item and the first `GET` operation defined in `collectionOperations` to generate the IRI of a collection.
Since version 2.3, you can also use the route name as operation name by convention as shown in the next example
for `book_custom` if no `method` nor `route_name` attributes are specified.

By default, API Platform uses the first `GET` operation defined in `itemOperations` to generate the IRI of an item and the first `GET` operation defined in `collectionOperations` to generate the IRI of a collection.

Note: With custom operation, you will probably want to properly document it. See the [swagger](swagger.md) part of the documentation to do so.
If you create a custom operation, you will probably want to properly document it. See the [swagger](swagger.md) part of the documentation to do so.

### Recommended Method

Expand Down Expand Up @@ -680,6 +705,7 @@ There is another way to create a custom operation. However, we do not encourage
the configuration at the same time in the routing and the resource configuration.

First, let's create your resource configuration:
>>>>>>> 2.2

```php
<?php
Expand All @@ -690,7 +716,8 @@ use ApiPlatform\Core\Annotation\ApiResource;
/**
* @ApiResource(itemOperations={
* "get",
* "special"={"route_name"="book_special"}
* "special"={"route_name"="book_special"},
* "book_custom",
* })
*/
class Book
Expand All @@ -708,6 +735,7 @@ App\Entity\Book:
get: ~
special:
route_name: 'book_special'
book_custom: ~
```

Or in XML:
Expand All @@ -726,6 +754,7 @@ Or in XML:
<itemOperation name="special">
<attribute name="route_name">book_special</attribute>
</itemOperation>
<itemOperation name="book_custom" />
</itemOperations>
</resource>
</resources>
Expand Down
3 changes: 3 additions & 0 deletions core/swagger.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ You can also dump your current Swagger documentation using the provided command:
```
$ docker-compose exec php bin/console api:swagger:export
# Swagger documentation in JSON format...

$ docker-compose exec php bin/console api:swagger:export --yaml
# Swagger documentation in YAML format...
```

## Overriding the UI Template
Expand Down
3 changes: 3 additions & 0 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@
2. [Enabling GraphQL](core/graphql.md#enabling-graphql)
3. [GraphiQL](core/graphql.md#graphiql)
27. [Handling Data Transfer Objects (DTOs)](core/dto.md)
28. [Identifiers](core/identifiers.md)
1. [Custom identifier normalizer](core/identifiers.md#custom-identifier-normalizer)
2. [Supported identifiers](core/identifiers.md#supported-identifiers)

## The Schema Generator Component: Generate Data Models from Open Vocabularies

Expand Down
2 changes: 1 addition & 1 deletion schema-generator/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ In such case, no mutator method will be generated.

## Making a Property Write Only

A property can be marked read only with the following configuration:
A property can be marked write only with the following configuration:

```yaml
Person:
Expand Down