Skip to content

merging #739

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

Merged
merged 23 commits into from
Feb 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b2da160
Fix quote rendering
dunglas Jan 18, 2019
f862cae
Add a tip to use MakerBundle to create resources
Deuchnord Jan 21, 2019
386e4c1
Use Bash syntax highlighting
alanpoulain Jan 22, 2019
4bc600e
Fix wording and command
Jan 22, 2019
fb05641
Add missing allow_plain_identifiers option
Steveb-p Jan 24, 2019
55a1de9
Merge pull request #723 from Steveb-p/patch-1
dunglas Jan 25, 2019
d82147c
Clean serialization incomplete informations from getting started section
jdeniau Jan 25, 2019
d75fcfa
Move the tip to the general Getting Started section
Jan 25, 2019
ee5f74a
Fixed a typo
janklan Jan 26, 2019
c970408
Merge pull request #725 from janklan/patch-3
dunglas Jan 27, 2019
32d23e3
Documentation for MongoDB (#720)
alanpoulain Jan 27, 2019
681c201
normalizer should be denormalizer
smoelker Feb 8, 2019
dbaf7d4
Update dto.md
tigitz Feb 13, 2019
7cfd226
Merge pull request #730 from tigitz/patch-1
dunglas Feb 13, 2019
3e501d4
Merge pull request #728 from smoelker/patch-1
dunglas Feb 13, 2019
393f92f
Merge pull request #724 from jdeniau/jd-fix-serializationClarification
dunglas Feb 13, 2019
1113a82
Merge pull request #721 from Deuchnord/patch-2
dunglas Feb 13, 2019
51d3d19
Update input/output doc + diagrams
soyuka Feb 15, 2019
3fad1a3
Document metadata cache parameter
soyuka Feb 19, 2019
641afa6
Merge pull request #733 from soyuka/doc-metadata-cache-parameter
soyuka Feb 19, 2019
62a1286
Merge pull request #731 from soyuka/improve-io-doc
soyuka Feb 19, 2019
a0e3190
Update to output attribute
antograssiot Feb 20, 2019
63cef67
Merge pull request #737 from antograssiot/io-update
antograssiot Feb 20, 2019
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
2 changes: 1 addition & 1 deletion client-generator/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Client Generator works especially well with APIs built with the [API Platform](h
* list view (with pagination)
* detail view
* creation form
* edition form
* editation form
* delete button
* Supports to-one and to-many relations
* Uses the appropriate input type (`number`, `date`...)
Expand Down
11 changes: 11 additions & 0 deletions core/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ api_platform:
# Specify a path name generator to use.
path_segment_name_generator: 'api_platform.path_segment_name_generator.underscore'

# Allow using plain IDs for JSON format
allow_plain_identifiers: false

doctrine:
# To enable or disable Doctrine ORM support.
enabled: true

doctrine_mongodb_odm:
# To enable or disable Doctrine MongoDB ODM support.
enabled: false

eager_loading:
# To enable or disable eager loading.
enabled: true
Expand Down
3 changes: 2 additions & 1 deletion core/data-persisters.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ process](serialization.md).

A data persister using [Doctrine ORM](http://www.doctrine-project.org/projects/orm.html) is included with the library and
is enabled by default. It is able to persist and delete objects that are also mapped as [Doctrine entities](https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/basic-mapping.html).
A [Doctrine MongoDB ODM](https://www.doctrine-project.org/projects/mongodb-odm.html) data persister is also included and can be enabled by following the [MongoDB documentation](mongodb.md).

However, you may want to:

* store data to other persistence layers (ElasticSearch, MongoDB, external web services...)
* store data to other persistence layers (ElasticSearch, external web services...)
* not publicly expose the internal model mapped with the database through the API
* use a separate model for [read operations](data-providers.md) and for updates by implementing patterns such as [CQRS](https://martinfowler.com/bliki/CQRS.html)

Expand Down
257 changes: 197 additions & 60 deletions core/dto.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Using Data Transfer Objects (DTOs)

## Specifying an Input or an Output Class
## Specifying an Input or an Output data representation

For a given resource class, you may want to have a different representation of this class as input (write) or output (read).
To do so, a resource can take an input and/or an output class:
Expand All @@ -17,100 +17,126 @@ use App\Dto\BookOutput;

/**
* @ApiResource(
* inputClass=BookInput::class,
* outputClass=BookOutput::class
* input=BookInput::class,
* output=BookOutput::class
* )
*/
final class Book
{
}
```

The `input_class` attribute is used during [the deserialization process](serialization.md), when transforming the user provided data to a resource instance.
Similarly, the `output_class` attribute is used during the serialization process, this class represents how the `Book` resource will be represented in the `Response`.
The `input` attribute is used during [the deserialization process](serialization.md), when transforming the user provided data to a resource instance.
Similarly, the `output` attribute is used during [the serialization process](serialization.md), this class represents how the `Book` resource will be represented in the `Response`.

To create a `Book`, we `POST` a data structure corresponding to the `BookInput` class and get back in the response a data structure corresponding to the `BookOuput` class.
The `input` and `output` attributes are taken into account by all the documentation generators (GraphQL and OpenAPI, Hydra).

To persist the input object, a custom [data persister](data-persisters.md) handling `BookInput` instances must be written.
To retrieve an instance of the output class, a custom [data provider](data-providers.md) returning a `BookOutput` instance must be written.
To create a `Book`, we `POST` a data structure corresponding to the `BookInput` class and get back in the response a data structure corresponding to the `BookOuput` class:

The `input_class` and `output_class` attributes are taken into account by all the documentation generators (GraphQL and OpenAPI, Hydra).
![Diagram post input output](images/diagrams/api-platform-post-i-o.png)

## Disabling the Input or the Output
To simplify object transformations we have to implement a Data Transformer that will convert the input into a resource or a resource into an output.

Both the `input_class` and the `output_class` attributes can be set to `false`.
If `input_class` is `false`, the deserialization process will be skipped, and no data persisters will be called.
If `output_class` is `false`, the serialization process will be skipped, and no data providers will be called.
With the following `BookInput`:

## Creating a Service-Oriented endpoint
```php
<?php
// src/Dto/BookInput.php

Sometimes it's convenient to create [RPC](https://en.wikipedia.org/wiki/Remote_procedure_call)-like endpoints.
For example, the application should be able to send an email when someone has lost his password.
namespace App\Dto;

So let's create a basic DTO for this request:
final class BookInput {
public $isbn;
}
```

We can transform the `BookInput` to a `Book` resource instance:

```php
<?php
// api/src/Entity/ResetPasswordRequest.php
// src/DataTransformer/BookInputDataTransformer.php

namespace App\Entity;
namespace App\DataTransformer;

use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Validator\Constraints as Assert;
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use App\Dto\BookInput;

/**
* @ApiResource(
* collectionOperations={
* "post"={
* "path"="/users/forgot-password-request"
* },
* },
* itemOperations={},
* outputClass=false
* )
*/
final class ResetPasswordRequest
final class BookInputDataTransformer implements DataTransformerInterface
{
/**
* @var string
*
* @Assert\NotBlank
* {@inheritdoc}
*/
public function transform($data, string $to, array $context = [])
{
$book = new Book();
$book->isbn = $data->isbn;
return $book;
}

/**
* {@inheritdoc}
*/
public $username;
public function supportsTransformation($data, string $to, array $context = []): bool
{
return Book::class === $to && $data instanceof BookInput;
}
}
```

In this case, we disable all operations except `POST`. We also set the `output_class` attribute to `false` to hint API Platform that no data will be returned by this endpoint.
And register it:

Then, thanks to [a custom data persister](data-persisters.md), it's possible to trigger some custom logic when the request is received.
```yaml
# api/config/services.yaml
services:
# ...
'App\DataTransformer\BookInputDataTransformer': ~
# Uncomment only if autoconfiguration is disabled
#tags: [ 'api_platform.data_transformer' ]
```

Create the data persister:
To manage the output, it's exactly the same process. For example with `BookOutput` being:

```php
<?php
// api/src/DataPersister/ResetPasswordRequestDataPersister.php
****
namespace App\DataPersister;
// src/Dto/BookOutput.php

use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Entity\ResetPasswordRequest;
namespace App\Dto;

final class ResetPasswordRequestDataPersister implements DataPersisterInterface
final class BookOutput {
public $name;
}
```

We can transform the `Book` to a `BookOutput` object:

```php
<?php
// src/DataTransformer/BookOutputDataTransformer.php

namespace App\DataTransformer;

use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use App\Dto\BookOutput;
use App\Entity\Book;

final class BookOutputDataTransformer implements DataTransformerInterface
{
public function supports($data): bool
{
return $data instanceof ResetPasswordRequest;
}

public function persist($data)
/**
* {@inheritdoc}
*/
public function transform($data, string $to, array $context = [])
{
// Trigger your custom logic here
return $data;
$output = new BookOutput();
$output->name = $data->name;
return $output;
}

public function remove($data)

/**
* {@inheritdoc}
*/
public function supportsTransformation($data, string $to, array $context = []): bool
{
throw new \RuntimeException('"remove" is not supported');
return BookOutput::class === $to && $data instanceof Book;
}
}
```
Expand All @@ -121,9 +147,120 @@ And register it:
# api/config/services.yaml
services:
# ...
'App\DataPersister\ResetPasswordRequestDataPersister': ~
'App\DataTransformer\BookOutputDataTransformer': ~
# Uncomment only if autoconfiguration is disabled
#tags: [ 'api_platform.data_persister' ]
#tags: [ 'api_platform.data_transformer' ]
```

## Updating a resource with a custom input

When performing an update (e.g. `PUT` operation), the resource to be updated is read by ApiPlatform before the deserialization phase. To do so, it uses a [data provider](data-providers.md) with the `:id` parameter given in the URL. The *body* of the request is the JSON object sent by the client, it is deserialized and is used to update the previously found resource.

![Diagram put input output](images/diagrams/api-platform-put-i-o.png)

Now, we will update our resource by using a different input representation.

With the following `BookInput`:

```
<?php
// src/Dto/BookInput.php

namespace App\Dto;

final class BookInput {
/**
* @var \App\Entity\Author
*/
public $author;
}
```

We will implement a `BookInputDataTransformer` that transforms the `BookInput` to our `Book` resource instance. In this case, the `Book` (`/books/1`) already exists, so we will just update it.

```
<?php
// src/DataTransformer/BookInputDataTransformer.php

namespace App\DataTransformer;

use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;
use App\Dto\BookInput;

final class BookInputDataTransformer implements DataTransformerInterface
{
/**
* {@inheritdoc}
*/
public function transform($data, string $to, array $context = [])
{
$existingBook = $context[AbstractItemNormalizer::OBJECT_TO_POPULATE];
$existingBook->author = $data->author;
return $existingBook;
}

/**
* {@inheritdoc}
*/
public function supportsTransformation($data, string $to, array $context = []): bool
{
return Book::class === $to && $data instanceof BookInput;
}
}
```

```yaml
# api/config/services.yaml
services:
# ...
'App\DataTransformer\BookInputDataTransformer': ~
# Uncomment only if autoconfiguration is disabled
#tags: [ 'api_platform.data_transformer' ]
```

## Disabling the Input or the Output

Both the `input` and the `output` attributes can be set to `false`.
If `input` is `false`, the deserialization process will be skipped, and no data persisters will be called.
If `output` is `false`, the serialization process will be skipped, and no data providers will be called.

## Input/Output metadata

When specified, `input` and `output` attributes support:
- a string representing the class to use
- a falsy boolean to disable them
- an array to specify more metadata for example `['class' => BookInput::class, 'name' => 'BookInput', 'iri' => '/book_input']`


## Using DTO objects inside resources

Because ApiPlatform can (de)normalize anything in the supported formats (`jsonld`, `jsonapi`, `hal`, etc.), you can use any object you want inside resources. For example, let's say that the `Book` has an `attribute` property that can't be represented by a resource, we can do the following:

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

namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiResource;
use App\Dto\Attribute;

/**
* @ApiResource(
* input=BookInput::class,
* output=BookOutput::class
* )
*/
final class Book
{
/**
* @var Attribute
**/
public $attribute;

public $isbn;
}
```

Instead of a custom data persister, you'll probably want to leverage [the Symfony Messenger Component integration](messenger.md).
The `Book` `attribute` property will now be an instance of `Attribute` after the (de)normalization phase.
2 changes: 1 addition & 1 deletion core/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ feature](http://symfony.com/doc/current/components/dependency_injection/autowiri

Alternatively, [the subscriber must be registered manually](http://symfony.com/doc/current/components/http_kernel/introduction.html#creating-an-event-listener).

[Doctrine events](http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#reference-events-lifecycle-events)
Doctrine events ([ORM](http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#reference-events-lifecycle-events), [MongoDB ODM](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/events.html#lifecycle-events))
are also available (if you use it) if you want to hook at the object lifecycle events.

Built-in event listeners are:
Expand Down
15 changes: 14 additions & 1 deletion core/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

API Platform Core provides a system to extend queries on items and collections.

Extensions are specific to Doctrine and Elasticsearch-PHP, and therefore, the Doctrine ORM support or the Elasticsearch
Extensions are specific to Doctrine and Elasticsearch-PHP, and therefore, the Doctrine ORM / MongoDB ODM support or the Elasticsearch
reading support must be enabled to use this feature. If you use custom providers it's up to you to implement your own
extension system or not.

Expand Down Expand Up @@ -152,6 +152,19 @@ security:
- { path: ^/users, roles: IS_AUTHENTICATED_FULLY }
```

## Custom Doctrine MongoDB ODM Extension

Creating custom extensions is the same as Doctrine ORM.

The interfaces are:
* `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationItemExtensionInterface` and `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationCollectionExtensionInterface` to add stages to the [aggregation builder](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/aggregation-builder.html).
* `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultItemExtensionInterface` and `ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Extension\AggregationResultCollectionExtensionInterface` to return a result.

The tags are `api_platform.doctrine.mongodb.aggregation_extension.item` and `api_platform.doctrine.mongodb.aggregation_extension.collection`.

The custom extensions receive the [aggregation builder](https://www.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/aggregation-builder.html),
used to execute [complex operations on data](https://docs.mongodb.com/manual/aggregation/).

## Custom Elasticsearch Extension

Currently only extensions querying for a collection of items through a [search request](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html)
Expand Down
Loading