Create RESTful API for Symfony2 with Apigility


by Enrico Zimuel

Apigility Apigility is an open source project to simplify the implementation of HTTP API for PHP applications. The project has been implemented in Zend Framework 2 but it can be used to create API for any PHP application. To prove this aspect, I show in this post how to use Apigility to create a RESTful API for an existing Symfony2 project.

This use case is an extension of the proof of concept example that has been presented at the PHP Forum 2013 in Paris, during the talk “Symfony2 and Zend Framework 2: the perfect team” of Stefan Koopmanschap and myself.

I would like to thanks Alessandro Nadalin for the suggestion about the bootstrap part of Symfony2.

Installation

You can install the Symfony2 application and the API implementation at the following github repository https://github.com/ezimuel/apigility-symfony2-use-case. This repository includes the Symfony2 application in the folder symfony2app and the Apigility API in the folder apigilityapi.

You need to install the Symfony2 application before to start using Apigility. To install the Symfony2 application follow the steps reported in this README.md file.

After this installation, please verify that you can execute the web application in a browser, if you are using PHP 5.4+ you can execute it using the following command line, from the symfony2app folder:

php app/console server:run

This command executes the internal PHP web server on localhost with default port 8000. You can check if the application is working correctly pointing your browser to http://localhost:8000/post. Try to add some post using the web interface in order to populate the database with some data.

If everything is working fine you can now install the API implementation provided by Apigility. You need to follow the instructions reported in this README.md file.

After this step you are ready to execute the RESTful API using a web server! If you are using PHP 5.4.8+ you can use the internal PHP web server using the following command, from the apigilityapi folder:

php -S 0:8080 -t public/ public/index.php

Consuming the RESTful API

The CRUD operations offered by the Symfony2 web application are now exposed as RESTful API, thanks to Apigility. You can access it using the following HTTP actions:

GET    /post        get the entire list of posts
GET    /post[/:id]  get the post specified by id
POST   /post        create a new post
PUT    /post[/:id]  update a post
PATCH  /post[/:id]  partial update a post
DELETE /post[/:id]  delete the post specified by id

For instance, you can get the lists of all the posts from the command line using the following HTTPie instruction:

http GET http://localhost:8080/post

You should retrieve a result like that:

HTTP/1.1 200 OK
Connection: close
Content-Type: application/hal+json
Host: localhost:8080

{
    "_embedded": {
        "post": [
            {
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/post/1"
                    }
                }, 
                "content": "test", 
                "id": 1, 
                "publish_date": {
                    "date": "2013-11-29 15:40:00", 
                    "timezone": "Europe/Berlin", 
                    "timezone_type": 3
                }, 
                "title": "test"
            }, 
			...
		]
    }, 
    "_links": {
        "self": {
            "href": "http://localhost:8080/post"
        }
    }
}

As you can see the body response is represented in JSON HAL format, the default format of Apigility.

One of the cool feature of Apigility is the error management, for instance if you try to execute a POST on the /post resource without the JSON data reported in the body, you will get the following error message:

http POST http://localhost:8080/post

HTTP/1.1 400 Bad Request
Connection: close
Content-Type: application/api-problem+json
Host: localhost:8080

{
    "detail": "You need a title and a content at least", 
    "httpStatus": 400, 
    "problemType": "http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html", 
    "title": "Bad Request"
}

This error is managed directly by Apigility and you don’t need to do nothing in the source code of your API.

If you want to add a valid post you need to specify the title and the content as JSON parameter. Using HTTPie you can execute the following command:

http --json POST http://localhost:8080/post title=Foo content=Bar

The HTTP response will be something like that:

HTTP/1.1 201 Created
Connection: close
Content-Type: application/hal+json
Host: localhost:8080
Location: http://localhost:8080/post/4

{
    "_links": {
        "self": {
            "href": "http://localhost:8080/post/4"
        }
    }, 
    "content": "Bar", 
    "id": 4, 
    "publish_date": {
        "date": "2013-12-04 16:00:03", 
        "timezone": "Europe/Berlin", 
        "timezone_type": 3
    }, 
    "title": "Foo"
}

Basically, using Apigility we exposed the model part of the Symfony2 project reusing the original code of the application. This use case can be interesting for existing Symfony2 projects where you want to have a RESTful API with versioning, error handling, authentication, etc ready in minutes using Apigility.

In the next section I explain the procedure that we used to create this API.

How to use Apigility

Apigility can be used to create API using a simple web user interface (built with AngularJS).
In order to execute this UI you need to enable the development mode of Apigility executing the following command, inside the root folder of the apigility skeleton application:

php public/index.php development enable

After that, you can execute Apigility configuring your web server to point to the public folder of the project. If you are using PHP 5.4.8+ you can use the internal web server of PHP using the following command:

