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: falsenamespace 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 BookBy Mathias Arlaud