1 of 63

The journey

V3

@soyuka

2 of 63

SOYUKA

ANTOINE

  • Full Stack developer at Les Tilleuls
  • Open source advocate
  • API Platform core contributor
  • VIM user
  • Motorbike, juggling, unicycling, climbing

@soyuka

3 of 63

@soyuka

4 of 63

@soyuka

5 of 63

2015

  • v0.0.1 out with symfony 2.7
  • JsonLdApiBundle
  • Filters, Serialization, less boilerplate then with other API bundles
  • hypermedia support (json-ld, schema.org etc.)

API Platform sees its first version

@soyuka

6 of 63

2018

  • Input/Output feature
  • MongoDB support
  • OpenAPI 3.0 (goodbye Swagger)
  • Next release: 2.4 !

Hacktoberfest: Sylius + API Platform = <3

@soyuka

7 of 63

Sylius and API Platform

@soyuka

8 of 63

2019

  • 20 merged PR
  • 10 new PRs
  • 87 issue closed
  • How can we improve subresources ?

EU FOSSA Hackathon

@soyuka

9 of 63

Subresources

@soyuka

10 of 63

2019

  • Works best with Subresources
  • Edge Side APIs is the future

Vulcain introduction at the SymfonyCon

@soyuka

11 of 63

2020

And much more! Check @dunglas blog post to catch up!

  • Architectural Decision Records
  • PHP 8 Attributes support
  • Caddy integration
  • Activity Pub component

@soyuka

12 of 63

2020

🤔 no Subresource-related � changes?

@soyuka

13 of 63

Subresource ADR

Nov 19, 2020 - Feb 20, 2020

@soyuka

14 of 63

@soyuka

15 of 63

/**

* @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

16 of 63

Resource Identifier

@soyuka

17 of 63

/**

* @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

18 of 63

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

19 of 63

Resource definition

#[ApiResource]

class Person {}

@soyuka

20 of 63

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

21 of 63

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

22 of 63

Resource definition

#[ApiResource(operations=[

new Get(),

new Post(),

])]

class Person

{

#[ApiProperty(identifier=true)]

public $id;

}

PHP 8.1

@soyuka

23 of 63

Resource definition

#[ApiResource]

#[Get]

#[Put]

#[Patch]�#[Delete]

#[GetCollection]

#[Post]

class Person {}

PHP 8

@soyuka

24 of 63

#[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

25 of 63

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

26 of 63

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

27 of 63

Codebase changes

AFTER

There are multiple #[ApiResource] on a class. Each having operations.

interface ResourceMetadataCollectionFactoryInterface

{

/** @throws ResourceClassNotFoundException */

public function create(string $resourceClass): ResourceMetadataCollection;

}

@soyuka

28 of 63

@soyuka

29 of 63

Codebase changes

Jun 11, 2021

@soyuka

30 of 63

Codebase changes

if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {

trigger_deprecation('api-platform/core', '2.7', sprintf(� 'Use "%s" instead of "%s".', � ResourceMetadataCollectionFactoryInterface::class,� ResourceMetadataFactoryInterface::class)� );

}

@soyuka

31 of 63

Codebase changes

if ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {

$operation = $this->resourceMetadataFactory->create($resourceClass)->getOperation();

}

// else Legacy code

@soyuka

32 of 63

@soyuka

33 of 63

Iterations

Aug 10, 2021

@soyuka

34 of 63

How it started

@soyuka

35 of 63

Iterations

Aug 10, 2021

@soyuka

36 of 63

What changed

  • Resource definition (more control over URLs)
  • ApiSubresource removal, use multiple Resource definitions instead
  • New state interfaces replacing DataProvider and DataPersister
  • Write support on subresources
  • Multiple RDF types definition

API Platform 3.0 codebase will have less code === less bugs

@soyuka

37 of 63

DataProvider

Request GET /persons/1

DataProvider::getItem(Person::class, [‘id’ => 1])

Person 1

Response 200: {“@id”: “/persons/1”}

@soyuka

38 of 63

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

39 of 63

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

40 of 63

DataPersister

Request POST /persons/1��{“name”: “Bob”}

DataPersister::persist(Person::class, Person(Bob))

Save to database

Response 201: {“@id”: “/persons/1”}

@soyuka

41 of 63

Goodbye Data Persister

interface DataPersisterInterface

{

public function supports($data): bool;

public function persist($data);

public function remove($data);

}

@soyuka

42 of 63

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

43 of 63

Edge Side APIs: Example

{

"@id": "/event_reservations",

"@type": "Hydra:collection",

"@context": "/contexts/EventReservation",

"hydra:member": [

"/event_reservations/7509122C",

"/event_reservations/E894B023",

// ...

]

}

Collection

@soyuka

44 of 63

Edge Side APIs: Example

#[ApiResource(iriOnly: true)]

class EventReservations

{

#[ApiProperty(types="hydra:member")]

public iterable $member = [];

}

Collection

@soyuka

45 of 63

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

46 of 63

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

47 of 63

#[ApiResource(

uriTemplate: '/event_reservation/{id}/details',

uriVariables: [

'id' => ['class' => EventReservation::class, 'identifiers' => ['id'], 'property' => 'details’]

]

)]

class EventDetail

{

// ...

}

Subresource

@soyuka

48 of 63

Upgrade

  • Rector scripts to update metadata
  • An upgrade guide
  • version 2.7 with deprecations
  • Detailed changelog
  • Updated documentation

We got you covered!

@soyuka

49 of 63

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

50 of 63

  1. Transform subresources

@soyuka

51 of 63

2. Transform resources

@soyuka

52 of 63

Deprecations

  • Symfony backward compatibility promise
  • ApiPlatform\Core => ApiPlatform
  • New interfaces (IriConverter, IdentifiersExtractor)
  • IdentifierConverter => IdentifierTransformer
  • Metadata changes

API Platform 2.7

@soyuka

53 of 63

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

54 of 63

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

55 of 63

Breaking changes

  • Skip null values
  • Patch support by default
  • ApiSubresource removed
  • Legacy code and legacy services removed
  • Alternate URLs responds with a 301 status code by default

API Platform 3.0 (experimental)

@soyuka

56 of 63

HTTP GET /companies/1/users/2

{

"@id": "/companies/1/users/2"� "@context": "User"

...

}

ApiResource on /companies/{id}/users/{userId}

@soyuka

57 of 63

HTTP GET /companies/1/users/2

{

"@id": "/users/2"� "@context": "User"

...

}

ApiResource on /companies/{id}/users/{userId}

@soyuka

58 of 63

@soyuka

59 of 63

HTTP GET /companies/1/users/2

�301 Location /users/2

{

"@id": "/users/2"� "@context": "User"

...

}

ApiResource on /companies/{id}/users/{userId}

@soyuka

60 of 63

When?

@soyuka

61 of 63

How to help?

composer config minimum-stability dev

composer require api-platform/core:dev-main

  1. Try the main branch:

2. Get in touch on github.com/api-platform/core and check 2.7 and 3.0 projects

@soyuka

62 of 63

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

63 of 63

ENJOY�the

@soyuka