Action-Domain-Responder in WordPress - 01

Trying to use WordPress in a different way.

Action, Domain, Responder

The ADR model, or pattern, is an attempt to reinvent the wheel provided by the MVC pattern to create something better suited to handle the flow of HTTP requests and responses.
I’ve used the MVC pattern in my Objective C days to develop applications and tried, without success, to do the same when I “landed” in WordPress and PHP; mind this speaks ill of my skill, not of the pattern.
Reading the document on the ADR repository I was tempted to give it a try in the WordPress context.
Following in this series of articles will be the chronicle of my attempt with all its failures, stumbles and mistakes.
It won’t be a “how to implement the ADR pattern in WordPress” guide; it will rather be a “let’s see if I can implement the ADR pattern in WordPress” guide; the hope is to learn something from my errors.
If you want to read more about the ADR pattern have a read on the original repository.

The moving pieces

In place of launching myself in an attempt to describe WordPress default request handling process in terms of one pattern or another, while try to weigh its advantages and disadvantages, I will rather try to describe my understanding of the Action-Domain-Responder pattern in WordPress terms and try to get to code as fast as I can.
The Action component is the code equivalent of what the HTTP client is trying to do: it could be described as “view the posts archive page”, “search for something and see the results” or “create a post”; for sake of simplicity I will try to implement the first example action: “view the site posts archive”.
The Domain is the context in which the request is happening and all the logic dealing with CRUD operations: it represents things like “who is the user”, “are there posts matching a query” or “insert this post in the database”, back-end and session stuff.
The Responder is everything that is presenting the result of the request handling to the user or client: stuff like “use this template and this content to render the homepage” or “return the JSON representation of the post” or “render a 404 page”; WordPress templates are kinda that.

Playground rules

Since I’m trying to play this game in the context of WordPress I’m setting some arbitrary (as in “I have set them”) rules and boundaries:

  • whatever implementation I come up with should be packed in a single WordPress plugin
  • said plugin can completely overhaul WordPress front and back ends
  • the plugin cannot add or remove plugins or themes from the site to reach its objective (deactivating stuff is fine though)
  • said plugin should but restore WordPress to its fully working state when deactivated
  • no .htaccess or other types of voodoo should be required for the plugin to work, pretty permalinks as a requirement is fine though
  • I would like said plugin to be PHP 5.2 compatible; not sure I can nail this though
  • the code should be testable and tested (I would not even bother if I could not test it)

This is quite a list but I want to keep things interesting for me.
I’ve got a vague idea of how I’m going to implement this and with it comes a vague list of packages I will probably use:

  • wp-browser - a WordPress-specific set of Codeception modules; to test stuff
  • di52 - a PHP 5.2 compatible dependency injection container with no dependencies
  • klein52 - a PHP 5.2 compatible router with no dependencies, a fork of the original klein with some useful additions for WordPress

Spoiler: I’m the author of all the packages above and the reason I’m using them is I know them well; if this attempt has to crash and burn I don’t want it to be for the tools.

A first objective and a first test

After the mandatory Composer, Codeception and wp-browser initialization plus some boilerplate plugin code this is my starting point; at this stage the plugin activates and deactivates just fine but does nothing of note.
There is not much worth noting here so it’s time to write the first test: an acceptance one making sure I can reach the/posts page and see the last published posts on it.
Why not the homepage? Simple answer: I could pass that test without writing a line of code as WordPress already does that by default.
With a default WordPress installation I could reach the same result by:

  • creating a page with the posts post name
  • in the Settings > Reading settings set the “Posts page” to the just created posts page

While not difficult this approach does not lie completely in the developer’s hands: the site administrator could delete the posts page (WordPress would still server coherent content though) and, from the developer perspective, it would require creating the posts page programmatically, making sure it’s there and override the index.php or home.php template files as (from the Codex):

Also, any Template assigned to the Page will be ignored and the theme’s index.php (or home.php if it exists) will control the display of the posts.

My first test will be a simple acceptance test (written in Codeception Cest format):

// file tests/acceptance/ViewPostCest.php

class ViewPostsCest {

    /**
     * It should show the site latest posts on the /posts page
     *
     * @test
     */
    public function should_show_the_site_latest_posts_on_the_posts_page(AcceptanceTester $I) {
        $I->haveManyPostsInDatabase(5);

        $I->amOnPage('/posts');

        $I->seeNumberOfElements('.post', 5);
    }
}

Since I’ve done nothing at this point I make sure the test is failing:

I’m very good at failing tests, apparently.

Passing the first test

Passing this first test, trying to stick with the Action-Domain-Responder pattern (my interpretation of it really), ended up being done with the code tagged dev-1 on the repository.
I’ve tapped heavily into the methods provided by the above tools to pass the first test and a “walking tour” of the code is in order to follow the logic.
First is the plugin entry point, or, in WordPress terms, the plugin main file:

// file adr-wp.php

<?php
/**
 * Plugin Name: ADR
 * The usual header stuff...
 */

if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
    include_once __DIR__ . '/vendor/autoload.php';
} else {
    include_once dirname(__FILE__) . '/vendor/autoload_52.php';
}

$container = new tad_DI52_Container();

$container->register('adr_ServiceProviders_Templates');
$container->register('adr_ServiceProviders_Routes');

I’m using di52 to use the dependency injection container pattern. I’ve added two service providers to “provide” the basic functions needed to fulfil the task.
The adr_ServiceProviders_Routes one registers the routes and filters the do_parse_request hook.

class adr_ServiceProviders_Routes extends tad_DI52_ServiceProvider {

