Skip to content

Commit

Permalink
Merge branch '4.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
dunglas committed Sep 28, 2024
2 parents 635738a + 6b5c5a5 commit 9506e8e
Show file tree
Hide file tree
Showing 7 changed files with 40 additions and 262 deletions.
5 changes: 1 addition & 4 deletions core/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,7 @@ api_platform:
# Enable the data collector and the WebProfilerBundle integration.
enable_profiler: true

# Keep doctrine/inflector instead of symfony/string to generate plurals for routes.
keep_legacy_inflector: true

collection:
collection:
# The name of the query parameter to filter nullable results (with the ExistsFilter).
exists_parameter_name: 'exists'

Expand Down
260 changes: 10 additions & 250 deletions core/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ It is also automatically documented as a `search` property for JSON-LD responses

## Parameters

You can declare parameters on a Resource or an Operation through the `parameters` property.
You can declare parameters on a Resource or an Operation through the `parameters` property.

```php
namespace App\ApiResource;
Expand All @@ -39,7 +39,7 @@ A Parameter can be linked to a filter, there are two types of filters:

### Alter the Operation via a parameter

A parameter can alter the current Operation context, to do so use a `ApiPlatform\State\ParameterProviderInterface`:
A parameter can alter the current Operation context, to do so use a `ApiPlatform\State\ParameterProviderInterface`:

```php
class GroupsParameterProvider implements ParameterProviderInterface {
Expand All @@ -64,7 +64,7 @@ class Book {
}
```

If you don't have autoconfiguration enabled, declare the parameter as a tagged service:
If you don't have autoconfiguration enabled, declare the parameter as a tagged service:

```yaml
services:
Expand All @@ -76,7 +76,7 @@ services:
### Call a filter
A Parameter can also call a filter and works on filters that impact the data persistence layer (Doctrine ORM, ODM and Eloquent filters are supported). Let's assume, that we have an Order filter declared:
A Parameter can also call a filter and works on filters that impact the data persistence layer (Doctrine ORM, ODM and Eloquent filters are supported). Let's assume, that we have an Order filter declared:
```yaml
# config/services.yaml
Expand All @@ -89,7 +89,7 @@ services:
tags: [ 'api_platform.filter' ]
```
We can use this filter specifying we want a query parameter with the `:property` placeholder:
We can use this filter specifying we want a query parameter with the `:property` placeholder:

```php
namespace App\ApiResource;
Expand Down Expand Up @@ -127,7 +127,7 @@ When you declare a parameter on top of a class, you need to specify it's key.

### The :property placeholder

When used on a Parameter, the `:property` placeholder allows to map automatically a parameter to the readable properties of your resource.
When used on a Parameter, the `:property` placeholder allows to map automatically a parameter to the readable properties of your resource.

```php
namespace App\ApiResource;
Expand All @@ -143,7 +143,7 @@ class Book {
}
```

This will declare a query parameter for each property (id, title and author) calling the SearchFilter.
This will declare a query parameter for each property (ID, title and author) calling the SearchFilter.

This is especially useful for sort filters where you'd like to use `?sort[name]=asc`:

Expand All @@ -161,7 +161,7 @@ class Book {
}
```

### Documentation
### Documentation

A parameter is quite close to its documentation and you can specify the JSON Schema and/or the OpenAPI documentation:

Expand Down Expand Up @@ -226,7 +226,7 @@ use ApiPlatform\Metadata\QueryParameter;
],
)]
class ValidateParameter {}
```
```

You can also use your own constraint by setting the `constraints` option on a Parameter. In that case we won't setup the automatic validation for you and it'll replace our defaults.

