jump to navigation

Rapid Application Prototyping in PHP Using a Micro Framework (Silex) — Part2 Basic Concepts May 27, 2014

Posted by Tournas Dimitrios in Silex.
Tags: ,
trackback

Silex framework logoWelcome back to the next part of the article series “Rapid Application Prototyping in PHP Using a Micro Framework (Silex)”. Understanding the basic concepts of this  framework (Silex) will make  you feel more comfortable and confident while using it . The goal of this article is to give you a high-level overview of how the Silex framework “works”. Internally, the Silex core is built on top of Pimple, a Dependency Injection Container, and a few components of the Symfony Framework. One of those components is the HttpFoundation component, which defines an object-oriented layer for the HTTP specification. HTTP has a set of predefined methods (also referred as verbs) to indicate what action should be performed on the identified recourse. Actually we don’t have to worry about HTTP-specifications (and all that boring technical detail) as the browser does all the heavy work behind the scenes to build a request according those specifications. The only thing that really interests us is how we should link a request to the corresponding resource (in technical jargon it is named : Routing). In Silex it’s possible to route in two ways . We can either route to a closure or a controller action. As we’ll be going over controllers in later parts of this article, let’s start by looking at how we can route to a closure . 

Typically, browsers support only three verbs (methods) of the HTTP-specification : HEAD , GET and POST. Developers have invented a nice trick to emulate a couple other verbs (read more on the documentation page) .

When users click on a link or enters a URL in their address bar, they’re creating a GET request.When users submit a form, they’re typically creating a POST request. The browser starts to communicate with the server with a HEAD request .

The basics of routing : 

A route is the combination of a HTTP method (verb) and a pattern (URI) . Each route is attached to an anonymous function (or closure), these functions contain application logic for that specific pattern (URI) . It sounds more complicated than it actually is, a practical example is probably more useful :

// Handle a GET request on "/about/" page
$app->get('/about/', function () {
    // Logic here ......
});
// Handle a POST request on "/feedback/"
$app->post('/feedback/', function (Request $request) {
    $message = $request->get('message');
    // Logic here .....
});

The above example demonstrates how GET/POST requests are “routed” to a corresponding function, these anonymous functions (or lambdas) should embed the required functionality (business logic) for the URI they are attached to. Usually, Closures are used (instead of anonymous functions), Closures can inherit variables from the parent scope. Any such variables must be passed to the Closure by the “use” language construct .

$books = array(
    1 => array(
        'date'      => '2014-05-29',
        'author'    => 'John Doe',
        'title'     => 'Learning Silex',
        'excerpt'      => 'Lorem ipsum ... ',
    ),
);

$app->get('/books/', function () use ($books) {
    $output = '';
    foreach ($books as $book) {
        $output .= $book['title'];
        $output .= '
';
    }

    return $output;
});

The above two examples are designated as “static routes”, a fixed pattern is used to identify the function they are attached to and no parameter(s) is|are passed over into the anonymous functions/Closures.  On the other side of the coin, we have Dynamic routes, which  are routes with a dynamic pattern . The dynamic parts of the pattern will be converted to route variables which will be passed into the handling function .

$books = array(
    1 => array(
        'date'      => '2014-05-29',
        'author'    => 'John Doe',
        'title'     => 'Learning Silex',
        'excerpt'      => 'Lorem ipsum ... ',
    ),
);

$app->get('/books/{id}', function ($id) use ($books) {

	$output = 'Sorry no record found with this id' ;
	if (array_key_exists($id, $books)) {
	 foreach ($books as $book) {
        $output .= $book['title'];
        $output .= '
';
    	}
	}

    return $output;
});

In the first part of this article we mentioned that the Application container of Silex extends Pimple, into that container we register all objects (services)  we want to have available across the whole application. Passing the container into a Closure will make all registered services (objects) available inside the scope of the Closure :

$books = array(
    1 => array(
        'date'      => '2014-05-29',
        'author'    => 'John Doe',
        'title'     => 'Learning Silex',
        'excerpt'      => 'Lorem ipsum ... ',
    ),
);

