View Composers, passing data to partials and staying DRY

by Atanas Angelov | Jan 24th, 2019 | Tutorials

What are View Composers?

Have you ever needed a way to pass some variables to a template partial so that you can reuse it in different circumstances? For example, let’s say we have a user-badge.php partial that looks like this:

<div class="user-badge">
    <div class="user-badge__avatar">
        <?php echo get_avatar( $user_id, 64 ); ?>
    </div>
    <div class="user-badge__name">
        <?php echo esc_html( $user_name ); ?>
    </div>
</div>

It would be nice if we could reuse it to display the current user’s badge as well as his/her friends’ badges by passing different $user_id and $user_name values whenever we use it.

Let’s check our options!

include ‘partials/user-badge.php’

The simplest and most straightforward way to include a partial is to, well, include it. Unfortunately there are a few downsides to this approach, the major ones being:

  1. The entire current scope is shared and can be polluted by the partial.
  2. We have to prepare the $user_name variable every time we want to include the partial which would make us repeat code (you should always strive to remain DRY). It would be much better if the partial only needed a $user_id and loaded anything else it requires automatically.
  3. It won’t work if we are using Blade, Twig or any custom view engine as their views have to be processed first. While it is nice to have options, let’s say that we don’t need this for our project, for simplicity’s sake.

get_template_part( ‘partials/user-badge.php’ )

This nifty core function allows us to include a partial while isolating its scope. One less problem to think about, right? Not really – we only replaced our scope issue with the inability to pass any variables at all since get_template_part() does not have this capability, unfortunately.

Global variables

The knee-jerk reaction to not being able to pass any variables with get_template_part() would be to use global variables. While this would allow us to pass data we will be polluting the global scope instead of the local one.

Using a custom function with extract()

We can write a little function which accepts a partial and some data then includes the file like this:

function my_template_part( $__path, $__context ) {
    $__template = locate_template( $__path );
    extract($__context);
    include $__template;
}

Not sure what extract() does? Check out the documentation here.

With this approach we will have a somewhat isolated scope (while not perfect, the $__ prefix will help out with accidental name clashes) and the ability to pass data! We’re almost there but we still have to repeat our $user_name preparation logic.

Additing our $user_name logic to the partial

We can tackle the repetition problem by adding our $user_name logic to the partial itself:

<?php
$user      = get_userdata( $user_id );
$user_name = $user->display_name;
?>
<div class="user-badge">
    <div class="user-badge__avatar">
        <?php echo get_avatar( $user_id, 64 ); ?>
    </div>
    <div class="user-badge__name">
        <?php echo esc_html( $user_name ); ?>
    </div>
</div>

While this will work, it has a cost – we have to mix business logic with presentation. This is something that we should always avoid as it is considered bad practice and causes technical debt.

View Composers to the rescue

A View Composer in WP Emerge is a class that:

  • Prepares and passes context to views;
  • Can be assigned to one or more views;
  • Has an isolated scope keeping business logic and presentation separate;
  • Works in an engine-agnostic way;

If you are a Sage user, this is very close to what Sage calls Controllers (just don’t mix them up with WP Emerge Controllers).

Great – this covers all we need and then some! Let’s get started.

Creating a View Composer

If we are using the starter theme we can use the CLI tool to generate a View Composer:

./vendor/bin/wpemerge make:view-composer UserBadgePartialViewComposer

which will generate app/src/ViewComposers/UserBadgePartialViewComposer.php:

<?php

namespace App\ViewComposers;

use WPEmerge\View\ViewInterface;

class UserBadgePartialViewComposer {
    public function compose( ViewInterface $view ) {
        $view->with( [
            'foo' => 'bar',
        ] );
    }
}

The compose() method will be called whenever a view has to be composed and the $view->with() method is what we can use to pass context to it. this example will result in a $foo variable with the value of 'bar' available in our view.

Let’s adjust the compose() method to what we actually need:

public function compose( ViewInterface $view ) {
    // Get any user_id that is passed to this view; default to 0 otherwise.
    $user_id = $view->getContext( 'user_id', 0 );

    // Load the current user if no user_id is passed.
    if ($user_id <= 0) {
        $user_id = get_current_user_id();
    }

    $user = get_userdata( $user_id );

    // Return empty data if the user was not found.
    if ( false === $user ) {
        $view->with( [
            'user_exists' => false,
            'user_id'     => 0,
            'user_name'   => '',
        ] );
    } else {
        $view->with( [
            'user_exists' => true,
            'user_id'     => $user->ID,
            'user_name'   => $user->display_name,
        ] );
    }
}

You’ll notice that we’ve spiced it up a bit so it can handle cases where no user ID is passed or where an invalid user ID is passed.

The next step is to assign our composer to our partial. We will do this inside app/framework.php:

View::addComposer(
    'partials/user-badge',
    \App\ViewComposers\UserBadgePartialViewComposer::class
);

We would do this immediately after calling WPEmerge::boot() instead if we are not using the WP Emerge Starter Theme.

And finally let’s finish up our partial:

<?php if ( $user_exists ) : ?>
    <div class="user-badge">
        <div class="user-badge__avatar">
            <?php echo get_avatar( $user_id, 64 ); ?>
        </div>
        <div class="user-badge__name">
            <?php echo esc_html( $user_name ); ?>
        </div>
    </div>
<?php else : ?>
    <div class="user-badge user-badge--not-found">
        <?php esc_html_e( 'Could not find user.', 'app' ); ?>
    </div>
<?php endif; ?>

Using our new user badge partial

We’re finally ready to use our brand new user badge partial:

// Show the current user's badge:
Theme::partial( 'user-badge' );

// Show the badge of the user with ID 4:
Theme::partial( 'user-badge', ['user_id' => 4] );

// Assuming we don't have a user with an ID of 99999999,
// we will test our handling of invalid IDs:
Theme::partial( 'user-badge', ['user_id' => 99999999] );

We would use app_view() instead of Theme::partial() if we are not using the WP Emerge Starter Theme.

In summary

  • Avoid writing quick and dirty context passing solutions as they have a number of pitfalls.
  • Define a View Composer class instead.
  • Assign that View Composer to the desired view.
  • Utilize your new context in the desired view.
  • Include your new view through Theme::partial() or app_view().

For more information on View Composers be sure to check out the documentation or join me in the WP Emerge Gitter Lobby for a quick chat!