php -S 0:8080 -t public/ public/index.php

If you point your browser to http://localhost:8080, you should see the welcome page of Apigility.

Welcome page of Apigility

To create a new API you can click on the Create New API button (top/right of the page). In our case, we created a new ApiBlog API.
Now you can choose the ApiBlog section (top of the page) and create a new RESTful service, choose the REST Service option of the menu on the left and click on the Create New REST Service button. Because we want to build API for an existing Symfony2 project, we need to choose a Code-Connected service, we choosed the Post name for this service.

Create New Service with Apigility

To create a REST service you need to insert some information, like the Route to match, the HTTP methods allowed for ENTITIES and for COLLECTIONS, the Identifier name, etc. In our case we choosed the following set:

  • Route to match: /post[/:id]
  • Identifier name: id
  • Collection name: post

We used the default values for the other parameters.

After this step we need to customize the Apigility configuration to use the Post entity provided by the Symfony2 project. The idea is to expose the Post entity, coming from the Symfony2 application, as RESTful resource.

The first step is to add to the composer.json of Apigility all the dependencies used by the composer.json of the Symfony2 application. In this way, we can reuse all the existing classes of the Symfony2 project in Apigility.

The second step is to edit the config/module.config.php file included in the ApiBlog that we just created (all the API produced by Apigility are shipped as ZF2 module in the /module folder).

You need to change the zf-rest['entity-class'] value to the Symfony2 entity ‘Blog\\ExampleBundle\\Entity\\Post’ (line 45 of our module.config.php) and the zf-hal['metadata_map'] value of ‘ApiBlog\\V1\\Rest\\PostEntity’ to ‘Blog\\ExampleBundle\\Entity\\Post’. You need also to add the value ‘hydrator’ => ‘ClassMethods’ to this Post entity (line 72 of our module.config.php).

The third and last step is to edit the PostResource class of Apigility to add all the specific RESTful actions for each HTTP methods. If we want to expose all the CRUD operations of the Symfony2 application we need to fill the following methods:

  • create($data), POST /post
  • delete($id), DELETE /post[/:id]
  • fetch($id), GET /post[/:id]
  • fetchAll($params), GET /post
  • patch($id, $data), PATCH /post[/:id]
  • update($id, $data), PUT /post[/:id]

The idea is to bootstrap the Symfony2 application (without the HTTP request management) in the constructor of the PostResource class and store the specific service that we need in a protected variable, in order to be used in the specific methods to retrieve data from the businees logic of the Symfony2 application.

In our case, the code that we used to bootstrap the Symfony2 application is reported below:

public function __construct()
{
    $symfonyApp = '/path/to/your/symfony2app';
    require_once $symfonyApp . '/app/AppKernel.php';

    $kernel = new \AppKernel('prod', true);
    $kernel->loadClassCache();
    $kernel->boot();

    $this->doctrine = $kernel->getContainer()
                             ->get('doctrine')
                             ->getManager();
}

The usage of the AppKernel class of Symfony2 is very similar to the code used in the web/app.php file of the Symfony Standard Edition distribution.

In our case, we used the doctrine manager service because our Symfony2 application used a simple entity of Doctrine as model. According to your Symfony2 application, you can get the specific service/object that you need to consume in the API from the Container of your application ($kernel->getContainer()).

Using the $this->doctrine object we can reuse the Post Entity of the Symfony2 application in all the API methods. You can see how we implemented our PostResource class, here.

Note that we can return the post entity object as result in all the API methods because we specified the usage of ClassMethods hydrator in our configuration file config/module.config.php.

Conclusion

In our use case we showed how to reuse an existing Symfony2 application and consume the Model part to build a RESTful API. We decided to bootstrap the Symfony2 application because in this way we are really reusing existing code, especially if the Model part of your application uses different service and not only Doctrine entities.

Regarding the performance of this approach I can say that they are related to the specific implementations of the Symfony2 application. Generally speaking, the performance of the Symfony2 bootstrap part are good thanks to the lazy loading mechanism of the framework. Of course, you can reduce the bootstrap part for the API removing the services and components that are not necessary for the Model part.

If you are interested in that optimization I suggest to have a look at the app/AppKernel.php and the Symfony\Component\HttpKernel\Kernel component of Symfony2.

In the case that your Symfony2 application uses only Doctrine as Model you can also use directly the ZF2 module of Doctrine2 to create the API, instead of bootstrap the Symfony2 application. Recently, Tom Anderson developed a tool to create an Apigility API from Doctrine entities in scope, you can find this project here.

The use case presented in this post can be considered a first approach to the usage of Apigility to create API for Symfony2, I’m sure we will see more examples in the next future.

To have more info about Apigility and to follow the future implementations of the project you can visit the web page apigility.org.

  • coolfr

    This is a great article and very helpful.