    /**
     * Binds and sets up implementations.
     */
    public function register() {
        add_filter( 'do_parse_request', array( $this, 'filterDoParseRequest' ) );

        respond( 'GET', '/posts', $this->container->callback( 'adr_Actions_ViewPosts', 'handle' ) );
    }

    public function filterDoParseRequest( $do_parse_request ) {
        // a klein52 methods that will either echo and die if found or let the execution continue
        dispatch_or_continue();

        // let WordPress do its job; we could not handle the request
        return $do_parse_request;
    }
}

The do_parse_request filter is a filter that allows WordPress developers to step in and handle the request:

  • after WordPress has been loaded and with it the plugins, theme; the site, blog and user information are set at this point
  • before the request is passed to WordPress to create the query or, in general, handle it

If a “falsy” value is returned while filtering do_parse_request then WordPress will presume the request has been handled; there are many things not happening when preventing WordPress from parsing the request but, for the time being, I’m content with it.
While filtering I’m using the klein52 function dispatch_or_continue; this function will try to match the current request to an handler registered with the respond function and either call that if found (“dispatch”) or continue and let WordPress handle the request.
This piece of code is fundamental as I want my code to handle the requests I’m interested in, one for the time being, but not all of them.
So what am I handling? Just a GET request for the /posts path.
How am I handling it? Here some DI52 voodoo kicks in so it’s worth taking the time to understand the code.
The adr_Actions_ViewPosts class provides a handle method; that method will handle the request in some way; I tell to the router (klein) that if a GET request for /posts comes it should call a callback function.
I’m using di52 callback method here to do, in a PHP 5.2 compatible way, what I could do with closures like this:

$container = $this->container;

$callback = function( _Request $request, _Response $response ) use( $container ) {
    $action = $container->make( 'adr_Actions_ViewPosts' );
    $action->handle( $request, $response );
};

respond( 'GET', '/posts', $callback);

All that is done for me by the container using the callback method; and in a PHP 5.2 compatible way (but the container will use closures on PHP 5.3+).

Action and domain

The first component of the pattern kicks now into play, the “action”, in the form of the adr_Actions_ViewPosts class:

// file src/adr/Actions/ViewPosts.php

class adr_Actions_ViewPosts {

    /**
     * @var \adr_Domains_Domain
     */
    protected $domain;
    /**
     * @var \adr_Responders_Responder
     */
    protected $responder;

    public function __construct( adr_Domains_Domain $domain, adr_Responders_Responder $responder ) {
        $this->domain    = $domain;
        $this->responder = $responder;
    }

    public function handle( _Request $request, _Response $response ) {
        $posts = $this->domain->get( array( 'post_type' => 'post' ) );

        $data = array(
            'posts' => $posts,
        );

        $this->responder->render( 'posts.twig', $data );
    }
}

The class requires two dependencies each modelling the remaining components of the pattern: the domain and the responder.
This is not a surprise: the “action” component is (kinda) like the “C” in “MVC” and act as a “controller” coordinating the domain and the the responder.
The handle method does little but fetching some posts from the domain and passing the wrapped result to the responder; easy enough.
The domain implementation is what one would expect:

// file src/adr/Domains/Domain.php

class adr_Domains_Domain {

    public function get( array $args ) {
        $query = new WP_Query( $args );

        return $query->have_posts() ? $query->posts : array();
    }
}

It is, for the time being, little more than a wrapper around the WP_Query class so much so that it will consume the same arguments.
There is really no benefit here, and yet, to reinvent the wheel.

Responder

Sticking with the “trusted tools” trend the responder is more complex in setup than in functionalities and is leveraging the Twig template engine in its PHP 5.2 compatible flavour.
The adr_Responders_Responder class is the one below:

// file src/adr/Responders/Responder

class adr_Responders_Responder {

    /**
     * @var Twig_Environment
     */
    protected $renderEngine;

    public function __construct( Twig_Environment $renderEngine ) {
        $this->renderEngine = $renderEngine;
    }

    public function render( $template, array $data ) {
        echo $this->renderEngine->render( $template, $data );
    }
}

It specifies, in its constructor method, an instance of the Twig_Environment class as a dependency.
The DI container, on its own, would create an instance of the class and inject it but the template engine requires some setup for it to be really ready to use; that “setup” phase is handled in the adr_ServiceProvider_Templates class:

// file src/adr/ServiceProviders/Templates.php

class adr_ServiceProviders_Templates extends tad_DI52_ServiceProvider {

    /**
     * Binds and sets up implementations.
     */
    public function register() {
        // when an instance of Twig_Environment is needed use this callback to build it
        // then always return that instance
        $this->container->singleton( 'Twig_Environment', array( $this, 'buildTwigEnvironment' ) );
    }

    /**
     * @return Twig_Environment
     */
    public function buildTwigEnvironment() {
        $root   = dirname( dirname( dirname( dirname( __FILE__ ) ) ) );
        $loader = new Twig_Loader_Filesystem( $root . '/templates' );
        $twig   = new Twig_Environment( $loader, array(
            'cache' => $root . '/templates/cache',
        ) );

        return $twig;
    }
}

After that the responder simply calls the Twig_Environment::render method passing the data on the following template:

<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Posts</title>
</head>
<body>
<main class="posts">
    {% if posts %}
        <ul>
            {% for post in posts %}
                <li class="post">
                    <h3>{{ post.post_title }}</h3>
                    {{ post.post_content }}
                </li>
            {% endfor %}
        </ul>
    {% else %}
        <p>Nothing found...</p>
    {% endif %}
</main>
</body>
</html>

The final result in all its “glory”:

And the passing test:

Next

I will iterate over the code to add more tests and try to tackle some less basic WordPress compatibility issues to make sure the full power of WordPress plugins can still play a role in the response.