Expand Down Expand Up @@ -299,7 +299,7 @@ final class SearchTextAndDateFilter implements FilterInterface
}
```

This can be used with parameters using attributes:
This can be used with parameters using attributes:

```php
namespace App\Entity;
Expand Down Expand Up @@ -2006,243 +2006,3 @@ The next filters are not related to how the data is fetched but rather to how th
#[ApiFilter(GroupFilter::class, arguments: ['parameterName' => 'foobargroups'])]
```

## Parameters

An option exists to declare parameters on a Resource or an Operation through the `parameters` property.

```php
namespace App\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
// This parameter "page" works only on /books
#[GetCollection(uriTemplate: '/books', parameters: ['page' => new QueryParameter])]
// This parameter is available on every operations, key is mandatory
#[QueryParameter(key: 'q', property: 'freetextQuery')]
class Book {}
```

Note that `property` is used to document the Hydra view. You can also specify an [OpenAPI Parameter](https://api-platform.com/docs/references/OpenApi/Model/Parameter/) if needed.

### Alter the Operation via a parameter

A parameter can alter the current Operation context, to do so use a `ApiPlatform\State\ParameterProviderInterface`:

```php
class GroupsParameterProvider implements ParameterProviderInterface {
public function provider(Parameter $parameter, array $uriVariables = [], array $context = []): HttpOperation
{
$request = $context['request'];
return $context['operation']->withNormalizationContext(['groups' => $request->query->all('groups')]);
}
}
```

Then plug this provider on your parameter:

```php
namespace App\ApiResource;
use ApiPlatform\Metadata\HeaderParameter;
#[Get(parameters: ['groups' => new HeaderParameter(provider: GroupsParameterProvider::class)])]
class Book {
public string $id;
}
```

If you don't have autoconfiguration enabled, declare the parameter as a tagged service:

```yaml
services:
ApiPlatform\Tests\Fixtures\TestBundle\Parameter\CustomGroupParameterProvider:
tags:
- name: 'api_platform.parameter_provider'
key: 'ApiPlatform\Tests\Fixtures\TestBundle\Parameter\CustomGroupParameterProvider'
```

### Call a filter

A Parameter can also call a filter and works on filters that impact the data persistence layer (Doctrine ORM and ODM filters are supported). Let's assume, that we have an Order filter declared:

```yaml
# config/services.yaml
services:
offer.order_filter:
parent: 'api_platform.doctrine.orm.order_filter'
arguments:
$properties: { id: ~, name: ~ }
$orderParameterName: order
tags: [ 'api_platform.filter' ]
```

We can use this filter specifying we want a query parameter with the `:property` placeholder:

```php
namespace App\ApiResource;
#[GetCollection(
uriTemplate: 'orders',
parameters: [
'order[:property]' => new QueryParameter(filter: 'offer.order_filter'),
]
)
class Offer {
public string $id;
public string $name;
}
```

### Decorate a Doctrine filter

A filter that implements the `ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface` interface can be decorated:

```php
namespace App\Doctrine\Filter;
use ApiPlatform\Doctrine\Common\Filter\PropertyAwareFilterInterface;
use ApiPlatform\Doctrine\Orm\Filter\FilterInterface;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
final class SearchTextAndDateFilter implements FilterInterface
{
public function __construct(#[Autowire('@api_platform.doctrine.orm.search_filter.instance')] readonly FilterInterface $searchFilter, #[Autowire('@api_platform.doctrine.orm.date_filter.instance')] readonly FilterInterface $dateFilter, protected ?array $properties = null, private array $dateFilterProperties = [], private array $searchFilterProperties = [])
{
}
// This function is only used to hook in documentation generators (supported by Swagger and Hydra)
public function getDescription(string $resourceClass): array
{
if ($this->searchFilter instanceof PropertyAwareFilterInterface) {
$this->searchFilter->setProperties($this->searchFilterProperties);
}
if ($this->dateFilter instanceof PropertyAwareFilterInterface) {
$this->dateFilter->setProperties($this->dateFilterProperties);
}
return array_merge($this->searchFilter->getDescription($resourceClass), $this->dateFilter->getDescription($resourceClass));
}
public function apply(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
{
if ($this->searchFilter instanceof PropertyAwareFilterInterface) {
$this->searchFilter->setProperties($this->searchFilterProperties);
}
if ($this->dateFilter instanceof PropertyAwareFilterInterface) {
$this->dateFilter->setProperties($this->dateFilterProperties);
}
$this->searchFilter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $context['filters']['searchOnTextAndDate']] + $context);
$this->dateFilter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operation, ['filters' => $context['filters']['searchOnTextAndDate']] + $context);
}
}
```

This can be used with parameters using attributes:

```php
namespace App\Entity;
#[GetCollection(
uriTemplate: 'search_filter_parameter{._format}',
parameters: [
'searchOnTextAndDate[:property]' => new QueryParameter(filter: 'app_filter_date_and_search'),
]
)]
// Note that we link the parameter filter and this filter using the "alias" option:
#[ApiFilter(SearchTextAndDateFilter::class, alias: 'app_filter_date_and_search', properties: ['foo', 'createdAt'], arguments: ['dateFilterProperties' => ['createdAt' => 'exclude_null'], 'searchFilterProperties' => ['foo' => 'exact']])]
#[ORM\Entity]
class SearchFilterParameter
{
/**
* @var int The id
*/
#[ORM\Column(type: 'integer')]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;
#[ORM\Column(type: 'string')]
private string $foo = '';
#[ORM\Column(type: 'datetime_immutable', nullable: true)]
private ?\DateTimeImmutable $createdAt = null;
public function getId(): ?int
{
return $this->id;
}
public function getFoo(): string
{
return $this->foo;
}
public function setFoo(string $foo): void
{
$this->foo = $foo;
}
public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeImmutable $createdAt): void
{
$this->createdAt = $createdAt;
}
}
```

### Parameter validation

Parameter validation is automatic based on the configuration for example:

```php
<?php
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
#[GetCollection(
uriTemplate: 'validate_parameters{._format}',
parameters: [
'enum' => new QueryParameter(schema: ['enum' => ['a', 'b'], 'uniqueItems' => true]),
'num' => new QueryParameter(schema: ['minimum' => 1, 'maximum' => 3]),
'exclusiveNum' => new QueryParameter(schema: ['exclusiveMinimum' => 1, 'exclusiveMaximum' => 3]),
'blank' => new QueryParameter(openApi: new OpenApiParameter(name: 'blank', in: 'query', allowEmptyValue: false)),
'length' => new QueryParameter(schema: ['maxLength' => 1, 'minLength' => 3]),
'array' => new QueryParameter(schema: ['minItems' => 2, 'maxItems' => 3]),
'multipleOf' => new QueryParameter(schema: ['multipleOf' => 2]),
'pattern' => new QueryParameter(schema: ['pattern' => '/\d/']),
'required' => new QueryParameter(required: true),
],
)]
class ValidateParameter {}
```

You can also use your own constraint by setting the `constraints` option on a Parameter. In that case we won't setup the automatic validation for you and it'll replace our defaults.


### Parameter security

Parameters may have security checks:

``` php
<?php
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\HeaderParameter;
use ApiPlatform\Metadata\QueryParameter;
#[GetCollection(
uriTemplate: 'security_parameters{._format}',
parameters: [
'sensitive' => new QueryParameter(security: 'is_granted("ROLE_ADMIN")'),
'auth' => new HeaderParameter(security: '"secretKey" == auth[0]'),
],
)]
class SecurityParameter {}
```
7 changes: 7 additions & 0 deletions core/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ docker compose exec php \
bin/console api:swagger:export
```

It is also possible to use OpenAPI v3.0.0 format:

```console
docker compose exec php \
bin/console api:openapi:export --spec-version=3.0.0
```

## Overriding the OpenAPI Specification

Symfony allows to [decorate services](https://symfony.com/doc/current/service_container/service_decoration.html), here we
Expand Down
Loading

0 comments on commit 9506e8e

Please sign in to comment.