Working With View Composers in Sage 10

View Composers are new to Sage 10 and are based on Laravel’s View Composers, which are described this way:

View composers are callbacks or class methods that are called when a view is rendered. If you have data that you want to be bound to a view each time that view is rendered, a view composer can help you organize that logic into a single location.

In slightly more Sage-specific terms, a View Composer passes some data to one or more views when they’re rendered. This allows you to tie some logic and data to your view without polluting your templates.

They can also be a replacement for Controller or Sage’s filters (although you can also use them in concert, if you like). Perhaps most excitingly they give you the ability to scope variables to your views, which allows you to write views in a freer, more readable way.

Scoping variables

Currently both Controller and Sage’s filters target things through the WordPress template hierarchy. This is useful, but limiting: any variables you pass are available to every single blade called for that template endpoint. This brings us some of the benefits of working with a global scope, but as is always the case when we work with global scope, it also introduces problems. To get around those problems, you have to structure the data passed to your views with a focus on avoiding collisions, not on readability and simplicity. View Composers greatly improve this situation by allowing you to scope variables to individual views, instead of entire template endpoints. Take the following:

// partials/header.blade.php
<header class="{{ $class }}">
  <img src="{{ $img_url }}">
  <h1>{!! $title !!}</h1>
</header>

// partials/list.blade.php
<img src="$img_url" class="list-flourish">
<ul class="{{ $class }}">
  @foreach($list as $item)
    <li>{!! $item !!}</li>
  @endforeach
</ul>

// page.blade.php
@include('partials.header')
@include('partials.list')

Here are a couple partials that are clear and easy to read: the variables make sense and it’s easy to see what the partial will do.

Unfortunately, because variables are passed in the same way to all views, both the header and the list will end up with the same $class and the same $img_url and for at least one of them those variables will contain the wrong values.

A View Composer handles that easily:

// Composers/Header.php
namespace App\Composers;
use Roots\Acorn\View\Composer;
class Header extends Composer
{
    protected static $views = [
        'partials.header',
    ];

    public function with($data, $view)
    {
        return [
            'img_url' => get_attachment_image_url(get_post_thumbnail_id()),
            'class' => 'hero',
            'title' => $post->post_title,
        ];
    }
}

// Composers/List.php
namespace App\Composers;
use Roots\Acorn\View\Composer;
class Header extends Composer
{
    protected static $views = [
        'partials.list',
    ];

    public function with($data, $view)
    {
        return [
            'img_url' => Roots\asset('images/list-flourish.png'),
            'class' => 'special-list',
            'items' => get_field('list_items'),
        ];
    }
}

Because these variables will be passed directly to the views themselves, they are effectively scoped to them.

// partials/header.blade.php
var_dump($class); // 'hero'

// partials/list.blade.php
var_dump($class); // 'special-list'

// page.blade.php
var_dump($class); // 'undefined variable' error

But…

Keep in mind that variables passed to a view by a View Composer will also be available to all views @includeed by that one. A View Composer just allows you to choose where that inheritance chain will begin.

with() vs override()

As you can see above, in most cases you’ll be using with() to pass data to your views. This method receives whatever data is in the pipeline when the View Composer is called, as well as an instance of the view, and returns a keyed array with all the data you wish to pass to your view.

with() is the "gentle" method; it has a much more forceful sibling named override() that does exactly what it says on the tin. If you’d like a peek at the nitty-gritty you can dive into the source code, but the long and short of it is that data added with with() is handed to the data pipeline to be given to the views, but it will only augment, never override. override() on the other hand, will override any variable with which it shares a key.

Take this example:

// Composers/Hero.php
namespace App\Composers;
use Roots\Acorn\View\Composer;
class Hero extends Composer
{
    protected static $views = [
        'partials.hero',
    ];

    public function with($data, $view)
    {
        return ['class' => 'hero'];
    }
}

// Composers/Heading.php
namespace App\Composers;
use Roots\Acorn\View\Composer;
class Heading extends Composer
{
    protected static $views = [
        'partials.heading',
    ];

    public function with($data, $view)
    {
        return ['class' => 'heading'];
    }
}

// partials/heading.blade.php
<h1 class="{{ $class }}">Welcome</h1>

// partials/hero.blade.php
<div class="{{ $class }}">
  @include('partials.heading')
