Indigo's core design decisions

Work on Indigo is proceeding reasonably smoothly. The main focus has been on the routing, controllers, views and database abstraction layer. I've been thinking about a number of design decisions that I made and whether or not they are the right call.

Routing

Routing was one of the aspects of Kohana that annoyed me, and one of the aspects that I adored about Drupal. I am a fan of hierarchical URLs and want my routes to support it. But, I still wanted controllers to be laid out similar to Kohana's. To find a mid ground, I require each controller to define a public $routes array that lists what input masks it can handle and what page to send them to. Once the router identifies a matching mask, it parses the URL arguments into the request to be sent to the page. The exact structure of it was lifted from Pyramid, using named arguments to identify dynamic pieces of the URL.

class Article extends Indigo\Controller
{
    public static $routes = [
        '/article' => [
            'page' => 'view_all'
        ],
        '/article/{id}' => [
            'page' => 'view'
        ],
        '/article/{id}/edit' => [
            'page' => 'edit',
        ],
    ];

    public function view_all($req) {  }
    public function view($req) {  }
    public function edit($req {  }
}

Request / Response

Indigo is structured to mostly follow a request/response pattern. I say "mostly" because the response object is not easily mutable by a page. I don't like this and will redesign it so that each page accepts a request and returns a response. My concern is that the complexity of the response object is somewhat unknown right now. I know a few details that I want it to have, but I don't think a page should be required to build a response object every time.

public function home($req)
{
    if ($req['method'] == 'POST')
    {
        return $req['post']['username'];
    } else {
        return $req['args']['id'];
    }
}

In order to reduce complexity on pages, I can pass the prepared response object to it, and expect it to return a response object. At a minimum, the page will modify the body of the response and return the object. Through this setup, the page is free to completely disregard the default response and return an entirely new one. This structure would also allow the page to manage things like URL redirects and HTTP response codes more freely.

public function home($req, $res)
{
    if ($req['method'] == 'POST')
    {
        $res['body'] = $req['post']['username'];
    } else {
        $res['http_code'] = '403 Forbidden';
    }

    return $res;
}

The other part of the request/response that I'm reconsidering is that the request "object" is an array. Throughout the rest of Indigo, I tend towards objects and type hinting, but in this case, it is an array that is built by the router. I would like to keep Indigo consistent, and to that end will need to change the request to fit my other conventions.

Dependency Injection

I am relatively inexperienced with dependency injection but am trying to incorporate it into Indigo. In trying to de-couple as much functionality from the global state as is reasonable, I am passing an instance of Indigo\Config to my class initializers. Combining this with a multiton and a factory pattern, I should be able to do automated testing by swapping out resources between tests. For instance, I could run a battery of tests and change out the default database between each to ensure that the code executes as expected on all databases.

$config = Indigo\Config::factory($site);
Indigo\Db::init($config);

I am a bit concerned about the Config object; it could become a dumping ground of unrelated information. I noticed this while working on one of the templating engines and had to create a new Config object just to initialize it. I will reconsider or redesign this set up in order to keep the goal - easily swappable resources for testing - while also maintaining a simplistic, high-level system.

Abstract Factories and Strict Interfaces

One of the primary goals of Indigo is to not force decisions on developers whenever possible. I don't want to force a developer to use my views, models or database abstraction layer. To that end, I have set up parts of the system to use abstract factories and strict interfaces. This means that a developer only needs an adapter between his desired library and Indigo. As an example, I already wrote a basic adapter between Indigo's views and the Twig template engine.

This specific implementation is fairly straightforward - an abstract factory returns an engine (such as Twig), which then acts as a factory to return a view. As long as the adapter implements the interface, any templating engine is supported by Indigo. The same can be said about models and database abstraction layers. I am writing my own default libraries for each of these, but they are loosely coupled and follow these same rules.

$template = Template::factory('twig')->createView('article/all');
$template->articles = $articles;

return $template->render();

As a bonus, this allows for multiple distinct instances of a library with different config parameters. An example of this would be multiple instances of a MySQL DAL, each one connected to a different database.

Indigo\Db::init($localConfig, 'local');
Indigo\Db::factory('local')->connect();

Indigo\Db::init($archiveConfig, 'archive');
Indigo\Db::factory('archive')->connect();

Tags: