Action-Domain-Responder in WordPress – 03

Doing some standard WordPress stuff.

Previously

I’ve set out to try and implement the Action-Domain-Responder pattern in a WordPress plugin.
While the usefulness of it might amount to nothing in the end the code involved, and the techniques, I find stimulating.
The first post in the series might prove a better starting point than this one to understand the reasoning and the flow of the code I’m showing.

This is a big experiment

While I try to reach the goal of implementing the ADR pattern in the context of a vanilla WordPress installation I’m also working on the new possibilities of the WordPress module to put in place real functional testing in WordPress; the plugin is pulling wp-browser from this branch and the test code I’m showing is using it.
At the time of publishing this article the wp-browser branch is still experimental and this means some changes are in place in the plugin code; I’m requiring the dev-wploader-go-to branch of wp-browser in the plugin Composer configuration:

composer require --dev lucatume/wp-browser:dev-wploader-go-to

The functional suite is configured to use the WPDb, WPFilesystem and WordPress modules: the first two to manage the WordPress database and files, and the latter to provide the functional testing capabilities.
The WordPress module is then configured to handle requests in the same scope as the tests setting the insulated flag parameter to false:

# Codeception Test Suite Configuration
#
# Suite for functional tests
# Emulate web requests and make WordPress process them

actor: FunctionalTester
modules:
  enabled:
    - WPDb
    - Asserts
    - WPFilesystem
    - WordPress
    - \Helper\Functional
  config:
    WPDb:
      dsn: 'mysql:host=%DB_HOST%;dbname=%DB_NAME%'
      user: %DB_USER%
      password: %DB_PASSWORD%
      dump: 'tests/_data/dump.sql'
      populate: true #import the dump before the tests
      cleanup: true #import the dump between tests
      url: '%WP_URL%'
      urlReplacement: true #replace the hardcoded dump URL with the one above
      tablePrefix: 'wp_'
    WordPress:
      depends: WPDb
      insulated: false
      wpRootFolder: '%WP_ROOT_FOLDER%'
      adminUsername: 'admin'
      adminPassword: 'admin'
      adminPath: '/wp-admin'
    WPFilesystem:
      wpRootFolder: '%WP_ROOT_FOLDER%'

Was the insulated parameter of the WordPress module configuration not set or set to true it would behave as usual handling any request in a separate PHP process from the tests; to read more about the experimental branch have a look at this post.

Once the inebriating power of being able to route anything to anywhere wears out, a shortcoming of this solution kicks in: no template actions or filters are applied when rendering templates this way. WordPress power lies in its flexibility and ease of use and that is based largely on how its functionalities can be extended by themes and plugins; those themes and plugins, in turn, rely on the event based architecture of WordPress to run their business logic.
The definition “event based architecture” means, in code terms, actions and filters.
In its current incarnation the ADR plugin successfully handles the request for the /post page but does so without allowing any functionality provided by plugins to kick in.
The first remedy that comes to mind is to, at a minimum, fire the wp_head and wp_footer actions.
Before adding any code I add a new test to the functional suite, the first one actually, to make sure I know what I’m looking for:

codecept generate:cest functional "HooksSupport\HeadAndFooter"

Before answering the question “why a functional test?” it’s worth taking a look at the test code:

<?php
namespace HooksSupport;

use FunctionalTester;

class HeadAndFooterCest {

    public function _before(FunctionalTester $I) {
        $I->useTheme('twentyseventeen');
        add_filter('adr.debug', '__return_true');
        add_filter('klein_die_handler', function () {
            return 'echo';
        });
    }

    /**
     * It should run the wp_head action while managing routes
     *
     * @test
     */
    public function should_run_the_wp_head_action_while_managing_routes(FunctionalTester $I) {
        add_action('wp_head', function () use (&$fired) {
            $fired = true;
            echo '<meta foo="bar">';
        });

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

        // we are on a page managed by the plugin
        $I->canSeeElement('body.adr');
        // the action fired 
        $I->assertTrue($fired);
        // the action output is there
        $I->seeInSource('<meta foo="bar">');
    }

    /**
     * It should run the wp_footer action while managing routes
     *
     * @test
     */
    public function should_run_the_wp_footer_action_while_managing_routes(FunctionalTester $I) {
        add_action('wp_footer', function () use (&$fired) {
            $fired = true;
            echo '<p>Hello from the footer</p>';
        });

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

        // we are on a page managed by the plugin
        $I->canSeeElement('body.adr');
        // the action fired 
        $I->assertTrue($fired);
        // the action output is there
        $I->seeInSource('<p>Hello from the footer</p>');
    }
}