$app->get('/book/{id}', function (Silex\Application $app, $id) use ($books) {

	if (! array_key_exists($id, $books)) {
		$app->abort(404, "Book $id does not exist.");
		}

    // other logic here
});

Routes can also be customized, it is possible to : set default values on variables of a route , process route-variables using converters, matching routes against certain expressions, limit a route to specific hosts/IP’s only, accepting only  HTTP/HTTPS requests, assigning a name to each route (some providers, such as UrlGeneratorProvider, can make use of named routes).The documentation   has extensive practical examples how to customize a route.

The HttPFoundation Component :

In PHP, each request is represented by global variables ($_GET,  $_POST, $_FILES, $_COOKIE, $_SESSION) and the response is generated by the business logic. Usually response messages are generated by the “echo, header , setcookie” language constructs. The Symfony2 HttpFoundation component (which is part of the Silex framework) replaces these default PHP global variables (and the response) by an object-oriented layer. The request/response lifecycle is wrapped into the following two Classes :

  • The lifecycle start with a   Symfony\Component\HttpFoundation\Request object . This object holds information about the client request. The information can be accessed via several public properties (each property is an  ParameterBag  instance) . Symfony’s documentation has an extensive list of all properties of the Request object . 
  • The lifecycle ends with a  Symfony\Component\HttpFoundation\Response object . This object holds all the information that needs to be sent back to the client from a given request (the response content, the status code, and an array of HTTP headers) . Via this object we can also set/clear Cookies , set HTTP status codes and manage HTTP headers related to Caching (setExpires , setMaxAge , setSharedMaxAge , setTtl). It is also possible to return a JsonResponse object ($app->json() ) and BinaryFileResponse ($app->sendFile()) .

BinaryFileResponse represents an HTTP response delivering a file. JsonResponse Converts the data into a JSON-encoded text.

And what’s the benefit of wrapping the request/response lifecycle  into objects, you might ask. It’s all about HTTP abstraction and encapsulation, variables that exists in the “Global space” leads to code that is unpredictable ( because it’s accessible from anywhere in the application). Having the control of these objects means that we decide which parts of the application can have access to their internal data.

The HttpKernel Component : 

The HttpKernel component provides a structured process for converting a Request object into a Response object by making use of the EventDispatcher, it actually notifies events to convert a Request object to a Response one. By taking advantage of HttpKernel’s event system we can “hook” all kind of functionality into the request/response lifecycle, we can change the default Silex behavior so to speak . Certainly you have heard the phrase “middlewares”, it just designates a procedure that intercede in the HTTP request/response lifecycle . We have to kinds of middlewares :

  • Application middlewares:  which are triggered independently of the current handled request
  • Route middlewares: which are triggered when their associated route is matched

The documentation page has examples for each type of intervention. In short :

  • A “before” application middleware allows you to tweak the Request object before the Closure/Controller is executed
  • An “after” application middleware allows you to tweak the Response object before it is sent to the client
  • A “finish” application middleware allows you to execute tasks after the Response has been sent to the client (ie sending emails or Logging)
  • A “before” route middleware is fired just before the route callback, but after the “before application middleware(s)
  • An “after” route middleware is fired just after the route callback, but before the  “after application middleware(s)

We can add as many middlewares we want (using a stack, so to speak)  in which case they are triggered in the same order as we added them on the stack. Explicitly defining priority is also possible though . 

A small cup of coffee

Final thoughts : 

This article has outlined a couple basic concepts of the Silex framework. We learned that by encapsulating the request/response lifecycle into objects (Symfony’s HttpFoundation component) and by exposing its internals via an unified API , our code becomes portable and flexible. Also we took a look at Symfony’s HttpKernel component which provides an excellent foundation upon which we can change the default request/response lifecycle of our application (by using middlewares) . The HttpFoundation and HttpKernel components are used by many frameworks (ie Laravel4 , Symfony2 , Silex1, Drupal) , so the same concepts apply to all those frameworks .
Wish you the best ….
Links : 

Advertisements

Comments»

No comments yet — be the first.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s