Skip to content
Merged
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
161 changes: 161 additions & 0 deletions core/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,167 @@ Item operations:
| `PATCH` | no | Apply a partial modification to an element | yes |
| `DELETE` | no | Delete an element | yes |

## The HTTP QUERY Operation

[HTTP QUERY](https://www.rfc-editor.org/rfc/rfc10008.html) is a safe, idempotent collection
operation whose criteria are sent in the request body instead of the URI. It is useful when a
collection query is too large or too structured for a URL. API Platform does not enable it by
default; add a `Query` operation explicitly.

Unlike `GET`, a `QUERY` request must include a `Content-Type` header, including when its body is
empty. API Platform supports `application/json` and `application/x-www-form-urlencoded` request
bodies for this operation.

The following operation uses a parameter-driven filter. Although it is declared with
`QueryParameter`, the `name` criterion is sent in the `QUERY` request body, not as `?name=...` in
the URL:

```php
<?php
// api/src/Entity/Book.php
namespace App\Entity;

use ApiPlatform\Doctrine\Orm\Filter\PartialSearchFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Query;
use ApiPlatform\Metadata\QueryParameter;

#[ApiResource(operations: [
new GetCollection(),
new Query(parameters: [
'name' => new QueryParameter(
filter: new PartialSearchFilter(),
property: 'name',
),
]),
])]
class Book
{
// ...
}
```

Call the `QUERY` operation with the same collection URI:

```console
curl -X QUERY https://example.com/books \
-H 'Accept: application/ld+json' \
-H 'Content-Type: application/json' \
--data '{"name":"Dune"}'
```

The parsed values are processed by the same [parameter and filter system](filters.md) as URL query
parameters. This lets existing `QueryParameter` filters describe and apply body criteria without a
custom provider.

### Criteria DTOs

For a structured query, set an `input` class on the operation and put `QueryParameter` attributes on
its properties. API Platform uses that class as the request-body schema and discovers its parameters
to apply their filters:

```php
<?php
// api/src/Dto/BookCriteria.php
namespace App\Dto;

use ApiPlatform\Doctrine\Orm\Filter\PartialSearchFilter;
use ApiPlatform\Metadata\QueryParameter;

final class BookCriteria
{
#[QueryParameter(filter: new PartialSearchFilter())]
public ?string $name = null;
}
```

```php
<?php
// api/src/Entity/Book.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Query;
use App\Dto\BookCriteria;

#[ApiResource(operations: [new Query(input: BookCriteria::class)])]
class Book
{
// ...
}
```

The input class is not deserialized and passed to a state provider by default. With the default
`Query` settings, it documents the body and declares the filter criteria; providers continue to use
the usual provider arguments and request context.

When the query itself is a command-like operation and a processor needs a typed criteria object,
disable the read stage and explicitly enable deserialization and writing. The processor then
receives the deserialized `BookCriteria` object as its `$data` argument:

```php
<?php
// api/src/Entity/Book.php
namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Query;
use App\Dto\BookCriteria;
use App\State\BookCriteriaProcessor;

#[ApiResource(operations: [
new Query(
input: BookCriteria::class,
read: false,
deserialize: true,
write: true,
processor: BookCriteriaProcessor::class,
),
])]
class Book
{
// ...
}
```

```php
<?php
// api/src/State/BookCriteriaProcessor.php
namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Dto\BookCriteria;
use App\Entity\Book;

/** @implements ProcessorInterface<BookCriteria, iterable<Book>> */
final readonly class BookCriteriaProcessor implements ProcessorInterface
{
public function __construct(private BookSearch $bookSearch) {}

public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): iterable
{
if (!$data instanceof BookCriteria) {
throw new \RutimeException('Expected BookCriteria.');
}

return $this->bookSearch->search($data);
}
}
```

This is the processor path: a processor is only called for a safe operation when `write` is set to
`true`. See [State Processors](state-processors.md) for implementing the processor.

### OpenAPI

When exporting an OpenAPI 3.2 document, API Platform represents the operation in the Path Item
Object's `query` field. Its request body lists `application/json` and
`application/x-www-form-urlencoded`; parameter-driven criteria are represented as body properties.
For an `input` criteria class, the request body references that class's input schema. Path and
header parameters remain OpenAPI parameters.

> [!NOTE] The `PATCH` method must be enabled explicitly in the configuration, refer to the
> [Content Negotiation](content-negotiation.md) section for more information.

Expand Down
Loading