There are some things going on here that is worth pointing out.
In the _before method I’m filtering the default klein52 output handler to echo the output in place of using die to display it; since the handling is happening in the same process as the tests any code that is calling the die or exit instruction will kill the tests too; this is possible due to the fact the WordPress module is not running in “insulated” mode.
Leveraging the fact the request is being handled in the same process as the tests I’m hooking in the wp_head and wp_footer actions directly expecting the code will be executed; this is possible due to the fact the WordPress module is not running in “insulated” mode.
I’ve added a body class check in the test to make sure that result comes from the plugin and not from the theme simply handling the request; the test installation has the Twentyseventeen theme installed and if the plugin was deactivated the tests would fail on the check for the body.adr class (the theme has no notion of the adr class after all) check. The others would, instead, pass: the theme will call the wp_head and wp_footer actions and will output the actions output by default.

As usual the first test run results in a failure:

failing functional test

But why did I use a functional test in place of an acceptance one? Here my personal discretion kicks in.
The reason I call this a “functional” test has to do with the testing methodology: I’m accessing WordPress internals, hooking actions and checking on a body class, to prepare and check my tests.
In the assumption that acceptance tests simulate a user interaction, a normal user, one that does not use the developer tools to inspect the code for example, user would and could not make all these checks and set up operations; just my opinion though.

Passing the first test

What should happen for this tests to pass is that the body_class function, the wp_head function and the wp_footer function are called while rendering the output.
Since I’m relying on Twig to render the response output I modify the template to this:

<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>
    {{ wp_head() }}
</head>
<body {{ body_class('adr') }}>
<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>
{{ wp_footer() }}
</body>
</html>

In the template I’m using a Twig extension that will go from {{ function() }} to the output of that function.
No such extension exists by default in Twig so I’ve created one in the src/adr/Twig/FunctionExtension.php class:

<?php

class adr_Twig_FunctionExtension extends Twig_Extension {

    protected $supportedFunctions = array();

    public function setSupportedFunctions(array $supportedFunctions) {
        $this->supportedFunctions = $supportedFunctions;
    }

    public function getFunctions() {
        $functions = array();

        foreach ($this->supportedFunctions as $function) {
            $functions[] = new Twig_SimpleFunction($function, $function);
        }

        return $functions;
    }
}

Provided some functions the extension will register each as a legit Twig function; by default no functions are supported so: where is that list of supported functions set?
That list is set by the adr_ServiceProviders_Templates class; the service providers task is exactly to set up and provide one or more services ready to user and the “service” provided by this class is a ready to use Twig environment (an instance of the Twig_Environment class in fact):

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

class adr_ServiceProviders_Templates extends tad_DI52_ServiceProvider {

    /**
     * Binds and sets up implementations.
     */
    public function register() {
        $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');
        $options = array(
            'cache' => $root . '/templates/cache',
        );

        // if debugging never use cached templates
        if (apply_filters('adr.debug', false)) {
            $options['debug'] = true;
            $options['auto_reload'] = true;
        }

        $twig = new Twig_Environment($loader, $options);

        $functionExtension = new adr_Twig_FunctionExtension();

        // allow filtering the supported functions
        $supportedFunctions = array('wp_head', 'wp_footer', 'body_class');
        $supportedFunctions = apply_filters('adr.twig.supportedFunctions', $supportedFunctions);

        $functionExtension->setSupportedFunctions($supportedFunctions);
        $twig->addExtension($functionExtension);

        return $twig;
    }
}

I’m applying the adr.twig.supportedFunctions filter to allow myself and third-party plugins to filter the list of supported functions; for the time being I’m doing the least amount of job and setting the ones I need as defaults.
Once the code is in place the test will pass:

passing functional test

Why filter in the service provider?

Why not filter the supported functions in the class?
The answer is a personal one:

  1. if a filter or action is about setup I want it to be where the setup happens; when using service providers I put all setup processes in the service providers
  2. having the filtering happen in the class would mean the class will now depend on the apply_filters function and real unit testing, no WordPress involved, just became more difficult for no benefit
  3. I dislike the idea of the class “knowing” about its surroundings and use context if it can be avoided; while that’s a feeble defense for not filtering in the class, hooking on actions is a prime example of knowing and relying on the context

This applies to all filters and actions: I usually put them all in the service providers and that’s what I will do across the development of the plugin.

Next

I will try to “generalize” the code to separate the klein and do_parse_request based handling logic from the ADR implementation to build something reusable without the ADR pattern in mind; furthermore I will try to make the plugin play nicer with the active theme.