Action-Domain-Responder in WordPress - 04

Setting query flags.

Part of a series

This post is part of a series of posts where I attempt to implement the Action-Domain-Responder pattern in the context of WordPress and in the form of a WordPress plugin; the first post in the series might prove a better starting point than this one.
The starting code for this post can be found on GitHub under the dev-3 tag.

Thinking of other plugins

In the previous post I wrote some tests, and the code to pass them, to make sure that when the plugin is handling requests in its own way support for WordPress basic actions and filters, vie the wp_head(), wp_footer() and body_class() functions, is still available.
A further step in that direction is to make sure the “context” of those requests, properties on the global wp_query object, are set to allow themes and plugins to “know what is happening”; any call to contextual functions like is_home() or is_single() relies on those parameters to be set and correct.
To test for this support I add a functional test:

codecept generate:cest functional "WPQuery\BasicSupport"

And write the following test hitting, again, the /posts route:

<?php

namespace WPQuery;

use FunctionalTester;

class BasicSupportCest {

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

    /**
     * It should correctly set global wp_query flags for route
     *
     * @test
     */
    public function should_correctly_set_global_wp_query_flags_for_route( FunctionalTester $I ) {
        $I->amOnPage( '/posts' );

        $expected_flags = [
            'is_single'            => false,
            'is_preview'           => false,
            'is_page'              => false,
            'is_archive'           => true, // the page is an archive of posts
            'is_date'              => false,
            'is_year'              => false,
            'is_month'             => false,
            'is_day'               => false,
            'is_time'              => false,
            'is_author'            => false,
            'is_category'          => false,
            'is_tag'               => false,
            'is_tax'               => false,
            'is_search'            => false,
            'is_feed'              => false,
            'is_comment_feed'      => false,
            'is_trackback'         => false,
            'is_home'              => false,
            'is_404'               => false,
            'is_paged'             => false,
            'is_admin'             => false,
            'is_attachment'        => false,
            'is_singular'          => false,
            'is_robots'            => false,
            'is_posts_page'        => true, // the page is the one dedicated to posts
            'is_post_type_archive' => 'post', // the page is the "post" post type archive
        ];

        /** @var \WP_Query $wp_query */
        global $wp_query;

        foreach ( $expected_flags as $key => $value ) {
            $I->assertEquals( $wp_query->{$key}, $value, "Global wp_query {$key} does not match expected {$value}" );
        }
    }
}

Running the test confirms there is something the plugin is not currently doing:

Passing the test requires, as a first step, deciding which part of the Action-Domain-Responder pattern will be in charge of setting the global query flags; the Responder part can be excluded immediately as responsible for handling the response.
The candidates remaining are, then, the Action component and the Domain component; I’ve tried to give a personal and concise interpretation of what each might be in charge of in WordPress terms in the first post of the series: it’s worth getting back to those definitions to make a decision:

  • 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.

In light of this definition I chose the Domain component as the one responsible for it.

Setting the query flags

Choosing the /posts page as a first route to test was not casual: there are a number of non intuitive things to fix in how the built-in post type is set up by default and how I’d like it to behave; the flags set in the tests above are a testimony to it.
By default for the post post type:

  • there is no archive dedicated to it; the is_archive flag would be false
  • is_posts_page is true only when visiting the page set to be the “Posts page” from the Admin UI
  • since it does not have an archive is_post_type_archive too would be false

I update the adr_Domains_Domain class to take care of the task:

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

class adr_Domains_Domain {

    /**
     * @var \WP_Query
     */
    protected $query;

    public function __construct( WP_Query $query ) {
        $this->query = $query;
    }

    public function get( array $args ) {
        $this->query->parse_query( $args );
        $posts = $this->query->get_posts();

        /** @var WP_Query $wp_query */
        global $wp_query;
        $this->set_query_flags( $args );
        $wp_query = $this->query;

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

    protected function set_query_flags( array $args = null ) {
        if ( empty( $args ) ) {
            return;
        }

        if ( ! empty( $args['post_type'] ) && $args['post_type'] === 'post' ) {
            $this->query->is_archive           = true;
            $this->query->is_post_type_archive = 'post';
            $this->query->is_posts_page        = true;
        }

        $this->query->is_home = false;
    }
}

Running the tests again yields the expected success:

From the code it’s clear the implementation tailor-made to pass the test at hand and will require more testing and refinements to handle other requests in a flexible way.

Next

The code I’m showing in this post is on GitHub tagged dev-04 for anyone to see.
I will refactor the code to generalize the plugin: the main objective of this will be to make broader testing possible and, collaterally, making the plugin re-usable outside of a specific implementation.