The pragmatic API framework
API PLATFORM CON 2024 - LILLE, FRANCE & ONLINE
API PLATFORM CON 2023 - LILLE, FRANCE & ONLINE
API PLATFORM 4
About Me
✔ API Platform release manager
✔ CTO at Les-Tilleuls.coop
✔ Developer, biker, builder
✔ Recent father
Antoine Bluchet
soyuka@phpc.social
@s0yuka
soyuka.me
with API Platform
Pragmatic API
development
Pragmatic API development
✔ Enable (or disable) the features you (don’t) need
✔ Decorate our extension points (IRI Converter, providers, processors, etc.)
✔ API Platform segregates commands and queries
✔ It’s not always CRUD: use operations to create RPC endpoints
Don’t fight the framework
Enable / Disable Features
use ApiPlatformMetadataPost;
#[Post(
read: true, // to call the provider
write: true, // to call the processor
deserialize: false, // transforms the request body to this resource's class
serialize: false, // transforms the provider's response to JSON
validate: false, // validates the user input
queryParameterValidationEnabled: false, // validates query parameters
output: false, // using false will return nothing
openapi: false, // disables the OpenAPI documentation
)]
class Book {}
use ApiPlatformMetadataApiResource;
use AppStateBookProvider;
#[Put(
uriTemplate: '/books/{id}',
allowCreate: true, // create a new value through a PUT operation
provider: BookProvider::class,
processor: BookProcessor::class,
)]
class Book
{
}
State Provider / Processor
use ApiPlatformMetadataOperation;
use ApiPlatformStateProviderInterface;
use ApiPlatformLaravelEloquentStateItemProvider;
use ApiPlatformDoctrineOrmStateItemProvider;
final readonly class BookProvider implements ProviderInterface
{
public function __construct(private ItemProvider $itemProvider) {}
public function provide(Operation $operation, array $uriVariables = [], array $context…
{
return $this->itemProvider->provide($operation, $uriVariables, $context);
}
}
Decorate the state providers
use ApiPlatformMetadataOperation;
use ApiPlatformStateProviderInterface;
final class BookProvider implements ProviderInterface
{
public function provide(Operation $operation, array $uriVariables = [], array $context…
{
$dbh = new PDO('sqlite:/db.sqlite');
$sth = $dbh->prepare('SELECT * FROM book WHERE id = :id');
$sth->bindParam('id', $uriVariables['id'], PDO::PARAM_STR);
$sth->execute();
return !($result = $sth->fetch()) ? null : $result;
}
}
Any data source
use ApiPlatformMetadataOperation;
use ApiPlatformStateProviderInterface;
final class BookProvider implements ProviderInterface
{
public function provide(Operation $operation, array $uriVariables = [], array $context…
{
return json_decode(
file_get_contents('https://demo.api-platform.com/api/books.jsonld')
);
}
}
Any data source
Use your favorite query builder
use ApiPlatformLaravelEloquentStateOptions;
use IlluminateDatabaseEloquentBuilder;
#[GetCollection(
uriTemplate: '/author/{author}/books',
uriVariables: ['author'],
stateOptions: new Options(
handleLinks: [self::class, 'handleLinks']
)
)]
class Employee
{
public static function handleLinks(Builder $builder, array $uriVariables, array $context): Builder
{
$builder->where('books.author_id', $uriVariables['author']);
}
}
AVAILABLE FOR
- Doctrine ORM
- Doctrine ODM
- Laravel Eloquent
- Elasticsearch
- ESQL
use ApiPlatformMetadataPost;
#[Post(uriTemplate: '/rpc', processor: [self::class, 'process'])]
final readonly class PostContent {
public function __construct(public string $name) {}
public static function process(mixed $data) {
// do something with $data
}
}
RPC endpoints
Not convinced?
Why you should use
API Platform
commit
f55cfce9c014655dd5343a5f20dfaf9ec5df2da0
Author: Kévin Dunglas <dunglas@gmail.com>
Date: Tue Jan 20 00:16:57 2015 +0100
first commit
✔ REST oriented
✔ JSON-LD, JSON:API
serialization
✔ OpenAPI, Hydra, JSON
Schema documentation
✔ URI based mechanics (cache,
live updates, relations etc.)
✔ Embedded content
negotiation, validation and
authorization
10 years of existence in January
The power of Edge
Side APIs
1
2
3
4
5
Atomic Resource
Pre-generate
HTTP Cache
Preload
Push
edge-side-api.rocks
Embedding...
@api-platform/ld
{
"@id": "/books/1",
"@type": ["https://schema.org/Book"],
"title": "Hyperion",
"author": "https://localhost/authors/1"
}
fetch('/books/1')
@api-platform/ld
{
"@id": "/books/1",
"@type": ["https://schema.org/Book"],
"title": "Hyperion",
"author": "https://localhost/authors/1"
}
fetch('/authors/1')
@api-platform/ld
{
"@id": "/books/1",
"@type": ["https://schema.org/Book"],
"title": "Hyperion",
"author": "https://localhost/authors/1"
}
fetch('/authors/1')
@api-platform/ld
{
"@id": "/books/1",
"@type": ["https://schema.org/Book"],
"title": "Hyperion",
"author": "https://localhost/authors/1"
}
import ld from '@api-platform/ld'
await ld('/books', {
urlPattern: new URLPattern("/authors/:id", "https://localhost")
})
console.log(books.author?.name)
@api-platform/mercure
import mercure, { close } from "@api-platform/mercure";
await mercure('https://localhost/authors/1', {
onUpdate: (author) => console.log(author)
})
Link: </.well-known/mercure>; rel="mercure"
Mercure
✔ Resource live updates
✔ Compatible with Laravel Echo
✔ High Availability as a service
https://mercure.rocks
@api-platform/ld + @api-
platform/mercure
const fetchFn = (url) => {
return mercure(url).then(d => d.json())
}
ld('/books', { fetchFn: fetchFn, urlPattern: pattern })
.then(books => {
// Do something with books
})
Client tools ✔ Admin generator @api-
platform/admin
✔ Manage topics on a single
EventSource connection
@api-platform/mercure
✔ Replace fetch by @api-
platform/ld for linked
data
And more at api-platform.com
Atomic resources?
✔ Small and atomic documents
✔ Don’t embed resources, use URIs instead
✔ Better browser and share cache hit rate
✔ Clients fetch only what they initially need
and update only small chunks of data
✔ Hypermedia is at the heart of Edge Side
APIs (JSON-LD, Hydra, JSON:API)
Read our white paper !
IRIs collection
GET /books
Content-Type: application/ld+json
{
"@context": "/contexts/Book",
"@id": "/books",
"@type": "Collection",
"totalItems": 4,
"member": [
"/books/1",
"/books/2",
"/books/3",
"/books/4"
]
}
GET /books
Content-Type: application/ld+json
{
"@context": "/contexts/Book",
"@id": "/books",
"@type": "Collection",
"totalItems": 4,
"member": [
{
"@id": "/books/1",
"title": "API Platform",
"author": "/authors/1"
},
{
"@id": "/books/2",
...
},
]
}
use ApiPlatformMetadataApiResource;
#[ApiResource(
normalizationContext: ['iri_only' => true],
)]
final readonly class Book
{
}
IRI Only GET /books
Content-Type: application/ld+json
{
"@context": "/contexts/Book",
"@id": "/books",
"@type": "Collection",
"totalItems": 4,
"member": [
"/books/73",
"/books/74",
"/books/75",
"/books/76"
]
}
use ApiPlatformMetadataGetCollection;
#[ApiResource(
uriTemplate: '/authors/{author}/books',
uriVariables: [
'author' => new Link(
fromClass: Author::class,
toProperty: 'author'
)
],
normalizationContext: ['iri_only' => true],
)]
final readonly class Book
{
}
Group data with IRIs
use ApiPlatformMetadataApiProperty;
#[ApiProperty(
property: 'books',
uriTemplate: '/authors/{author}/books'
)]
final readonly class Author
{
}
GET /authors/1
Content-Type: application/ld+json
{
"@context": "/contexts/Book",
"@id": "/authors/1",
"@type": "Author",
"books": "/authors/1/books"
}
Group data with IRIs
Preload
✔ Preload resources
✔ Server push
✔ Early Hints
✔ Caddy module
https://vulcain.rocks
API Platform 3.4/4.0
What’s new ?
Query parameters
use ApiPlatformLaravelEloquentFilterPartialSearchFilter;
use ApiPlatformLaravelEloquentFilterOrderFilter;
use ApiPlatformMetadataApiResource;
use ApiPlatformMetadataQueryParameter;
#[ApiResource]
#[QueryParameter(key: 'sort[:property]', filter: OrderFilter::class)]
#[QueryParameter(key: ':property', filter: PartialSearchFilter::class)]
class Book extends Model
{
}
Query parameters
Parameters
✔ Header / Query parameter
✔ JSON Schema validation
✔ OpenAPI and Hydra compatible
✔ :property placeholder for better DX
✔ Better filter composition (OrFilter, decorate
Doctrine filters etc.)
✔ Filter aliasing
Error management
use ApiPlatformMetadataGetCollection;
use AppExceptionMyDomainException;
#[GetCollection(errors: [MyDomainException::class])]
class Book
{
}
Error management
✔ Exception can be API resources using
ApiPlatformMetadataError
✔ Specify errors in an operation to document them
with OpenAPI
✔ Read our guides:
➔ api-platform.com/docs/guides/error-provider/
➔ api-platform.com/docs/guides/error-resource/
Exception are automatically rendered
using the Problem details specification
(RFC 7807)!
Laravel support
✔ Most of our existing features are useable in a Laravel
project
✔ Authorization and validation use Laravel FormRequest
and policies
✔ Read the documentation: api-platform.com/docs/laravel/
✔ Send feedbacks!
And way more…
✔ Doctrine ENUM filter
✔ Serializer default context fixes with an
option to change the deserializer type
✔ Static headers
✔ Inflector is now a service
✔ Configure whether to override OpenAPI
responses
Read the CHANGELOG !
From v3 to v4
How to update?
$ composer require api-platform/core:^3.4
Update to API Platform 3.4
api-platform.com/docs/core/upgrade-guide/
FIX DEPRECATIONS!
$ composer update
// composer.json
- "api-platform/core": "^3.4",
+ "api-platform/symfony": "^3.4",
+ "api-platform/doctrine-orm": "^3.4",
Step 1
Step 2
Step 3 $ phpunit
Update to API Platform 3.4
api-platform/symfony
Symfony API Platform integration
api-platform/graphql
Build GraphQL enpoints
api-platform/laravel
Laravel API Platform integration
And more…
Hydra, JSON-LD, OpenAPI,
JSON Schema etc.
api-platform/doctrine-orm
Doctrine ORM bridge
api-platform/doctrine-odm
Doctrine ODM bridge
Choose your components
api_platform:
- use_symfony_listeners: true
+ use_symfony_listeners: false
defaults:
operations:
- ApiPlatformMetadataGet
- ApiPlatformMetadataGetCollection
- ApiPlatformMetadataPost
- - ApiPlatformMetadataPut
- ApiPlatformMetadataPatch
- ApiPlatformMetadataDelete
serializer:
- hydra_prefix: true
+ hydra_prefix: false
What changed?
What changed?
✔ No more PUT operation by default
✔ Hydra prefix disabled
✔ ApiPlatform/{Api,Exception} moved to ApiPlatform/Metadata
✔ Listeners are no longer used*
* only useful when using controllers on API Resources
Hydra prefix?
GET /books
Content-Type: application/ld+json
{
"@context": "/contexts/Book",
"@id": "/books",
"@type": "Collection",
"totalItems": 4,
"member": [
"/books/73",
"/books/74",
"/books/75",
"/books/76"
]
}
GET /books
Content-Type: application/ld+json
{
"@context": "/contexts/Book",
"@id": "/books",
"@type": "hydra:Collection",
"hydra:totalItems": 4,
"hydra:member": [
"/books/73",
"/books/74",
"/books/75",
"/books/76"
]
}
composer require api-platform/symfony:^4.0.0
Install API Platform 4
composer require api-platform/laravel
Thanks!
SPONSOR ME!
github.com/soyuka

