Implementing a RESTful Core

What makes a web project RESTful? According to Wikipedia, there are three main parts to a RESTful application: – A base URL – An internet media type that defines state transition data elements – Standard HTTP methods (GETPOSTPUTDELETE) and standard HTTP response codes (200, 403, 400, 500, etc).

In our project, base URLs will be placed into routes.php file and other mentioned points will be described now.

We’ll receive request data as application/x-www-form-urlencoded and send response data as application/json. Though I don’t believe it’s a good idea to use x-www-form-urlencoded in a real application (since you’ll struggle to send complex data structures and associative arrays with x-www-form-urlencoded), I’ve decided to implement this standard for simplicity’s sake.

If you remember, we’ve already set our response JSON header in our DI file:

$di->setShared(
  'response',
  function () {
      $response = new \Phalcon\Http\Response();
      $response->setContentType('application/json', 'utf-8');

      return $response;
  }
);

Now we have to set up response codes and a response format. The official tutorial suggests that we form JSON responses in every single method, but I don’t think it’s a good idea. It is much more universal to return controller method results as arrays and then convert them to standard JSON responses. It’s also wiser to form HTTP response codes in a single place within the project; we’re going to do this in our index.php file.

To do this, we’re going to utilize Phalcon’s ability to execute code before and after request handling with the $app->before() and $app->after() methods. We’ll place a callback in the $app->after() method for our purpose:

// Making the correct answer after executing
  $app->after(
    function () use ($app) {
      // Getting the return value of method
      $return = $app->getReturnedValue();

      if (is_array($return)) {
        // Transforming arrays to JSON
        $app->response->setContent(json_encode($return));
      } elseif (!strlen($return)) {
        // Successful response without any content
        $app->response->setStatusCode('204', 'No Content');
      } else {
        // Unexpected response
        throw new Exception('Bad Response');
      }

      // Sending response to the client
      $app->response->send();
    }

Here we get the return value and transform array to JSON. If everything was OK, but the return value was empty (for example, if we’d successfully added a new user), we would give a 204 HTTP code and send no content. In all the other cases, we throw an exception.

HANDLING EXCEPTIONS

One of the most important aspects of a RESTful application is correct and informative responses. High-load applications are usually big, and errors of various types can occur everywhere: validation errors, access errors, connection errors, unexpected errors, etc. We want to transform all of these errors into unified HTTP response codes. It can be easily done with the help of the exceptions.

In my project, I’ve decided to use two different kinds of exceptions: there are “local” exceptions- special classes inherited from the \RuntimeException class, separated by services, models, adapters and so on (such division helps to treat each level of the MVC model as a separate one)- then there are HttpExceptions, inherited from the AbstractHttpException class. These exceptions are consistent with HTTP response codes, so their names are Http400ExceptionHttp500Exception, and so on.

The AbstractHttpException class has three properties: httpCodehttpMessage and appError. First two properties are overridden in their inheritors and contain basic response information such as httpCode: 400 and httpMessage: Bad request. The appError property is an array of the detailed error information, including the error description.

Our final version of index.php will catch three types of exceptions: AbstractHttpExceptions, as described above; Phalcon Request Exceptions, which may occur while parsing a request; and all other unanticipated exceptions. All of them are converted to a pretty JSON format and sent to the client via standard Phalcon Response class:

<?php

use App\Controllers\AbstractHttpException;

try {
  // Loading Configs
  $config = require(__DIR__ . '/../app/config/config.php');

  // Autoloading classes
  require __DIR__ . '/../app/config/loader.php';

  // Initializing DI container
  /** @var \Phalcon\DI\FactoryDefault $di */
  $di = require __DIR__ . '/../app/config/di.php';

  // Initializing application
  $app = new \Phalcon\Mvc\Micro();
  // Setting DI container
  $app->setDI($di);

  // Setting up routing
  require __DIR__ . '/../app/config/routes.php';

  // Making the correct answer after executing
  $app->after(
    // After Code
  );

  // Processing request
  $app->handle();
} catch (AbstractHttpException $e) {
  $response = $app->response;
  $response->setStatusCode($e->getCode(), $e->getMessage());
  $response->setJsonContent($e->getAppError());
  $response->send();
} catch (\Phalcon\Http\Request\Exception $e) {
  $app->response->setStatusCode(400, 'Bad request')
                ->setJsonContent([
                  AbstractHttpException::KEY_CODE    => 400,
                  AbstractHttpException::KEY_MESSAGE => 'Bad request'
                ])
                ->send();
} catch (\Exception $e) {
  // Standard error format
  $result = [
    AbstractHttpException::KEY_CODE    => 500,
    AbstractHttpException::KEY_MESSAGE => 'Some error occurred on the server.'
  ];

  // Sending error response
  $app->response->setStatusCode(500, 'Internal Server Error')
                ->setJsonContent($result)
                ->send();
}

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *