Last time we found out what API Platform is (read: https://codete.com/blog/api-platform-rest-api-from-scratch-vol-1/), we downloaded and configured the project, got some knowledge about validation and available data formats, prepared our first entity and introduced pagination and filtering.

We are ready to continue implementation, so today we will focus on more advanced features like Subresources, Normalization/Denormalization and Nested objects, features of framework which prevent us from reinventing a wheel on data delivery and transport tier and let us focus mainly on business logic implementation.

Enjoy your reading!

Subresources

We always have to deal with some nested objects in our applications, API Platform has solution to this too.

First, we will add  a new entity like Role.php file by command

role creating rest api

 

The creator even allows us to configure relation between Account and Role. You can do this by choosing relation type field. It will help you decide what kind of relation you are supposed to use. You can use specific relation type like ManyToMany, if you already know what it’s supposed to be.

role relation creating rest api

 

As the result, you get such a class:

# example/api/src/Entity/Role.php

 

And some new stuff related with Role is added on Account side:

# example/api/src/Entity/Account.php

 

Run the following commands in your terminal to update database schema:

Let’s add some fixtures.

First, add the following line to AccountFixtures.php file

# example/api/src/DataFixtures/AccountFixtures.php

 

It will allow us to use the created entities in another fixture class

Now create fixtures class for Role.

# example/api/src/DataFixtures/RoleFixtures.php

 

Then, we can load data to the database.

subresources rest api

Whenever you get a list of accounts or a specific account, you will see the roles property in response.

subresources get accounts list rest api

If we want to have nested resources endpoints like /accounts/{id}/roles to get account roles via special endpoint without using any filtering or custom actions, we have to add ApiSubresource annotation on the roles property in Account.php file.

# example/api/src/Entity/Account.php

 

and accounts property in Role.php file.

# example/api/src/Entity/Role.php

 

The result is:

subresources get roles list of account rest api

Additionally if you want to filter roles by active user, it isn’t a problem. The only thing you have to do is to add ApiFilter annotation in Role.php file like this:

# example/api/src/Entity/Role.php

 

and admire 🙂

subresources filter by nested rest api

Normalization/Denormalization

Another great feature is the ability to control normalization and denormalization of request and response properties.

Let’s assume that we want to be able to set up password during Account creation (normalization) and hide password while obtaining object or collection (denormalization).

First, add configuration in ApiResource annotation:

And add Groups annotation to each property which you want to expose in a specific action e.g. POST

# example/api/src/Entity/Account.php

 

Role.php file has to be updated too, because while obtaining accounts we might not see any properties of nested roles:

# example/api/src/Entity/Role.php

 

Now, let’s add password property to Account.php file.

# example/api/src/Entity/Account.php

 

As usual, update database

Important! If you have any errors during an update of schema, remove all records from database before you run the following commands because we do not want password to be null or set some default value. It is enough for our test purposes.

Log in to the database:

And call SQL query like:

Now

Update AccountFixtures.php file like this

# example/api/src/DataFixtures/AccountFixtures.php

 

And load data to database:

You can see the result of your work in sandbox code snippets. As you can see, password property appears only in POST and PUT methods.

serialization deserialization rest api

But what in case when we want to be able to update only password and isActive instead of username?

We can deal with it by configuring denormalization_context for put method in itemOperations and adding a new group to the Account.php file like this:

# example/api/src/Entity/Account.php

 

As a result, you see new code snippet in PUT method of /accounts:

serialization deserialization update rest api

Now you are able to update only isActive and password properties:

Nested objects Normalization/Denormalization

By default, nested objects are presented by URI

But you can achieve something like this

This approach allows you to improve performance by avoiding execution of many queries to get whole data. In this example we can save one request but what in such a  case when we have a list of hundreds of items which have relation to hundreds of nested objects? What in such a case when we want to get everything in one query?

For now it looks like this:

nested serialization deserialization before rest api

Let’s prepare our model to get the whole structure in one request.

Add account normalization group “account.read” to Role.php file properties like this:

# example/api/src/Entity/Role.php

 

Now you can see the result:

nested serialization deserialization after rest api

We can use this approach in denormalization either, when we want to add a nested object to a new or existing object. The only thing which has to be done is adding denormalization group  “account.write” and “account.update” to properties of the nested object, in our example it will be something like this in Role.php file:

# example/api/src/Entity/Role.php

 

and Account.php file

# example/api/src/Entity/Account.php

 

This configuration allows to:

– Add Role to existing Account

Content-Type: application/json

Method: PUT

– Save a new Account with already created Role(s)

Content-Type: application/json

Method: POST

– Create a completely new Role object if there is no id in the request

Content-Type: application/json

Method: PUT

The last example is not a super safe one in our case but it shows how nested denormalization works.

If you provide Role id, then object is loaded from database and it is set as a relation to an actual Account object.

In case that you do not provide Role id, a new object along with provided parameters, is created and set as a relation to an actual Account object.

Other interesting features

  1. Full control because of disabling, enabling, overwriting endpoints or adding completely custom ones https://api-platform.com/docs/core/operations#creating-custom-operations-and-controllers
  2. It’s possible to create a common Symfony Controller with Twig templates if somebody feels that it is a good idea…
  3. Also API Platform offers Admin Panel available by default visible on https://localhost:81/ and ,of course, it is customizable.
  4. Good news is that if application is based on Symfony then it is possible (depends on version) to install a standalone lib https://github.com/api-platform/core and use features of the Framework.
  5. Possibility of using included in Symfony features like Listeners, Security and so on.

Summary

As you can see, we do not have to write so much code to prepare a fully working application. You can see differences between commits, there is more code written in JavaScript (Postman) instead of PHP.

I hope you have gained some interesting knowledge which will help you in developing your applications faster and better.

If you have some questions or feedback do not hesitate to contact me.

Maybe next time we will talk a bit about Front-end Client Generator which is another great feature of API Platform second version.

BONUS

  • Full example

https://github.com/slawomirkania/example/tree/step-12

As before, just run following commands to start the application:

If you have some problems with php container, check error by running the following command:

Resolve problems and then try again.

  • Running Postman tests

I have prepared some test cases which you can run on your own environment.

Download tests from:

https://github.com/slawomirkania/example/blob/step-12/api/tests/tests.zip

Install Newman: https://www.getpostman.com/docs/v6/postman/collection_runs/command_line_integration_with_newman

and run tests from command line:

or import test collection and environment to Desktop version of Postman.

slawomir.kania

Software Engineer experienced in REST and Microservices solutions. Interested in modern web technologies.