TSM - Zero to RESTful in 4 easy steps

Georgiana Gligor - Owner @Tekkie Consulting

Developing applications professionally on the LAMP stack imposes use of modern frameworks. They standardize the way we work by using a predictable code and file organization, come prepackaged with basic utilities and libraries. Moreover, frameworks address security concerns at the core of the software, are a key factor in teamwork by enforcing code standards and easier onboarding of teammates. Last but not least, they are backed by fantastic communities, and make us more efficient by helping focus on the functionality of our project.

However, there are projects where using a full-fledged framework feels heavy on the developer. Individual microservices, quick prototypes, small websites, simple APIs, mock functionality, these are all cases when a developer would rather benefit from the smaller footprints of ready-made (and tested) libraries. That's where micro-frameworks come into play, providing the necessary glue between them.

We will build an application to track data about our vehicles, and get alerted when certain operations need to happen. This will happen over the course of several articles, let's quickly check the roadmap: 1. project setup and dependencies, TDD support and logging capabilities 2. design the RESTful API; BDD with Behat 3. implement APIs defined in the previous step 4. development environment with Vagrant and Ansible; build automation; continuous integration

In the PHP world, micro-frameworks have become first-class citizens a long time ago. Slim, Flight, Limonade, GluePHP, Phlyty are a few well-known examples. Our micro-framework of choice is Silex, based on the highly popular Symfony components. To get a feeling about how much lighter Silex is, let's have a look at how its Dependency Injection Container (Pimple) compares to the Symfony one (taken from a presentation of Igor Wiedler):

Project setup

Let's start by creating a new project: create a folder to work in and then navigate to it. Here I use an absolute path on my machine:

$ mkdir -p /Users/g/Sites/learn/silex-tutorial
$ cd /Users/g/Sites/learn/silex-tutorial

Install Composer

We will use Composer to manage my dependencies. It comes packaged as a Phar archive, so it's a good idea to first check that the corresponding extension is enabled in php.ini:

extension=phar.so # should be un-commented

I prefer to have Composer globally available, since it will come in handy for other projects as well. We will need sudo just for the installation bit.

$ curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer

Declare Silex as a dependency

Next step is to create composer.json to manage our dependencies.

{
  "require" : {
    "silex/silex" : "1.*"
  }
}

Let's now execute in the working folder the install command:

$ composer install

This will not only install Silex and its declared dependencies, but also create a composer.lock file that locks the version of all installed components to a known state.

Making the first request

Let's prepare a small strategy on the layout of the files we want.

It is good practice to keep the publicly accessible files in a separate folder, so we will use web/ for this purpose.

The resource loading and other internal kitchen will live in app/bootstrap.php. Here's how we plan our file layout to be.

The bootstrap file defines the Silex application object and returns it, while the contents of our publicly accessible file is very simple, instantiates the application object from the bootstrap file and run()s it.

Have a look on how we make use of Composer's autoload feature:

require_once(__DIR__ . '/../vendor/autoload.php');

This saves us from a lot of trouble because it loads all classes inside vendor/ without us needing to do anything extra than the file require.

Let's see what happens if we access the public web/index.php file right now:

Here's Silex responding to our request. It tells us that it has no idea what to do with our request, because we have not defined any behavior for our application so far. Time to add some, right?

Let's define a simple behavior that serves GET requests and responds with some simple text. We do this by defining a route pattern, which is composed of: the pattern itself ('/' in the example below) the HTTP method that the pattern can be accessed at (the verb get() called on the application object) * the closure function which will be executed

$app->get('/', function () {
      return 'Up and running.';
});

If we want the same closure function to be executed for multiple HTTP methods, we can use the following approach:

$app
    ->match('/', function () {return 'Up and running'; })
    ->method('GET|POST');

Let's decompose this for a moment: we matches the pattern first, attach the closure function, and then specify the list of methods the pattern can be accessed at.

Add TDD support

It is important to have a way of writing tests for our API application. The de-facto standard in the PHP world is PHPUnit, and even Drupal8 has switched to use it from previous SimpleTest.

Let's revisit the file layout and see how we can add a simple way of benefiting from test-driven development.

As you can see, we have added a simple PHPUnit configuration file, and created a folder to hold our tests. When we attempt to use it, everything seems fine:

We'll create a test class to check the main route we have defined previously; we'll refer to it further as "status route", since it tells us our application is up. In the file tests/BasicTest.php we'll define

/**
 * Basic test class to check the application status route
 */
class BasicTest extends WebTestCase

Note that we are extending Silex\WebTestCase for our functional test, therefore we need to implement the createApplication() method to tell the test how it can be aware of the Silex application object. It is pretty simple, we are requiring the bootstrap file and returning the $app object. The WebTestCase is defined in an external package, so we need to enhance our composer.json with the following section

"require-dev" : {
    "symfony/browser-kit": ">=2.3,<2.4-dev"
}

The actual test method is extremely simple: we initiate a GET request on the status route, verify that the response is ok (has status code 200) and the actual content is "Up and running".

/**
 * Checking the status route
 */
public function testStatusRoute()
{
  $client = $this->createClient();
  $client->request('GET', '/');
  $this->assertTrue($client->getResponse()->isOk());
  $this->assertEquals(
        'Up and running',
        $client->getResponse()->getContent()
  );
}

As a matter of personal preference, I like to have tests under their own namespace, therefore namespace TutorialTest needs some autoloading love in composer.json before it can be used. We will need to add to composer.json the following section:

"autoload" : {
    "psr-0" : {
        "TutorialTest" : "tests"
    }
}

After making thischange, we will need to run

$ composer update

This will resolve all dependencies of the current project, and write the new versions to composer.lock.

Let's run the tests again:

This was just the start of functional testing, you can find out much more on $client usage in Symfony2's testing documentation.

Logging capabilities

Integrating external libraries is done in Silex via providers. Let's say we want to use the powerful Monolog library for our logging purposes. We would view it as a service, and integrate it in our application via a service provider. Luckily, there already is a MonologServiceProvider handy, so let's use it.

We start by adding a new requirement in composer.json:

"monolog/monolog": ">=1.6.0"

and then run composer update to grab it.

We'll now create the logs folder and give appropriate permissions:

$ mkdir logs
$ touch logs/dev.log
$ chmod -R 766 logs/

Also, we will add the new folder to the .gitignore list, as we don't want to commit our logs to versioning control.

Let's see a simple usage example of the logging service. Our status route code will be

$app
    ->match('/', function () use ($app) {
      $app['monolog']->addInfo('Logging example in the status route');
      return 'Up and running';
    })
    ->method('GET|POST');

Checking our logfile will show any request we make.

So far, we have an application where we installed a vanilla Silex, responds to a simple GET request, and added TDD support and logging capabilities. In the next part, we'll design our RESTful API.