Mathias Arlaud
Co-Founder & COO @Bakslash - Co-Founder & CTO @Synegram
@matarld
mtarld
les-tilleuls.coop
Act 1
/2015
JsonLdApiBundle
2015
2016
API Platform
Full CRUD, data validation, pagination, sorting, filtering, hypermedia entrypoint, ...
~200k installs
Act 2
Fix a ton of issues
Various fixes
- Full refactoring
- Use PHP 7
- Add support for content negotiation
- Add Swagger/OpenAPI support
- Add HAL support
- Full rewrite of the metadata system (annotations, YAML and XML formats support)
- Remove the event system in favor of the builtin Symfony kernel's events
- Use the ADR pattern
- Fix a ton of issues
- Properties mapping with XML/YAML is now possible
- Ability to configure and match exceptions with an HTTP status code
- Various fixes and improvements (SwaggerUI, filters, stricter property metadata)
- [...]
2017
2018
2019
2020
2021
2022
2.1: Subresources
2.2: ApiFilter annotation
2.3: Interfaces as resources
2.4: Messenger integration
2.5: PATCH support
2.6: PHP8 attributes
#[ApiResource(
itemOperations: [...],
collectionOperations: [...],
)]
final class Author
{
// ...
}
Hey API Platform! Could you expose this like that?
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
#[ApiResource(
itemOperations: [
'get',
'patch' => [
'normalizationContext' => ['groups' => 'admin'],
'security' => 'is_granted("ROLE_ADMIN")',
],
],
collectionOperations: [
'get',
'anonymize' => [
'method' => 'POST',
'path' => '/author/anonymize',
'security' => 'is_granted("ROLE_ADMIN")',
'normalization_context' => ['groups' => 'admin'],
'inupt' => false,
],
],
)]
final class Author {}
'patch' => [
'normalization_context' => ['groups' => 'admin'],
'security' => 'is_granted("ROLE_ADMIN")',
]
new Patch(
normalizationContext: ['groups' => 'admin'],
security: 'is_granted("ROLE_ADMIN")',
)
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\{Get,GetCollection,Patch,Post};
#[ApiResource(
operations: [
new Get(),
new GetCollection(),
new Patch(
normalizationContext: ['groups' => 'admin'],
security: 'is_granted("ROLE_ADMIN")',
),
new Post(
uriTemplate: '/author/anonymize',
security: 'is_granted("ROLE_ADMIN")',
normalizationContext: ['groups' => 'admin'],
input: false,
),
],
)]
final class Author {}
No more item/collection
namespace App\Metadata;
use ApiPlatform\Metadata\HttpOperation;
final class AdminOperation extends HttpOperation
{
public function __construct(/* ... */)
{
$groups = ($normalizationContext['groups'] ?? [])['admin'];
$normalizationContext['groups'] = $groups;
parent::__construct(
// ...
security: 'is_granted("ROLE_ADMIN")',
normalizationContext: $normalizationContext,
);
}
}
namespace App\Entity;
use App\Metadata\AdminOperation;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\{Get,GetCollection};
#[ApiResource(
operations: [
new Get(),
new GetCollection(),
new AdminOperation(
method: 'PATCH',
),
new AdminOperation(
method: 'POST',
uriTemplate: '/author/anonymize',
input: false,
),
],
)]
final class Author {}
namespace App\Metadata;
use ApiPlatform\Metadata\HttpOperation;
final class AdminOperation extends HttpOperation
{
public function __construct(/* ... */)
{
$groups = ($normalizationContext['groups'] ?? [])['admin'];
$normalizationContext['groups'] = $groups;
parent::__construct(
// ...
security: 'is_granted("ROLE_ADMIN")',
normalizationContext: $normalizationContext,
);
}
}
namespace App\Entity;
use App\Metadata\AdminOperation;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\{Get,GetCollection};
#[ApiResource]
#[Get]
#[GetCollection]
#[AdminOperation(
method: 'PATCH',
normalizationContext: ['groups' => 'read'],
)]
#[AdminOperation(
method: 'POST',
uriTemplate: '/author/anonymize',
input: false,
)]
final class Author {}
namespace App\Entity;
use App\Metadata\AdminOperation;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\{Get,GetCollection};
#[ApiResource(
operations: [
new Get(),
new GetCollection(),
new AdminOperation(
method: 'PATCH',
normalizationContext: ['groups' => 'read'],
),
new AdminOperation(
method: 'POST',
uriTemplate: '/author/anonymize',
input: false,
),
],
)]
final class Author {}
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
#[ApiResource]
final class Author
{
#[ApiSubresource]
private Collection $books;
}
/books
/authors
/authors/{authorId}/books
/authors/{id}/books
ApiResource
ApiSubresource
ApiSubresource
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Link;
#[ApiResource]
#[ApiResource(
uriTemplate: '/authors/{authorId}/books',
uriVariables: [
'authorId' => new Link(fromClass: Author::class, toProperty: 'author'),
],
operations: [new GetCollection(), new Post()],
)]
#[ApiResource(
uriTemplate: '/authors/{authorId}/books/{id}',
uriVariables: [
'authorId' => new Link(fromClass: Author::class, toProperty: 'author'),
'id' => new Link(fromClass: Book::class),
],
operations: [new Get(), new Put(), new Patch(), new Delete()],
)]
final class Book
{
}
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Link;
#[ApiResource]
#[ApiResource(
uriTemplate: '/authors/{authorId}/books',
uriVariables: [
'authorId' => new Link(fromClass: Author::class, toProperty: 'author'),
],
operations: [new GetCollection(), new Post()],
)]
final class Book
{
}
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Link;
#[ApiResource]
final class Book
{
}
ItemDataProviderInterface
CollectionDataProviderInterface
SubresourceDataProviderInterface
RestrictedDataProviderInterface
ContextAwareItemDataProviderInterface
ContextAwareCollectionDataProviderInterface
DataPeristerInterface
ContextAwareDataPeristerInterface
ResumableDataPersisterInterface
ProviderInterface
ProcessorInterface
PATCH /books/1
FooItemDataProvider
DoctrineItemDataProvider
ElasticItemDataProvider
GraphqlItemDataProvider
FooDataPersister
GraphqlDataPersister
DoctrineDataPersister
namespace App\Entity;
use App\State\BookProcessor;
use App\State\BookProvider;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Patch;
#[ApiResource(
operations: [
new Patch(
provider: BookProvider::class,
processor: BookProcessor::class,
),
],
)]
final class Book {}
namespace App\Entity;
use ApiPlatform\Doctrine\Orm\State\ItemProvider;
use ApiPlatform\Doctrine\Common\State\PersistProcessor;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Patch;
#[ApiResource(
operations: [
new Patch(
provider: ItemProvider::class,
processor: PersistProcessor::class,
),
],
)]
final class Book {}
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Patch;
#[ApiResource(
operations: [
new Patch(),
],
)]
final class Book {}
namespace App\State;
use App\Dto\AnotherBookRepresentation;
use ApiPlatform\Metadata\CollectionOperationInterface;
use ApiPlatform\State\ProcessorInterface;
final class BookProvider implements ProviderInterface
{
public function provide(Operation $operation, array $uriVariables = [], array $context = [])
{
if ($operation instanceof CollectionOperationInterface) {
return [new Book(), new Book()];
}
return new Book();
}
}
namespace App\State;
use App\Dto\AnotherBookRepresentation;
use ApiPlatform\Metadata\DeleteOperationInterface;
use ApiPlatform\State\ProcessorInterface;
final class BookProcessor implements ProcessorInterface
{
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
{
if ($operation instanceof DeleteOperationInterface) {
// delete the book
}
// persist the book
}
}
'get_another_representation' => ['output' => ]
GET /books_other/{id}
final class BookDataTransformer
{
public function transform( )
{
return ;
}
}
final class BookItemDataProvider
{
public function getItem(...)
{
return ;
}
}
new GetCollection(output: )
GET /books_other/{id}
final class AnotherReprenstationProvider
{
public function provide(...)
{
return ;
}
}
$ composer require api-platform/core:^2.7
./composer.json has been updated Running composer update api-platform/core Loading composer repositories with package information Updating dependencies
Lock file operations: 0 installs, 1 update, 0 removals
- Upgrading api-platform/core (v2.6.8 => v2.7.0-beta.1)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 0 installs, 1 update, 0 removals
- Upgrading api-platform/core (v2.6.8 => v2.7.0-beta.1): Extracting archive
Generating optimized autoload files
60 packages you are using are looking for funding.
# config/packages/api_platform.yaml
api_platform:
metadata_backward_compatibility_layer: false
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiSubresource;
#[ApiResource(
itemOperations: ['get'],
)]
final class Author {
#[ApiSubresource]
public Collection $books;
}
namespace App\Entity;
use ApiPlatform\Metadata\{ApiResource,Get};
#[ApiResource(
operations: [
new Get(),
],
)]
final class Author {
public Collection $books;
}
$ php bin/console api:upgrade-resource
-use ApiPlatform\Core\Annotation\ApiResource;
+use ApiPlatform\Metadata\Link;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\Get;
#[ApiResource(
- iri: 'http://schema.org/Book',
+ types: ['http://schema.org/Book'],
- itemOperations: ['get'],
- collectionOperations: [],
+ operations: [new Get()],
)]
+#[ApiResource(
+ uriTemplate: '/authors/{id}/books.{_format}',
+ uriVariables: ['id' => new Link(fromClass: Author::class, identifiers: ['id'])],
+ operations: [new GetCollection()],
+)]
final class Book
By Mathias Arlaud