Sunday, January 2, 2011

Request execution in Kohana 3.1

Yesterday Kohana 3.1 RC1 has been announced and tagged. I think now it's time to get familiar it. In this post I'm going to examine the refactored request execution workflow.



Let's start with the bootstrap mechanism. The procedural bootstrap code is in the index.php and application/bootstrap.php since Kohana 3.0. In KO3.1 I can only see a very little difference: the main request execution is done at index.php and not at application/bootstrap.php but nothing else changed seriously, environment and routing setup is the same as in 3.0.

So at the end of index.php the main request is initialized, executed and it's response is sent back to the client:

echo Request::factory()
 ->execute()
 ->send_headers()
 ->body();

It's also (almost) the same as it was in KO3.0 but the internals are pretty different - and this is the topic of this post :)

If you are familiar with Kohana, you surely know that it implements the HMVC design pattern, but if not, read about it before going on. In a nutshell it's an architectural pattern, where you have a main request coming from the client. The request has a proper MVC triad, and it has sub-requests which do more atomic parts of the job. All of the subrequests have their own MVC triad, and they can have further subrequests, recursively.

The Request class in KO3.1 has two strictly HMVC-related static properties: $current which is the currently executed request and $initial which is the main request (note that it was $instance in 3.0).

When Request::factory() is called with no parameters then a Request instance is created for the request URI, and if it is the main request then some HTTP-related static properties of the Request class are also populated (e.g. $client_ip, $user_agent etc).

In Request::__construct($uri, $cache) it is determined if the request is internal or external (based on the $uri, and a Request_Client instance is created for the the request. The client can be external (Request_Client_External) if the URI is a valid URL (it contains the '://' string) or internal otherwise (Request_Client_Internal). If it is an internal request then routing is also done here: a matching route is searched, and the route parameters are extracted from the URI (or an exception is thrown if no matching route found). The abstract class Request_Client represents something that can start a request, or that can be a client of a request in other words. Its two subclasses implement the way how the request execution should be done.

An external client can execute an external HTTP request three ways:
  • using the PECL HTTP extension if it's loaded
  • using the cURL extension if it's loaded
  • usings streams if both above methods could not be done

An internal client provides the way of execution how it is done by KO3.0 Request class. It picks the route parameters from it's request, instantiates a Controller class and calls it's required action method. If you are not familiar with Kohana's routing mechanism then get started with it.

Your action method is called in your controller instance, and this is where your job comes. Your controller knows what request instance it belongs to, and what response instance should be populated by it (these objects can be accessed by $this->request and $this->response in the action methods). The methods of the request instance should be called to read the request information and the response instance should be used to build the output. To make it easier to understand here is a hello world-like action method:

application/classes/controller/welcome.php:
class Controller_Welcome extends Controller {

 public function action_index()
 {
        $name = Arr::get($this->request->query(), 'name', 'Guest');
        $view = View::factory('hello')->set('name', $name);

 $this->response->body($view);
 }

}

application/view/hello.php:
hello <?php echo $name ?>!
We read the array guery parameters (GET parameters) using $this->request->query(), pick the 'name' value from it if this key exists, otherwise we use the default 'Guest'. We create a new View object that uses our template hello.php and pass a parameter called 'name' to it. Then finally we set this View instance as the body of the response.

Note that KO3.1 uses the jQuery-style getter-setter methods for properties and it uses public attributes in less cases. For example Response::body() returns the $_body attribute of the Response object if it's called with no parameters, otherwise it sets it's value to the passed parameter.

So this is how internal and external requests work. The main difference related to KO3.0 is that in 3.1 we have separate classes to execute internal and external requests. In 3.0 we have only one type of request, and if we want to send a HTTP request to an external host, the we create a request and execute the external request using the Remote class which is a wrapper around cURL. Now the Remote class is gone, there are external requests and there is no need to create a new controller/action for executing them. In KO3.1 we also have a response class which separates the request and response data in a more clear way.

in this post many terms and many classes occured, so let's clean them up and describe the responsibility of the classes:

Request: it represents a node in the HMVC requeest hierarchy. It can be the initial or the current request. It can be internal or external, it's decided at request creation and it has an appropriate client instance.

Request_Client_External: the type of client that should execute a request to an external host. It uses PECL HTTP, cURL or streams to execute the request.

Request_Client_Internal: a client that is able to execute an internal request. It gets routing information from the Request it belongs to, instantiates the required controller and calls it's action method.

Controller: a controller is a class that is instantiated by an internal client. It can read request information using it's $request property and it should create the response by setting the attributes of it's $response property. No echo here. The $request and $response properties are injected by the internal client.

Response: the response of a request. The response of subrequests are returned to their parent request and the response of the main request is sent back to the browser. It holds response headers, response body, cookies, etc.

No comments:

Post a Comment