Yet another post object wrapping 04

Easy author fetching and inclusion considerations.

A rough code interface

In an earlier post I’ve laid out a desirable interface for a post object alternative to the traditional WP_Post one.
The “end” of such an interaction is this post object I will consume, typically in templates.
I came up with some examples that hid, in fact, some gotchas.

The author object

One of the examples is “transparent” access to a post author data.
Getting a post object through the get_post function will fill the post_author property of the post object with a user ID; a second query to the database will yield a wholly formed WP_User object that can be retrieved using that ID.
In vanilla WordPress terms the code would be

foreach($posts as $post){
   $author = get_user_by('id', $post->post_author);
   $author_name = $author->last_name . ', ' . $author->first_name;
}

while the more streamlined proposed approach would be

foreach($posts as $post){
   $author_name = $post->author->last_name . ', ' . $post->author->first_name;
}

Before delving into the extension of the repository class (tad_PostRepository) capabilities I have to make a modification to the post class (tad_Post) as now the code

$author = $post->author;

will try to access a meta value and return null.

Smaller tests

The Codeception testing apparel will not take offense in running tests from many files and I like to keep things separated for the sake of my future self and the underlying gamification that keeps me going when having to refactor test files.
I’ve hence scaffolded a new test case to test the author functionalities

wpcept generate:functional tad_Post_Author

and ran a first test to make sure things are failing the way I’ve anticipated

class tad_Post_AuthorTest extends \WP_UnitTestCase {

    protected $backupGlobals = false;

    /**
     * @test
     * it should return a WP_User object when accessing the author property
     */
    public function it_should_return_a_wp_user_object_when_accessing_the_author_property() {
        $id = $this->factory->post->create( [ 'post_author' => 1 ] );

        $sut = new tad_Post( $id );

        $author = $sut->author;
        $this->assertInstanceOf( 'WP_User', $author );
    }

}

Red light, ok.
Failing author test

To pass the test I’ve added a chain of methods that will take care of lazily instantiating the author WP_User object when the author is requested.

class tad_Post implements tad_PostInterface {

    /**
     * Gets a value from the post object.
     *
     * If the key does not refer to a defined WP_Post property then a single meta with that name will be fetched.
     *
     * @param $key
     * @param $default
     *
     * @return mixed|null
     */
    public function get( $key, $default = null ) {

        if ( in_array( $key, $this->get_inclusion_keys() ) ) {
            return $this->get_included( $key, $default );
        }

        $this->fetch_meta();
        $this->fetch_terms();

        if ( array_key_exists( $key, $this->postarr ) || $alias = array_key_exists( $key, $this->get_column_aliases() ) ) {
            if ( ! empty( $alias ) ) {
                $key = $this->get_column_from_alias( $key );
            }

            return $this->post->$key;
        }
        if ( array_key_exists( $key, $this->post_meta ) ) {
            $single = in_array( $key, $this->get_single_meta_keys() );

            return $this->get_meta( $key, $single, $default );
        }
        if ( array_key_exists( $key, $this->terms ) ) {
            $single = in_array( $key, $this->get_single_term_keys() );
            $terms  = $this->get_terms( $key, $default );

            return $single ? reset( $terms ) : $terms;
        }

        return $default;
    }

    /**
     * Explicit magic method implementation.
     *
     * @param      $key
     *
     * @return mixed|null
     */
    public function __get( $key ) {
        return $this->get( $key );
    }

    /**
     * @return false|WP_User
     */
    public function get_author() {
        if ( empty( $this->author ) ) {
            $this->_author = get_user_by( 'id', $this->post_author );
        }

        return $this->_author;
    }

    public function get_inclusion_keys() {
        return array( 'author' );
    }

    public function get_included( $key, $default ) {
        switch ( $key ) {
            case 'author'    :
                return $this->get_author();
        }
    }
}

All green, fine.
Passing author test

Inclusions

Lazily fetching elements objects related to the post is a fine approach when not all of afore mentioned objects will be accessed but the number of queries made to the database might get exceedingly high in cases like the one below

$posts = tad_PostRepository::find('all', array('posts_per_page' => 20));

foreach($posts as $post){
    $author = $post->author;

    // do something with author
    $author->...

    $comments = $post->comments;

    foreach($comments as $comment){
        // do something with comments
        $comment->...
    }
}

WordPress will make a number of accessory queries to the database but the ones determined by explicit code like the one above wil be:

  • 1 to get the 20 posts
  • 20 to get each post author
  • 20 to get each post comment

41 in total. Without going into performance details, those only have sense in context, I’d like to be able to specify what should be included along with the post object during the post finding phase in the tad_PostRepository::find('all', ...) call.
This would change the first line of the code to

$posts = tad_PostRepository::find('all', 
    array(
        'posts_per_page' => 20,
        'include' => array('author', 'comments')
));

foreach($posts as $post){
    $author = $post->author;

    // do something with author
    $author->...

    $comments = $post->comments;

    foreach($comments as $comment){
        // do something with comments
        $comment->...
    }
}

That should keep the query number down to:

  • 1 to get the 20 posts
  • 1 to get the authors for the 20 posts
  • 1 to get the comments for the 20 posts

This approach will increase the size of the queries while reducing their numbers.
In the end it will be a decision to be taken ad hoc for each WordPress project yet it’s an added possibility that I’m not willing to let slip and one that’s covered in many ORM systems like the ones inspiring this coding effort (Laravel Eloquent ORM and Doctrine ORM).

Next

Anticipating the inclusion possibility I will need to rework some code on the base tad_Post class to move on the repository after that.

I appreciate your input