How to migrate from Api Platform v2 to v3?
Grégoire AbachinSébastien Touzé9 min read
How to migrate from Api Platform v2 to v3?
The API Platform team recently announced that it would be dropping support for version 2.x to focus on v3. Although it has opened a Crowdfunding to offer Long Term Support on version 2.7, with Symfony 6.4 being released it is a good idea to prepare to migrate to the latest LTS versions of both Symfony (6.4) and APIPlatform (3.x).
This article aims to share feedback from the API platform upgrades we’ve carried out at Theodo, so that you can do so with complete peace of mind. This article is a complement to the update guide and focuses on evolutions to be made on most code bases. For a more in-depth look, you can find the complete list of BC breaks in the CHANGELOG.
Migrate Api Platform to v2.7 and prepare for v3
Ensure no regression after the upgrade
Before updating Api Platform, you should make sure that your Api Platform routes are fully covered by tests. You can define integration tests with PHPUnit, Behat or Pest, depending on the tool you’re most comfortable with.
Here is an example of a PHPUnit test that checks that the /api/books
route returns a 200
HTTP status code:
<?php
namespace App\Tests;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class BookTest extends WebTestCase{
public function testGetBooks()
{
$client = static::createClient();
$client->request('GET', '/api/books');
$this->assertEquals(200, $client->getResponse()->getStatusCode());
// Add additional assertions here depending on your needs
}
}
For more information about testing Api Platform routes, see the Testing section of the documentation.
A good way to be sure all your routes are covered is to use the --coverage
option for Pest, or --coverage-clover
(or any other format) for PHPUnit. Keep in mind that Api Platform routes are not directly defined in your code, but your custom filters, custom controllers and any other additional logic will be.
To list all available routes, you can use the Symfony command bin/console debug:router
.
Follow Api Platform’s upgrade guide
Api Platform v2.7 is a transition version between v2 and v3. It contains all the features of v3 which you can enable using a flag. All the features that will be removed in v3 are marked as deprecated. It is a good way to migrate your application to v3 progressively.
Api Platform provides a migration guide. It contains a lot of useful information to migrate your application and lists most of the BC breaks and deprecations.
The key steps are:
- Update Api Platform to v2.7 in your
composer.json
file - Run the command api:upgrade-resource to let Api Platform handle most of the BC breaks for you.
- Handle the remaining deprecations listed in the guide and fix the BC breaks
Ensure all resources are correctly migrated
After running the upgrade command, you should check that all your resources are correctly migrated.
Check that all routes are still available
You can do so by running the command api:openapi:export (with the option --output=swagger_docs.json
) and comparing the generated output file with the previous version you had.
Check that all routes are declared in the right order
After the upgrade, you should check that all your routes are declared in the right order. With the new update, the order of the routes becomes important.
For example, if you had multiple routes defined for the same operation, with the following order:
/**
* @ApiResource(
* collectionOperations={
* "get",
* "export"": {
* "method": "GET",
* "path": "/invoices/generate-recap",
* "pagination_enabled": false,
* "controller": GenerateRecapInvoiceController::class,
* },
* },
* )
*/
After running the upgrade command, the export
route will become a GetCollection
operation just like the get
route. The export
route will be declared after the get
route and will therefore never be called:
#[ApiResource(
operations: [
new GetCollection(),
new GetCollection(
uriTemplate: '/invoices/generate-recap',
paginationEnabled: false,
controller: GenerateRecapInvoiceController::class
),
],
)]
To fix this issue, you should declare the export
route, with the most specific route pattern first:
#[ApiResource(
operations: [
- new GetCollection(),
new GetCollection(
uriTemplate: '/invoices/generate-recap',
paginationEnabled: false,
controller: GenerateRecapInvoiceController::class
),
+ new GetCollection(),
],
)]
That error can be hard to spot, especially if you have a lot of routes. This is why integration tests are important for each of your routes.
Migrate your custom Api Platform Event Subscribers
If you had custom Api Platform Event Subscribers to implement some additional logic on your routes and relied on request attributes, you should update them to use the new behavior.
Attributes _api_item_operation_name
, _api_collection_operation_name
, _api_subresource_operation_name
are deprecated and replaced by _api_operation_name
and _api_operation
and will become unavailable when you will switch the metadata_backward_compatibility_layer
flag to false
.
That logic is implemented in the ApiPlatform\Util\AttributesExtractor.php
class, which has been updated and that you can use to retrieve the operation_name
and the operation
.
If you already used the ApiPlatform\Core\Util\AttributesExtractor.php
class, you should update it to use the new one and pay attention to the following changes (from the apiplatform-core github repository):
// ApiPlatform\Util\AttributesExtractor.php (2.6 -> 2.7)
- foreach (OperationType::TYPES as $operationType) {
- $attribute = "_api_{$operationType}_operation_name";
- if (isset($attributes[$attribute])) {
- $result["{$operationType}_operation_name"] = $attributes[$attribute];
- $hasRequestAttributeKey = true;
- break;
+ if (isset($attributes['_api_operation_name'])) {
+ $hasRequestAttributeKey = true;
+ $result['operation_name'] = $attributes['_api_operation_name'];
+ }
+ if (isset($attributes['_api_operation'])) {
+ $result['operation'] = $attributes['_api_operation'];
+ }
+
+ // TODO: remove in 3.0
+ if (!isset($result['operation']) || ($result['operation']->getExtraProperties()['is_legacy_resource_metadata'] ?? false) || ($result['operation']->getExtraProperties()['is_legacy_subresource'] ?? false)) {
+ foreach (OperationType::TYPES as $operationType) {
+ $attribute = "_api_{$operationType}_operation_name";
+ if (isset($attributes[$attribute])) {
+ $result["{$operationType}_operation_name"] = $attributes[$attribute];
+ $hasRequestAttributeKey = true;
+ break;
+ }
Route names
If you relied on route names, note that they have also been changed as part of this update. A route declared as api_books_get_collection
in 2.6 is now declared as _api_/books.{_format}_get_collection
in 2.7.
Migrate your custom route Controllers
If you had custom route Controllers and used a path parameter to retrieve the resource, you should update them to use the new behavior. The path parameter must now be named id
.
#[ApiResource(
operations: [
new Post(
- uriTemplate: '/authors/{selectedAuthor}/book',
+ uriTemplate: '/authors/{id}/book',
controller: CreateBookLinkedToAuthorController::class,
),
],
)]
Migrate from DataProviders and DataPersisters to Providers and Processors
If you had custom DataProviders and DataPersisters, you should update them to use the new Providers and Processors. The same applies to custom DataTransformers, which can be replaced by a combination of a Processor and a Provider.
Refer to the state Providers and state Processors sections of the documentation for more information.
You can find an example of a DataProvider implementation on our blog.
Change the value of the metadata_backward_compatibility_layer
flag
You’re all set to migrate to Api Platform v3!
The last step is to update the metadata_backward_compatibility_layer
flag to false
in your api_platform.yaml
file and ensure that all your tests are still passing.
Migrate to Api Platform v3
Now, you are ready to migrate to Api Platform v3!
We recommend you migrate to the latest stable version of Api Platform v3. You can find the latest stable version and the latest changes in the CHANGELOG.
Prerequisites
Api Platform v3 requires the latest version of PHP and Symfony, you will need to update both before switching to the latest API Platform version.
Api Platform version | PHP Version | Symfony Version |
---|---|---|
2.7 | ≥ 7.1 | ^4.4 |
3.0, 3.1, 3.2 | ≥ 8.1 | ^6.1 |
While only version 8.1 for PHP and 6.1 for Symfony are required, we recommend updating to the latest version that offers Long Term Support. You can find information on actively supported versions here:
For instance, there are cache problems on Api Platform v3.0, so you should consider upgrading directly to the latest minor version available.
Update the bundle declaration
Update the Api Platform bundle declaration in your config/bundles.php
file:
return [
// ...
- ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
+ ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
]
The bundle has been moved from ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle
to ApiPlatform\Symfony\Bundle\ApiPlatformBundle
.
Check all imports still work
Now that you have migrated to Api Platform v3, you should check that all your imports still work. Here are some imports that have changed and are not handled by the upgrade command:
- use ApiPlatform\Core\Action\NotFoundAction;
+ use ApiPlatform\Action\NotFoundAction;
- use ApiPlatform\Core\Annotation\ApiProperty;
+ use ApiPlatform\Metadata\ApiProperty;
- use ApiPlatform\Core\EventListener\EventPriorities;
+ use ApiPlatform\Symfony\EventListener\EventPriorities;
- use ApiPlatform\Core\JsonApi\Serializer\CollectionNormalizer;
+ use ApiPlatform\JsonApi\Serializer\CollectionNormalizer;
Update types on Api Platform classes you extend
Some return types and parameter types have changed on Api Platform classes and / or on Symfony classes. You should update them in your classes that extend them.
For instance, in your custom filters, you should update the type of the $value
parameter in the filterProperty
method to mixed
:
/**
* @param mixed[] $context
*/
protected function filterProperty(
string $property,
- $value,
+ mixed $value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
- string $operationName = null,
+ Operation $operation = null,
array $context = []
): void {
/ ...
}
Update your frontend types
One of the main changes brought about by Api Platform v3 is that null is no longer one of the default return types. This may have consequences for your front-end, especially if you use TypeScript. You may need to update your types to take null values into account.
According to Api Platform’s upgrade guide, this follows a JSON Merge Patch RFC:
For instance, on a UserMetadata object, frontend types should be updated to the following:
export type UserMetadata = {
id: string;
fullName: string;
- firstName: string | null;
- lastName: string | null;
- phoneNumber: string | null;
+ firstName?: string;
+ lastName?: string;
+ phoneNumber?: string;
};
Summary of a successful migration from API Platform v2 to v3
Transitioning from API Platform v2 to v3 is a progressive process, which involves first upgrading to the transitional v2.7, ensuring all relevant routes are covered by tests, and then gradually migrating to v3.
This process requires careful attention to changes in route names and imports, behavior of custom controllers, changes in Providers and Processors, bundle declarations, among other breaking changes.
Once these steps are completed, it is also crucial to verify that front-end types are updated to account for the change in default return types.
After completing this process, your API should be fully updated and ready to benefit from the features and improvements in API Platform v3.
This migration is also a good opportunity to check that all your routes are secure. To do so, we recommend reading our article on Access Control, which is compatible with v3!