API Platform: The Pragmatic API framework

  • 1.
    The pragmatic APIframework API PLATFORM CON 2024 - LILLE, FRANCE & ONLINE
  • 2.
    API PLATFORM CON2023 - LILLE, FRANCE & ONLINE API PLATFORM 4
  • 3.
    About Me ✔ APIPlatform release manager ✔ CTO at Les-Tilleuls.coop ✔ Developer, biker, builder ✔ Recent father Antoine Bluchet soyuka@phpc.social @s0yuka soyuka.me
  • 4.
  • 5.
    Pragmatic API development ✔Enable (or disable) the features you (don’t) need ✔ Decorate our extension points (IRI Converter, providers, processors, etc.) ✔ API Platform segregates commands and queries ✔ It’s not always CRUD: use operations to create RPC endpoints Don’t fight the framework
  • 6.
    Enable / DisableFeatures use ApiPlatformMetadataPost; #[Post( read: true, // to call the provider write: true, // to call the processor deserialize: false, // transforms the request body to this resource's class serialize: false, // transforms the provider's response to JSON validate: false, // validates the user input queryParameterValidationEnabled: false, // validates query parameters output: false, // using false will return nothing openapi: false, // disables the OpenAPI documentation )] class Book {}
  • 7.
    use ApiPlatformMetadataApiResource; use AppStateBookProvider; #[Put( uriTemplate:'/books/{id}', allowCreate: true, // create a new value through a PUT operation provider: BookProvider::class, processor: BookProcessor::class, )] class Book { } State Provider / Processor
  • 8.
    use ApiPlatformMetadataOperation; use ApiPlatformStateProviderInterface; useApiPlatformLaravelEloquentStateItemProvider; use ApiPlatformDoctrineOrmStateItemProvider; final readonly class BookProvider implements ProviderInterface { public function __construct(private ItemProvider $itemProvider) {} public function provide(Operation $operation, array $uriVariables = [], array $context… { return $this->itemProvider->provide($operation, $uriVariables, $context); } } Decorate the state providers
  • 9.
    use ApiPlatformMetadataOperation; use ApiPlatformStateProviderInterface; finalclass BookProvider implements ProviderInterface { public function provide(Operation $operation, array $uriVariables = [], array $context… { $dbh = new PDO('sqlite:/db.sqlite'); $sth = $dbh->prepare('SELECT * FROM book WHERE id = :id'); $sth->bindParam('id', $uriVariables['id'], PDO::PARAM_STR); $sth->execute(); return !($result = $sth->fetch()) ? null : $result; } } Any data source
  • 10.
    use ApiPlatformMetadataOperation; use ApiPlatformStateProviderInterface; finalclass BookProvider implements ProviderInterface { public function provide(Operation $operation, array $uriVariables = [], array $context… { return json_decode( file_get_contents('https://demo.api-platform.com/api/books.jsonld') ); } } Any data source
  • 12.
    Use your favoritequery builder use ApiPlatformLaravelEloquentStateOptions; use IlluminateDatabaseEloquentBuilder; #[GetCollection( uriTemplate: '/author/{author}/books', uriVariables: ['author'], stateOptions: new Options( handleLinks: [self::class, 'handleLinks'] ) )] class Employee { public static function handleLinks(Builder $builder, array $uriVariables, array $context): Builder { $builder->where('books.author_id', $uriVariables['author']); } } AVAILABLE FOR - Doctrine ORM - Doctrine ODM - Laravel Eloquent - Elasticsearch - ESQL
  • 13.
    use ApiPlatformMetadataPost; #[Post(uriTemplate: '/rpc',processor: [self::class, 'process'])] final readonly class PostContent { public function __construct(public string $name) {} public static function process(mixed $data) { // do something with $data } } RPC endpoints
  • 14.
  • 15.
    Why you shoulduse API Platform commit f55cfce9c014655dd5343a5f20dfaf9ec5df2da0 Author: Kévin Dunglas <dunglas@gmail.com> Date: Tue Jan 20 00:16:57 2015 +0100 first commit ✔ REST oriented ✔ JSON-LD, JSON:API serialization ✔ OpenAPI, Hydra, JSON Schema documentation ✔ URI based mechanics (cache, live updates, relations etc.) ✔ Embedded content negotiation, validation and authorization 10 years of existence in January
  • 16.
    The power ofEdge Side APIs
  • 17.
  • 18.
  • 19.
    @api-platform/ld { "@id": "/books/1", "@type": ["https://schema.org/Book"], "title":"Hyperion", "author": "https://localhost/authors/1" } fetch('/books/1')
  • 20.
    @api-platform/ld { "@id": "/books/1", "@type": ["https://schema.org/Book"], "title":"Hyperion", "author": "https://localhost/authors/1" } fetch('/authors/1')
  • 21.
    @api-platform/ld { "@id": "/books/1", "@type": ["https://schema.org/Book"], "title":"Hyperion", "author": "https://localhost/authors/1" } fetch('/authors/1')
  • 22.
    @api-platform/ld { "@id": "/books/1", "@type": ["https://schema.org/Book"], "title":"Hyperion", "author": "https://localhost/authors/1" } import ld from '@api-platform/ld' await ld('/books', { urlPattern: new URLPattern("/authors/:id", "https://localhost") }) console.log(books.author?.name)
  • 23.
    @api-platform/mercure import mercure, {close } from "@api-platform/mercure"; await mercure('https://localhost/authors/1', { onUpdate: (author) => console.log(author) }) Link: </.well-known/mercure>; rel="mercure"
  • 24.
    Mercure ✔ Resource liveupdates ✔ Compatible with Laravel Echo ✔ High Availability as a service https://mercure.rocks
  • 26.
    @api-platform/ld + @api- platform/mercure constfetchFn = (url) => { return mercure(url).then(d => d.json()) } ld('/books', { fetchFn: fetchFn, urlPattern: pattern }) .then(books => { // Do something with books })
  • 27.
    Client tools ✔Admin generator @api- platform/admin ✔ Manage topics on a single EventSource connection @api-platform/mercure ✔ Replace fetch by @api- platform/ld for linked data And more at api-platform.com
  • 29.
    Atomic resources? ✔ Smalland atomic documents ✔ Don’t embed resources, use URIs instead ✔ Better browser and share cache hit rate ✔ Clients fetch only what they initially need and update only small chunks of data ✔ Hypermedia is at the heart of Edge Side APIs (JSON-LD, Hydra, JSON:API) Read our white paper !
  • 30.
    IRIs collection GET /books Content-Type:application/ld+json { "@context": "/contexts/Book", "@id": "/books", "@type": "Collection", "totalItems": 4, "member": [ "/books/1", "/books/2", "/books/3", "/books/4" ] } GET /books Content-Type: application/ld+json { "@context": "/contexts/Book", "@id": "/books", "@type": "Collection", "totalItems": 4, "member": [ { "@id": "/books/1", "title": "API Platform", "author": "/authors/1" }, { "@id": "/books/2", ... }, ] }
  • 31.
    use ApiPlatformMetadataApiResource; #[ApiResource( normalizationContext: ['iri_only'=> true], )] final readonly class Book { } IRI Only GET /books Content-Type: application/ld+json { "@context": "/contexts/Book", "@id": "/books", "@type": "Collection", "totalItems": 4, "member": [ "/books/73", "/books/74", "/books/75", "/books/76" ] }
  • 32.
    use ApiPlatformMetadataGetCollection; #[ApiResource( uriTemplate: '/authors/{author}/books', uriVariables:[ 'author' => new Link( fromClass: Author::class, toProperty: 'author' ) ], normalizationContext: ['iri_only' => true], )] final readonly class Book { } Group data with IRIs
  • 33.
    use ApiPlatformMetadataApiProperty; #[ApiProperty( property: 'books', uriTemplate:'/authors/{author}/books' )] final readonly class Author { } GET /authors/1 Content-Type: application/ld+json { "@context": "/contexts/Book", "@id": "/authors/1", "@type": "Author", "books": "/authors/1/books" } Group data with IRIs
  • 35.
    Preload ✔ Preload resources ✔Server push ✔ Early Hints ✔ Caddy module https://vulcain.rocks
  • 37.
  • 38.
    Query parameters use ApiPlatformLaravelEloquentFilterPartialSearchFilter; useApiPlatformLaravelEloquentFilterOrderFilter; use ApiPlatformMetadataApiResource; use ApiPlatformMetadataQueryParameter; #[ApiResource] #[QueryParameter(key: 'sort[:property]', filter: OrderFilter::class)] #[QueryParameter(key: ':property', filter: PartialSearchFilter::class)] class Book extends Model { }
  • 39.
  • 40.
    Parameters ✔ Header /Query parameter ✔ JSON Schema validation ✔ OpenAPI and Hydra compatible ✔ :property placeholder for better DX ✔ Better filter composition (OrFilter, decorate Doctrine filters etc.) ✔ Filter aliasing
  • 41.
    Error management use ApiPlatformMetadataGetCollection; useAppExceptionMyDomainException; #[GetCollection(errors: [MyDomainException::class])] class Book { }
  • 42.
    Error management ✔ Exceptioncan be API resources using ApiPlatformMetadataError ✔ Specify errors in an operation to document them with OpenAPI ✔ Read our guides: ➔ api-platform.com/docs/guides/error-provider/ ➔ api-platform.com/docs/guides/error-resource/ Exception are automatically rendered using the Problem details specification (RFC 7807)!
  • 44.
    Laravel support ✔ Mostof our existing features are useable in a Laravel project ✔ Authorization and validation use Laravel FormRequest and policies ✔ Read the documentation: api-platform.com/docs/laravel/ ✔ Send feedbacks!
  • 46.
    And way more… ✔Doctrine ENUM filter ✔ Serializer default context fixes with an option to change the deserializer type ✔ Static headers ✔ Inflector is now a service ✔ Configure whether to override OpenAPI responses Read the CHANGELOG !
  • 47.
    From v3 tov4 How to update?
  • 48.
    $ composer requireapi-platform/core:^3.4 Update to API Platform 3.4 api-platform.com/docs/core/upgrade-guide/
  • 49.
  • 50.
    $ composer update //composer.json - "api-platform/core": "^3.4", + "api-platform/symfony": "^3.4", + "api-platform/doctrine-orm": "^3.4", Step 1 Step 2 Step 3 $ phpunit Update to API Platform 3.4
  • 51.
    api-platform/symfony Symfony API Platformintegration api-platform/graphql Build GraphQL enpoints api-platform/laravel Laravel API Platform integration And more… Hydra, JSON-LD, OpenAPI, JSON Schema etc. api-platform/doctrine-orm Doctrine ORM bridge api-platform/doctrine-odm Doctrine ODM bridge Choose your components
  • 52.
    api_platform: - use_symfony_listeners: true +use_symfony_listeners: false defaults: operations: - ApiPlatformMetadataGet - ApiPlatformMetadataGetCollection - ApiPlatformMetadataPost - - ApiPlatformMetadataPut - ApiPlatformMetadataPatch - ApiPlatformMetadataDelete serializer: - hydra_prefix: true + hydra_prefix: false What changed?
  • 53.
    What changed? ✔ Nomore PUT operation by default ✔ Hydra prefix disabled ✔ ApiPlatform/{Api,Exception} moved to ApiPlatform/Metadata ✔ Listeners are no longer used* * only useful when using controllers on API Resources
  • 54.
    Hydra prefix? GET /books Content-Type:application/ld+json { "@context": "/contexts/Book", "@id": "/books", "@type": "Collection", "totalItems": 4, "member": [ "/books/73", "/books/74", "/books/75", "/books/76" ] } GET /books Content-Type: application/ld+json { "@context": "/contexts/Book", "@id": "/books", "@type": "hydra:Collection", "hydra:totalItems": 4, "hydra:member": [ "/books/73", "/books/74", "/books/75", "/books/76" ] }
  • 56.
    composer require api-platform/symfony:^4.0.0 InstallAPI Platform 4 composer require api-platform/laravel
  • 57.

Editor's Notes

  • #13 If you need a controller, use Symfony or Laravel in their standard way.
  • #14 Day 2 16:10 PM - 16:50 PM
  • #18 edge-side-api.rocks demonstration
  • #25 Utiliser RabbitMQ avec Symfony et API Platform. 16h30
  • #34 Day 2 16:10 PM - 16:50 PM
  • #44 15:20 PM - 16:00 PM day 1
  • #49 If a class does not exists it moved to the ApiPlatform\Metadata namespace.
  • #51 If a class does not exists it moved to the ApiPlatform\Metadata namespace.
  • #56 Day2 14:50 PM - 15:30 PM
  • #61 If you need a controller, use Symfony or Laravel in their standard way.
  • #62 If you need a controller, use Symfony or Laravel in their standard way.
  • #63 If you need a controller, use Symfony or Laravel in their standard way.
  • #64 If you need a controller, use Symfony or Laravel in their standard way.