The journey
V3
@soyuka
SOYUKA
ANTOINE
@soyuka
@soyuka
@soyuka
2015
API Platform sees its first version
@soyuka
2018
Hacktoberfest: Sylius + API Platform = <3
@soyuka
Sylius and API Platform
@soyuka
2019
EU FOSSA Hackathon
@soyuka
Subresources
@soyuka
2019
Vulcain introduction at the SymfonyCon
@soyuka
2020
And much more! Check @dunglas blog post to catch up!
@soyuka
2020
🤔 no Subresource-related � changes?
@soyuka
Subresource ADR
Nov 19, 2020 - Feb 20, 2020
@soyuka
@soyuka
/**
* @ApiResource(path="/users")
* @ApiResource(path="/companies/{companyId}/users")
*/
class User {
/** @ApiProperty(identifier=true) */
public int $id;
/** @var Company[] */
public array $companies = [];
}
Get Users belonging to the company on (/companies/1/users)
@soyuka
Resource Identifier
@soyuka
/**
* @ApiResource(
* path="/companies/{companyId}/users",
* identifiers={"companyId": {Company::class, "id"}, "id": {User::class, "id"}}
* )
*/
class User {
/** @ApiProperty(identifier=true) */
public int $id;
/** @var Company[] */
public array $companies = [];
}
ADR: 0001-resource-identifiers �Nov 26, 2020
@soyuka
Resource definition
The API Platform @ApiResource annotation was initially created to declare a Resource as defined in Roy Fiedling's dissertation about REST in correlation with RFC 7231 about HTTP Semantics.
Feb 20, 2021
@soyuka
Resource definition
#[ApiResource]
class Person {}
@soyuka
Resource representation
{
"@context": "/contexts/Person",
"@type": "https://schema.org/Person",
"@id": "/persons/1",
"address": "/addresses/1",
"email": "mailto:jane-doe@xyz.edu",
"name": "Jane Doe"
}
Person application/ld+json
@soyuka
Resource representation
{
"_links": {
"self": {
"href": "/persons/1"
},
"address": {
"href": "/addresses/1",
}
},
"id": "1",
"name": "Jane Doe",
"email": "mailto:jane-doe@xyz.edu"
}
Person application/hal+json
@soyuka
Resource definition
#[ApiResource(operations=[
new Get(),
new Post(),
])]
class Person
{
#[ApiProperty(identifier=true)]
public $id;
}
PHP 8.1
@soyuka
Resource definition
#[ApiResource]
#[Get]
#[Put]
#[Patch]�#[Delete]
#[GetCollection]
#[Post]
class Person {}
PHP 8
@soyuka
#[ApiResource(
uriTemplate: "/companies/{companyId}/users/{id}",
uriVariables: [
"companyId" => ["class" => Company::class, "identifiers" => ["id"], "property" => "user"],
"id" => ["class" => Person::class, "identifiers" => ["id"]]
]
)]
class Person {
public $id;
public Company $company;
}
@soyuka
namespace ApiPlatform\Metadata;
class Operation
{
public const METHOD_GET = 'GET';
public const METHOD_POST = 'POST';
public const METHOD_PUT = 'PUT';
public const METHOD_PATCH = 'PATCH';
public const METHOD_DELETE = 'DELETE';
protected string $method;
protected string $uriTemplate;
protected ?string $description = null;
protected ?array $types = null;
}
@soyuka
Codebase changes
BEFORE (< 2.7)
For each @ApiResource declared on a class we have one ResourceMetadata (via the ResourceMetadataFactory). A ResourceMetadata has item and collection operations.
interface ResourceMetadataFactoryInterface
{
/** @throws ResourceClassNotFoundException */
public function create(string $resourceClass): ResourceMetadata;
}
@soyuka
Codebase changes
AFTER
There are multiple #[ApiResource] on a class. Each having operations.
interface ResourceMetadataCollectionFactoryInterface
{
/** @throws ResourceClassNotFoundException */
public function create(string $resourceClass): ResourceMetadataCollection;
}
@soyuka
@soyuka
Codebase changes
Jun 11, 2021
@soyuka
Codebase changes
if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {
trigger_deprecation('api-platform/core', '2.7', sprintf(� 'Use "%s" instead of "%s".', � ResourceMetadataCollectionFactoryInterface::class,� ResourceMetadataFactoryInterface::class)� );
}
@soyuka
Codebase changes
if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {
$operation = $this->resourceMetadataFactory->create($resourceClass)->getOperation();
}
// else Legacy code
@soyuka
@soyuka
Iterations
Aug 10, 2021
@soyuka
How it started
@soyuka
Iterations
Aug 10, 2021
@soyuka
What changed
API Platform 3.0 codebase will have less code === less bugs
@soyuka
DataProvider
Request GET /persons/1
DataProvider::getItem(Person::class, [‘id’ => 1])
Person 1
Response 200: {“@id”: “/persons/1”}
@soyuka
Goodbye DataProvider
interface ItemDataProviderInterface
{
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []);
}
interface CollectionDataProviderInterface
{
public function getCollection(string $resourceClass, string $operationName = null);
}
interface SubresourceDataProviderInterface
{
public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null);
}
@soyuka
Welcome State Provider
namespace ApiPlatform\State;
interface ProviderInterface
{
public function provide(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []);
public function supports(string $resourceClass, array $identifiers = [], ?string $operationName = null, array $context = []): bool;
}
@soyuka
DataPersister
Request POST /persons/1��{“name”: “Bob”}
DataPersister::persist(Person::class, Person(Bob))
Save to database
Response 201: {“@id”: “/persons/1”}
@soyuka
Goodbye Data Persister
interface DataPersisterInterface
{
public function supports($data): bool;
public function persist($data);
public function remove($data);
}
@soyuka
Welcome State Processor
namespace ApiPlatform\State;
interface ProcessorInterface
{
public function resumable(?string $operationName = null, array $context = []): bool;
public function supports($data, array $identifiers = [], ?string $operationName = null, array $context = []): bool;
public function process($data, array $identifiers = [], ?string $operationName = null, array $context = []);
}
@soyuka
Edge Side APIs: Example
{
"@id": "/event_reservations",
"@type": "Hydra:collection",
"@context": "/contexts/EventReservation",
"hydra:member": [
"/event_reservations/7509122C",
"/event_reservations/E894B023",
// ...
]
}
Collection
@soyuka
Edge Side APIs: Example
#[ApiResource(iriOnly: true)]
class EventReservations
{
#[ApiProperty(types="hydra:member")]
public iterable $member = [];
}
Collection
@soyuka
Edge Side APIs: Example
{
"@id": "/event_reservations/E894B023",
"@type": "https://schema.org/EventReservation",
"@context": "/contexts/EventReservation",
"reservationNumber": "E894B023",
"underName": "/people/1",
"reservationFor": "/events/1",
"details": "/event_reservations/E894B023/details"
}
Item
@soyuka
Edge Side APIs: Example
#[ApiResource(type: 'https://schema.org/EventReservation')]
class EventReservation
{
#[ApiProperty(identifier=true)]
public string $reservationNumber;
public People $underName;
public Event $reservationFor;
public EventDetail $details;
}
Item
@soyuka
#[ApiResource(
uriTemplate: '/event_reservation/{id}/details',
uriVariables: [
'id' => ['class' => EventReservation::class, 'identifiers' => ['id'], 'property' => 'details’]
]
)]
class EventDetail
{
// ...
}
Subresource
@soyuka
Upgrade
We got you covered!
@soyuka
Many thanks to Thomas Votruba, Romain Allanot and Loic Boursin
composer config minimum-stability dev
composer require --dev -W rector/rector-src
php bin/console api:rector:upgrade MyResource.php
@soyuka
@soyuka
2. Transform resources
@soyuka
Deprecations
API Platform 2.7
@soyuka
Metadata changes
Before | After |
iri: 'https://schema.org/Book' | types: ['https://schema.org/Book'] |
path: '/books/{id}/publication' | uriTemplate: '/books/{id}/publication' |
identifiers: [] | uriVariables: [] |
attributes: [] | extraProperties: [] |
attributes: ['validation_groups' => ['a', 'b']] | validationContext: ['groups' => ['a', 'b']] |
@soyuka
Keep the legacy metadata services with this flag:
api_platform:
metadata_backward_compatibility_layer: true
Defaults to true in 2.7
Change it to `false` to be ready for 3.0�Removed in 3.0
@soyuka
Breaking changes
API Platform 3.0 (experimental)
@soyuka
HTTP GET /companies/1/users/2
{
"@id": "/companies/1/users/2"� "@context": "User"
...
}
ApiResource on /companies/{id}/users/{userId}
@soyuka
HTTP GET /companies/1/users/2
{
"@id": "/users/2"� "@context": "User"
...
}
ApiResource on /companies/{id}/users/{userId}
@soyuka
@soyuka
HTTP GET /companies/1/users/2
�301 Location /users/2
{
"@id": "/users/2"� "@context": "User"
...
}
ApiResource on /companies/{id}/users/{userId}
@soyuka
When?
@soyuka
How to help?
composer config minimum-stability dev
composer require api-platform/core:dev-main
2. Get in touch on github.com/api-platform/core and check 2.7 and 3.0 projects
@soyuka
Thanks
Loic Boursin, Romain Allanot, Kevin Dunglas, Alan Poulain, Soiland Reyes, Tomas Votruba (rector), Cecile Helary Hamerel, Laury Sorriaux, Vincent Chalamon, Pierre Thibaudeau, Samuel Bigard, Stian Soiland-Reyes (university of Manchester), Nina, Thomas Colin, La coopérative Les Tilleuls, my Github sponsors
@soyuka
ENJOY�the
@soyuka