#native_company# #native_desc#
#native_cta#

Creating a REST API in Symfony 3

By Voja Janjic
on November 29, 2016

Nowadays, Web applications are not simply displaying data, they are also integrating with other applications and combining the information and functionalities. A common way to enable easy communication between different applications is the REST API. Let’s see how to achieve that with the Symfony 3 PHP framework.

The Bundles

In Symfony, a REST API can be quickly set up using multiple bundles. We will use rest-bundle and jms/serializer-bundle. Let’s add the following to the composer.json:

"friendsofsymfony/rest-bundle": "2.0.0-BETA2",
"jms/serializer-bundle": "1.1.0"

Run composer install. After that, we need to register the bundles in AppKernel.php:

$bundles = [
            …
            new FOSRestBundleFOSRestBundle(),  
            new JMSSerializerBundleJMSSerializerBundle(),  
	...
        ]; 

Each bundle is like a package that can help us with some part of the functionality. FOSRestBundle provides API-related functionality, while the JMSSerializer helps with serialization and deserialization of entity objects into JSON.

Configuration

The bundles, of course, must be configured. Let’s add the following to app/config/config.yml:

fos_rest:
    body_listener: true
    param_fetcher_listener: true
    format_listener:
        enabled: true
        rules:
          - { path: ^/api, fallback_format: json }
          - { path: ^/, fallback_format: html }
    # Enable serializer for the REST API
    serializer:
        serialize_null: true
    view:
        view_response_listener: force

    # Disable CSRF protection
    disable_csrf_role: ROLE_API

The REST configuration says that all routes under “api” path will be part of the REST API and that the response format will be JSON.

Finally, let’s configure the serializer:

jms_serializer:
    metadata:
        auto_detection: true
    handlers:
        datetime:
            default_format: c

Entity Classes

In ORM, the entity classes represent the database tables and its relationships. The database structure is defined through the properties of these classes and annotations. After running a specific command, Doctrine will automatically generate an SQL query and create the entire database structure.

For example, let’s perform CRUD operations on posts:

<?php

namespace AppBundleEntity;

use DoctrineCommonCollectionsArrayCollection;
use DoctrineORMMapping as ORM;

/**
 * @ORMEntity
 */
class Post
{

    /**
     * @var integer
     * @ORMColumn(type="integer")
     * @ORMId
     * @ORMGeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     * @ORMColumn
     */
    private $title;

    /**
     * @var string
     * @ORMColumn(type="text", nullable=true)
     */
    private $text;

    /**
     * @var float
     * @ORMColumn(type="datetime", nullable=true)
     */
    private $createDate;    

    function __construct()
    {
        $this->createDate = new DateTime();
    }

    function getId()
    {
        return $this->id;
    }

    function getTitle()
    {
        return $this->title;
    }

    function getText()
    {
        return $this->text;
    }

    public function getCreateDate()
    {
        return $this->createDate;
    }    

    function setId($id)
    {
        $this->id = $id;
    }

    function setTitle($title)
    {
        $this->title = $title;
    }

    function setText($text)
    {
        $this->text = $text;
    }

    public function setCreateDate($createDate)
    {
        $this->createDate = $createDate;

        return $this;
    }

}

After running php bin/console doctrine:schema:update –force in the console, the database structure will be in place.

Controllers

A controller receives a request for an API URL, uses the entity classes to fetch the information from the database, and returns the JSON as a response. In all API controllers, we will apply the following URL structure for CRUD operations:

Let’s see an example. Create PostController.php in src/AppBundle/Controller/Rest:

<?php

namespace AppBundleControllerRest;

use AppBundleEntityPost;
use AppBundleFileHandlerFileHandlerException;
use DateTime;
use FOSRestBundleControllerAnnotations as Rest;
use FOSRestBundleControllerFOSRestController;
use SensioBundleFrameworkExtraBundleConfigurationSecurity;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpKernelExceptionAccessDeniedHttpException;

/**
 * @RestRoute("/api/post")
 */
class PostController extends FOSRestController
{
    /**
     * @RestGet("/{id}", requirements={"id":"d+"})
     * @RestView(
     *  serializerGroups={"post"}
     * )
     */
    public function getAction($id)
    {
    	// Fetch a single post from the database
        $post = $this->getDoctrine()
                ->getRepository('AppBundle:Post')
                ->find($id);

        if (!$post) {
            throw $this->createNotFoundException();
        }

        // Show the JSON response
        // Which object fields are shown is handled by the serializer
        return ['post' => $post];

        // JSON response can be return in another way as well
        // return $this->view(['post' => $post])->setStatusCode(201);
    }

    /**
	 * @RestGet
     * @RestView(
     *  serializerGroups={"post"}
     * )
	 */
	public function listAction()
    {

    }

    /**
     * @RestPost
     * @RestView(
     *  serializerGroups={"post"},
     *  statusCode=400
     * )
     */
    public function createPost(Request $request)
    {

    }

    /**
     * @RestPost("/{id}", requirements={"id":"d+"})
     * @RestView(
     *  serializerGroups={"post"},
     *  statusCode=400
     * )
     */
    public function updateAd($id, Request $request)
    {

    }

    /**
     * @RestDelete("/{id}", requirements={"id":"d+"})
     * @RestView(
     *  serializerGroups={"post"},
     *  statusCode=400
     * )
     */
    public function deleteAction($id)
    {

    }
}

It is important that each API controller extends the FOSRestController class. Also, note the annotation above the class name – it means that all routes in this controller will be under api/post. The sub-routes for each method are defined with @Rest lines.

Last, but not the least, serializerGroups defines the serializer configuration that will be used to serialize the object into JSON. Serializer groups are defined in the src/AppBundle/Resources/config/serializer folder. We will create a file called Entity.Post.yml there, which could look like this:

AppBundleEntityPost:
    exclusion_policy: ALL
    
    properties:
        id:
            expose: true
            groups: [ post ]
        title:
            expose: true
            groups: [ post ]
        text:
            expose: true
            groups: [ post ]

If we compare it to the entity class from above, we will see that there is no createDate field. It means that that field will not be shown in the API response.

Next Steps

Use ApiDoc to automatically generation documentation for your API, as well as one of the faker libraries, to quickly add sample data to the application.