</div>

// result
<div class="hero">
  <h1 class="hero">Welcome</h1>
</div>

Because you’ve used with(), the $class variable is not overridden by the Heading View Composer: The key class already exists in the $data that Heading receives, which means that it it will consider that data already "set" and not override it.

If we use override(), though, then it will work just as we want:

// Composers/Heading.php		
// ...
public function override($data, $view)
{
    return ['class' => 'heading'];
}
// ...
// result
<div class="hero">
  <h1 class="heading">Welcome</h1>
</div>

If you’d like to see where the magic happens, you can check out these lines of code that compose the data before passing it off to the view, but the long and short of it is that a humble array_merge combines them with the existing data.

Where does your data come from?

So we’ve talked about how to pass data to your views, but where do you get it from? In general, you’ll be getting in one of two ways:

  1. Generating it internally in the View Composer, i.e. via WP_Query or get_post_meta()
  2. Plucking it out of the $data passed to your View Composer

Generate it internally

This is for situations where the context in which the view has been called is unimportant, or can be easily inferred by globally available information (i.e. is_home()). This will usually be partials that are the same across your site, such as your main navigation menu, or a site-wide call-to-action in the footer. All you want to do in your template is @include the partial in question and head off to the races.

// Composers/FooterNavigation.php
namespace App\Composers;
use Roots\Acorn\View\Composer;
class FooterNavigation extends Composer
{
    protected static $views = [
        'partials.navigation.footer',
    ];

    public function with($data, $view)
    {
        return [
            'heading' => $this->heading(),
            'menu' => $this->menu(),
        ];
    }

    public function heading()
    {
        return get_field('footer_menu_heading', 'option') ?? false;
    }

    public function menu()
    {
        if($menu_item_ids = get_field('footer_menu_items', 'option') {
            return array_map(function($item) {
                    $page = get_post($item);
                    return [
                        'title' => $page->post_title,
                        'url' => get_permalink($page),
                ];
            }, $menu_item_ids);
        }

        return false;
    }
}

// partials/navigation/footer.blade.php
<nav>
  @if($heading)
    <h3>{!! $heading !!}</h3>
  @endif
  @if($menu)
    <ul>
      @foreach($menu as $item)
        <li><a href="{{ $item['url'] }}">{{ $item['title'] }}</a></li>
      @endforeach
    </ul>
  @endif
</nav>

Get it from $data

$data is the context as it exists when your view is @includeed. This means that any variable available to that view is available to your View Composer as well. $data is a keyed array, and the keys are the same as the variable names that can be used in the blade.

This approach is useful when you need to use, or want to control, the context in which a view is used. It allows you to write reusable, understandable code, because you can pass variables to views in your templates, and have those variables available to your View Composers.

A good but simple example might be a partial that wraps images in a <figure> and adds a <figcaption>:

// Composer/Figure.php
namespace App\Composers;
use Roots\Acorn\View\Composer;
class Figure extends Composer
{
    protected static $views = [
        'partials.figure',
    ];

    public function with($data, $view)
    {
        return [
            'image' => $this->image($data),
            'caption' => $this->caption($data),
        ];
    }

