Skip to content

Commit

Permalink
Merge pull request #1820 from soyuka/main
Browse files Browse the repository at this point in the history
Merge 3.1
  • Loading branch information
soyuka authored Oct 16, 2023
2 parents a96e94f + 0676478 commit cc4786d
Show file tree
Hide file tree
Showing 26 changed files with 660 additions and 422 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,14 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
cname: api-platform.com

- name: Auth gcloud
uses: google-github-actions/auth@v1
with:
credentials_json: ${{ secrets.BUCKET_CREDS }}

- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v1'

- name: Deploy on bucket
run: gsutil -m rsync -d -r ./public gs://api-platform-website-v3/
2 changes: 1 addition & 1 deletion admin/customizing.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,4 @@ For instance, using an autocomplete input is straightforward, [checkout the dedi
API Platform is built on top of [React Admin](https://marmelab.com/react-admin/).
You can use all the features provided by the underlying library with API Platform Admin, including support for [authentication](https://marmelab.com/react-admin/Authentication.html), [authorization](https://marmelab.com/react-admin/Authorization.html) and deeper customization.

To learn more about these capabilities, refer to [the React Admin documentation](https://marmelab.com/react-admin/).
To learn more about these capabilities, refer to [the React Admin documentation](https://marmelab.com/react-admin/documentation.html).
2 changes: 2 additions & 0 deletions admin/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ library to expose a nice, responsive, management interface (Create-Retrieve-Upda

You can **customize everything** by using provided React Admin and [MUI](https://mui.com/) components, or by writing your custom [React](https://reactjs.org/) components.

<p align="center" class="symfonycasts"><a href="https://symfonycasts.com/screencast/api-platform/react-admin?cid=apip"><img src="../distribution/images/symfonycasts-player.png" alt="React Admin Screencast"><br>Watch the React Admin screencast</a></p>

## Features

* Automatically generates an admin interface for all the resources of the API thanks to the hypermedia features of Hydra or to the OpenAPI documentation
Expand Down
521 changes: 292 additions & 229 deletions core/bootstrap.md

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions core/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ The entity is retrieved in the `__invoke` method thanks to a dedicated argument

When using `GET`, the `__invoke()` method parameter will receive the identifier and should be called the same as the resource identifier.
So for the path `/user/{uuid}/bookmarks`, you must use `__invoke(string $uuid)`.
**Warning: the `__invoke()` method parameter [MUST be called `$data`](https://symfony.com/doc/current/components/http_kernel.html#4-getting-the-controller-arguments)**, otherwise, it will not be filled correctly!

Services (`$bookPublishingHandler` here) are automatically injected thanks to the autowiring feature. You can type-hint any service
you need and it will be autowired too.
Expand Down Expand Up @@ -503,5 +502,5 @@ book_post_publication:
defaults:
_controller: App\Controller\BookController::createPublication
_api_resource_class: App\Entity\Book
_api_item_operation_name: post_publication
_api_operation_name: post_publication
```
67 changes: 26 additions & 41 deletions core/elasticsearch.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ application search, security analytics, metrics, logging, etc.
API Platform comes natively with the **reading** support for Elasticsearch. It uses internally the official PHP client
for Elasticsearch: [Elasticsearch-PHP](https://www.elastic.co/guide/en/elasticsearch/client/php-api/current/index.html).

Be careful, API Platform only supports Elasticsearch >= 6.5.0.
Be careful, API Platform only supports Elasticsearch >= 7.11.0 < 8.0.

## Enabling Reading Support

To enable the reading support for Elasticsearch, simply require the Elasticsearch-PHP package using Composer:

```console
composer require elasticsearch/elasticsearch:^6.0
composer require elasticsearch/elasticsearch:^7.11
```

Then, enable it inside the API Platform configuration:
Expand Down Expand Up @@ -146,10 +146,20 @@ Here is an example of mappings for 2 resources, `User` and `Tweet`, and their mo
// api/src/Model/User.php
namespace App\Model;

use ApiPlatform\Elasticsearch\State\CollectionProvider;
use ApiPlatform\Elasticsearch\State\ItemProvider;
use ApiPlatform\Elasticsearch\State\Options;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;

#[ApiResource]
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;

#[ApiResource(
operations: [
new GetCollection(provider: CollectionProvider::class, stateOptions: new Options(index: 'user')),
new Get(provider: ItemProvider::class, stateOptions: new Options(index: 'user')),
],
)]
class User
{
#[ApiProperty(identifier: true)]
Expand All @@ -175,10 +185,20 @@ class User
// api/src/Model/Tweet.php
namespace App\Model;

use ApiPlatform\Elasticsearch\State\CollectionProvider;
use ApiPlatform\Elasticsearch\State\ItemProvider;
use ApiPlatform\Elasticsearch\State\Options;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;

#[ApiResource]
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;

#[ApiResource(
operations: [
new GetCollection(provider: CollectionProvider::class, stateOptions: new Options(index: 'tweet')),
new Get(provider: ItemProvider::class, stateOptions: new Options(index: 'tweet')),
],
)]
class Tweet
{
#[ApiProperty(identifier: true)]
Expand All @@ -199,43 +219,8 @@ Keep in mind that it is your responsibility to populate your Elasticsearch index
a custom [state processors](state-processors.md#creating-a-custom-state-processor) or any other mechanism that suits your
project (such as an [ETL](https://en.wikipedia.org/wiki/Extract,_transform,_load)).

To disable elasticsearch index discovery for non-elasticsearch entities you can set `elasticsearch: false` in the `#[ApiResource]` attribute. If this property is absent, all entities will perform an index check during cache warmup to determine if they are on elasticsearch or not.

You're done! The API is now ready to use.

### Creating custom mapping

If you don't follow the Elasticsearch recommendations, you may want a custom mapping between API Platform resources and
Elasticsearch indexes/types.

For example, consider an index being similar to a database in an SQL database and a type being equivalent to a table.
So the `User` and `Tweet` resources of the previous example would become `user` and `tweet` types in an index named `app`:

```yaml
# api/config/packages/api_platform.yaml
parameters:
# ...
env(ELASTICSEARCH_HOST): 'http://localhost:9200'

api_platform:
# ...

mapping:
paths: ['%kernel.project_dir%/src/Model']

elasticsearch:
hosts: ['%env(ELASTICSEARCH_HOST)%']
mapping:
App\Model\User:
index: app
type: user
App\Model\Tweet:
index: app
type: tweet

#...
```

## Filtering

See how to use Elasticsearch filters and how to create Elasticsearch custom filters in [the Filters chapter](filters.md).
Expand Down
6 changes: 3 additions & 3 deletions core/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ These built-in event listeners are registered for routes managed by API Platform

Name | Event | [Pre & Post hooks](#custom-event-listeners) | Priority | Description
------------------------------|--------------------|---------------------------------------------|----------|-------------
`AddFormatListener` | `kernel.request` | None | 7 | Guesses the best response format ([content negotiation](content-negotiation.md))
`QueryParameterValidateListener` | `kernel.request` | None | 16 | Validates query parameters
`ReadListener` | `kernel.request` | `PRE_READ`, `POST_READ` | 4 | Retrieves data from the persistence system using the [state providers](data-providers.md) (`GET`, `PUT`, `PATCH`, `DELETE`)
`AddFormatListener` | `kernel.request` | None | 28 | Guesses the best response format ([content negotiation](content-negotiation.md))
`ReadListener` | `kernel.request` | `PRE_READ`, `POST_READ` | 4 | Retrieves data from the persistence system using the [state providers](state-providers.md) (`GET`, `PUT`, `PATCH`, `DELETE`)
`QueryParameterValidateListener` | `kernel.request` | None | 2 | Validates query parameters
`DeserializeListener` | `kernel.request` | `PRE_DESERIALIZE`, `POST_DESERIALIZE` | 2 | Deserializes data into a PHP entity (`POST`); updates the entity retrieved using the state provider (`PUT`, `PATCH`)
`DenyAccessListener` | `kernel.request` | None | 1 | Enforces [access control](security.md) using Security expressions
`ValidateListener` | `kernel.view` | `PRE_VALIDATE`, `POST_VALIDATE` | 64 | [Validates data](validation.md) (`POST`, `PUT`, `PATCH`)
Expand Down
6 changes: 2 additions & 4 deletions core/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,13 @@ use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use App\Entity\Offer;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;
use Symfony\Bundle\SecurityBundle\Security;

final class CurrentUserExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface
{
private $security;

public function __construct(Security $security)
public function __construct(private readonly Security $security)
{
$this->security = $security;
}

public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
Expand Down
6 changes: 3 additions & 3 deletions core/file-upload.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ recommended you [read the documentation of
VichUploaderBundle](https://github.com/dustin10/VichUploaderBundle/blob/master/docs/index.md)
before proceeding. It will help you get a grasp on how the bundle works, and why we use it.

**Note**: Uploading files won't work in `PUT` or `PATCH` requests, you must use `POST` method to upload files.
See [the related issue on Symfony](https://github.com/symfony/symfony/issues/9226) and [the related bug in PHP](https://bugs.php.net/bug.php?id=55815) talking about this behavior.

## Installing VichUploaderBundle

Install the bundle with the help of Composer:
Expand Down Expand Up @@ -43,9 +46,6 @@ resource (in our case: `Book`).
This example will use a custom controller to receive the file.
The second example will use a custom `multipart/form-data` decoder to deserialize the resource instead.

**Note**: Uploading files won't work in `PUT` or `PATCH` requests, you must use `POST` method to upload files.
See [the related issue on Symfony](https://github.com/symfony/symfony/issues/9226) and [the related bug in PHP](https://bugs.php.net/bug.php?id=55815) talking about this behavior.

### Configuring the Resource Receiving the Uploaded File

The `MediaObject` resource is implemented like this:
Expand Down
42 changes: 22 additions & 20 deletions core/filters.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Filters

API Platform provides a generic system to apply filters and sort criteria on collections.
Useful filters for Doctrine ORM, MongoDB ODM and ElasticSearch are provided with the library.
Useful filters for Doctrine ORM, MongoDB ODM, and ElasticSearch are provided with the library.

You can also create custom filters that fit your specific needs.
You can also add filtering support to your custom [state providers](state-providers.md) by implementing interfaces provided
Expand All @@ -23,7 +23,7 @@ to a Resource in two ways:

1. Through the resource declaration, as the `filters` attribute.

For example having a filter service declaration in `services.yaml`:
For example, having a filter service declaration in `services.yaml`:

```yaml
# api/config/services.yaml
Expand Down Expand Up @@ -254,15 +254,15 @@ The above URLs will return all offers for the product having the following IRI a

### Date Filter

The date filter allows to filter a collection by date intervals.
The date filter allows filtering a collection by date intervals.

Syntax: `?property[<after|before|strictly_after|strictly_before>]=value`

The value can take any date format supported by the [`\DateTime` constructor](https://www.php.net/manual/en/datetime.construct.php).

The `after` and `before` filters will filter including the value whereas `strictly_after` and `strictly_before` will filter excluding the value.

Like others filters, the date filter must be explicitly enabled:
Like other filters, the date filter must be explicitly enabled:

[codeSelector]

Expand Down Expand Up @@ -533,7 +533,7 @@ You can filter offers by joining two values, for example: `/offers?price[gt]=12.

### Exists Filter

The exists filter allows you to select items based on a nullable field value.
The "exists" filter allows you to select items based on a nullable field value.
It will also check the emptiness of a collection association.

Syntax: `?exists[property]=<true|false|1|0>`
Expand Down Expand Up @@ -582,7 +582,7 @@ App\Entity\Offer:

[/codeSelector]

Given that the collection endpoint is `/offers`, you can filter offers on nullable field with the following query: `/offers?exists[transportFees]=true`.
Given that the collection endpoint is `/offers`, you can filter offers on the nullable field with the following query: `/offers?exists[transportFees]=true`.

It will return all offers where `transportFees` is not `null`.

Expand All @@ -600,7 +600,7 @@ api_platform:

### Order Filter (Sorting)

The order filter allows to sort a collection against the given properties.
The order filter allows sorting a collection against the given properties.

Syntax: `?order[property]=<asc|desc>`

Expand Down Expand Up @@ -990,8 +990,8 @@ api_platform:

### Match Filter

The match filter allows to find resources that [match](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html)
the specified text on full text fields.
The match filter allows us to find resources that [match](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html)
the specified text on full-text fields.

Syntax: `?property[]=value`

Expand Down Expand Up @@ -1021,7 +1021,7 @@ Given that the collection endpoint is `/tweets`, you can filter tweets by messag

### Term Filter

The term filter allows to find resources that contain the exact specified
The term filter allows us to find resources that contain the exact specified
[terms](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html).

Syntax: `?property[]=value`
Expand Down Expand Up @@ -1194,7 +1194,7 @@ final class RegexpFilter extends AbstractFilter
{
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
{
// otherwise filter is applied to order and page as well
// Otherwise filter is applied to order and page as well
if (
!$this->isPropertyEnabled($property, $resourceClass) ||
!$this->isPropertyMapped($property, $resourceClass)
Expand Down Expand Up @@ -1260,7 +1260,7 @@ class Offer
You can now use this filter in the URL like `http://example.com/offers?regexp_email=^[FOO]`. This new filter will also
appear in OpenAPI and Hydra documentations.

In the previous example, the filter can be applied on any property. You can also apply this filter on a specific property:
In the previous example, the filter can be applied to any property. You can also apply this filter on a specific property:

```php
<?php
Expand Down Expand Up @@ -1303,10 +1303,11 @@ class Offer
```
These properties can then be accessed in the custom filter like this:
```php
//App/Filter/CustomAndFilter.php
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []) {
// api/src/Filter/CustomAndFilter.php
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void {
$rootAlias = $queryBuilder->getRootAliases()[0];
foreach (array_keys($this->getProperties()) as $prop) { //NOTE: we use array_keys because getProperties() returns a map of property => strategy
foreach(array_keys($this->getProperties()) as $prop) { // we use array_keys() because getProperties() returns a map of property => strategy
if (!$this->isPropertyEnabled($prop, $resourceClass) || !$this->isPropertyMapped($prop, $resourceClass)) {
return;
}
Expand Down Expand Up @@ -1335,7 +1336,7 @@ services:
tags: [ 'api_platform.filter' ]
```

In the previous example, the filter can be applied on any property. However, thanks to the `AbstractFilter` class,
In the previous example, the filter can be applied to any property. However, thanks to the `AbstractFilter` class,
it can also be enabled for some properties:

```yaml
Expand Down Expand Up @@ -1394,10 +1395,11 @@ Suppose you want to use the [match filter](#match-filter) on a property named `$
namespace App\ElasticSearch;
use ApiPlatform\Elasticsearch\Extension\RequestBodySearchCollectionExtensionInterface;
use ApiPlatform\Metadata\Operation;
class AndOperatorFilterExtension implements RequestBodySearchCollectionExtensionInterface
{
public function applyToCollection(array $requestBody, string $resourceClass, ?string $operationName = null, array $context = []): array
public function applyToCollection(array $requestBody, string $resourceClass, ?Operation $operation = null, array $context = []): array;
{
$requestBody['query'] = $requestBody['query'] ?? [];
$andQuery = [
Expand All @@ -1415,7 +1417,7 @@ class AndOperatorFilterExtension implements RequestBodySearchCollectionExtension
### Using Doctrine ORM Filters

Doctrine ORM features [a filter system](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/filters.html) that allows the developer to add SQL to the conditional clauses of queries, regardless of the place where the SQL is generated (e.g. from a DQL query, or by loading associated entities).
These are applied on collections and items and therefore are incredibly useful.
These are applied to collections and items and therefore are incredibly useful.

The following information, specific to Doctrine filters in Symfony, is based upon [a great article posted on Michaël Perrin's blog](http://blog.michaelperrin.fr/2014/12/05/doctrine-filters/).

Expand Down Expand Up @@ -1594,7 +1596,7 @@ On the first property, `name`, it's straightforward. The first attribute argumen
#[ApiFilter(SearchFilter::class, strategy: 'partial')]
```

In the second attribute, we specify `properties` on which the filter should apply. It's necessary here because we don't want to filter `colors` but the `prop` property of the `colors` association.
In the second attribute, we specify `properties` to which the filter should apply. It's necessary here because we don't want to filter `colors` but the `prop` property of the `colors` association.
Note that for each given property we specify the strategy:

```php
Expand Down Expand Up @@ -1632,7 +1634,7 @@ class DummyCar
```

The `BooleanFilter` is applied to every `Boolean` property of the class. Indeed, in each core filter we check the Doctrine type. It's written only by using the filter class:
The `BooleanFilter` is applied to every `Boolean` property of the class. Indeed, in each core filter, we check the Doctrine type. It's written only by using the filter class:

```php
#[ApiFilter(BooleanFilter::class)]
Expand Down
Loading

0 comments on commit cc4786d

Please sign in to comment.