    public function image($data)
    {
        if (!is_array($data) || !isset($data['image_id']) {
            return false;
        }

        return wp_get_attachment_image($image_id);
    }

    public function caption($data)
    {
        if (!is_array($data) || !isset($data['image_caption']) {
            return false;
        }

        return $data['image_caption'];
    }
}

// partials/figure.blade.php
@if($image)
  <figure>
    {!! $image !!}
    @if($caption)
      <figcaption>{!! $caption !!}</figcaption>
    @endif
  </figure>
@endif

// page.blade.php
@include('partials.figure', [
  'image_id' => $page_featured_image_id, 
  'caption' => $page_featured_image_caption,
])
// You could also define `image_id` and `image_caption` in the
// Composer for `page.blade.php`, but sometimes passing them in
// the template can make the template behavior a bit more clear.

Now you can use the figure partial to wrap any image you have an id for in a <figure> with a caption: All of the logic is abstracted away from your view, making it much more declarative and easy to read.

Extending View Composers

Say you have two designs for a card that differ only cosmetically, and use the same general data types (i.e. a title, an image id, and a link), and that data needs to be processed in identical ways (i.e. generate an image of a particular size from the id)…but the data can come from completely different sources: One card is used to preview blog posts, while the other is used for manually-curated cards that link off site. This is a perfect case for create a base View Composer, and then extending it.

// Composers/CardBase.php
namespace App\Composers;
use Roots\Acorn\View\Composer;
class CardBase extends Composer
{
    // Note that we don't define a list of partials!
    // The base is not associated with a partial.
    
    protected static $dataMethods = [
        'title',
        'image',
        'url',
    ];

    public function with($data, $view)
    {
        return array_map(function($method) use ($data, $view) {
            if (method_exists($this, $method)) {
                return [$method => $this->{$method}($data, $view)];
            }

            return [$method => false];
        }, $this::$dataMethods);
    }

    public function title($data, $view) { /* title logic */ }
    public function image($data, $view) { /* image logic */ }
    public function url($data, $view) { /* url logic */ }
}

// Composers/PostPreview.php
namespace App\Composers;
class PostPreview extends CardBase
{
    protected static $views = [
        'partials.post.preview',
    ];
}

// Composers/ExternalLink.php
namespace App\Composers;
class ExternalLink extends CardBase
{
    protected static $views = [
        'partials.external',
    ];

    public function url($data, $view) { /* ExternalLink-specific url logic */ }
}

This way, all of the necessary logic is concentrated in the base View Composer, and the View Composers extending it only need to define their views—or possible override logic as needed.

The array_map() technique used in with() also allows for inheritance to be a little more gentle, as it makes sure that any variables that we want to pass to a view will have some value associated with them, even if no method for that variable has been defined. This is a technique you should be careful with, because in a certain context it is suppressing errors. Only use it when the lack of a method (and a variable being set to false) is a valid an expected condition, not indicative of a larger problem.

Simplifying the view relationship declaration

In the previous examples I used a static property called $views to hold an array with all the views I want the View Composer to be associated with. If you’re going to associate a View Composer with multiple views, this is absolutely necessary. If your View Composer is only associated with a single view, though, you can skip that property entirely if the directory structure and name of the View Composer match that of the view.

For example:

// Composers/Partials/Hero.php
namespace App\Composers\Partials;
use Roots\Acorn\View\Composer;

class Hero extends Composer
{
    public function with($data, $view)
    {
        return ['class' => 'hero'];
    }
}

This version of the hero View Composer we made earlier will be loaded automatically for the view at partials/hero.blade.php because their paths (relative to the Composers and views directories respectively) and filenames (ignoring capitalization) are the same. This saves you some typing, and makes the relationship between View Composer and view a little more intuitive.

View Composers and Controller and filters

As I mentioned in the introduction, much of the functionality provided by View Composers is similar to that provided by the excellent soberwp/controller from Sage 9, as well as the filter system built into Sage for delivering data to views. Both of these systems work great, but I’ve used View Composers on two projects now (one launched, and one about to launch), and I’ve found it more pleasant and more fun to work with.

View Composers allow you to work with a cohesive mental model where template partials are directly and easily connected to what could be called controller partials. It allows you to modularize your logic as you modularize your view in a way that was either impossible or difficult with the previous systems. Since they’re class-based, you also get both encapsulation and inheritance for "free," which helps to reduce errors, collisions, and reused code.

The encapsulation of methods also encourages writing many simple pure(ish) methods that just return or modify data, making your View Composer easier to read, debug, and understand. It’s also encouraged me to go ever further with modularizing my components so that instead of trying to manage several large components, I just compose (ha) new ones out of smaller, simpler modules.

Like everything Sage provides, they’re a tool, and you can use them or not use them. My own experience with them has been that, just like other technologies Sage has pushed me to learn—Blade, webpack, etc—I’ve become a slightly better programmer through using them, and they’ve made my projects better and more fun to work on.

Join the discussion on Roots Discourse

Help support our open-source development efforts

Help grow Roots

Join over 6,000 subscribers on our newsletter to get the latest Roots updates, along with occasional tips on building better WordPress sites.

Looking for WordPress plugin recommendations, the newest modern WordPress projects, and general web development tips and articles?

“Easily the best WordPress email I get.” Colin OBrien

Follow us on Twitter @rootswp

Ready to checkout?