If you’ve spent any time wrangling WordPress themes, you know the struggle: templates that become a spaghetti monster of PHP logic, database queries, and HTML. It’s the kind of code that makes you long for a more structured, maintainable approach – something entirely foreign to the unholy database bloat championed by page builders like Divi or Elementor. That’s precisely why I fell in love with Sage. It brings a clean, MVC-like architecture to WordPress, leveraging modern tools like Blade for templates and Acorn for dependency injection, elevating development to a programmatic art form rather than a frantic exercise in patching together functions.
One of Sage’s unsung heroes in maintaining this pristine separation of concerns is the View Composer. Think of a View Composer as a backstage manager for your data. Instead of letting your Blade templates directly fetch data, process it, and then display it – a recipe for messy templates – View Composers handle all that heavy lifting before the data even reaches the presentation layer. They ensure your Blade files remain purely focused on presentation, making them a joy to read, debug, and maintain.
Let’s walk through a concrete example. Imagine we have a custom post type called ‘Book’ and we want to display a list of all books on a dedicated archive page, say using the template resources/views/page-books.blade.php. A common, less-than-ideal approach would be to dump WP_Query calls directly into that Blade file or even its corresponding PHP file. With Sage, we can do much better.
First, we’ll create a new View Composer. I usually place these in app/View/Composers. Let’s call ours BookArchiveComposer.php:
namespace App\View\Composers;
use Roots\Acorn\View\Composer;
use WP_Query;
class BookArchiveComposer extends Composer
{
/**
* List of views served by this composer.
*
* @var array
*/
protected static $views = [
'page-books',
];
/**
* Data to be passed to view before rendering.
*
* @param array $data
* @return array
*/
public function with($data)
{
return array_merge($data, [
'books' => $this->getBooks(),
]);
}
/**
* Get all books.
*
* @return array
*/
protected function getBooks()
{
$query = new WP_Query([
'post_type' => 'book',
'posts_per_page' => -1, // Get all books
'orderby' => 'title',
'order' => 'ASC',
]);
return collect($query->posts)->map(function ($post) {
return (object) [ // Return simple object for clean template access
'title' => get_the_title($post),
'link' => get_permalink($post),
'excerpt' => get_the_excerpt($post),
];
})->toArray();
}
}
In this composer, protected static $views = ['page-books']; tells Sage that this composer should run whenever the page-books template is rendered. The with() method is where the magic happens; it’s responsible for returning an array of data that will be available to the view. Here, I’ve created a private getBooks() method to encapsulate the WP_Query logic, returning a clean array of book objects. This keeps with() focused on preparing what’s passed to the view, and you’ll notice the use of collect() – a handy Laravel Collection helper made available through Acorn – to easily transform the WP_Query posts into simple, consumable objects.
Now, our resources/views/page-books.blade.php template becomes incredibly neat and focused:
@extends('layouts.app')
@section('content')
<div class="container">
<h1>Our Book Collection</h1>
@if ($books)
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
@foreach ($books as $book)
<article class="bg-white p-6 shadow rounded">
<h2 class="text-xl font-bold mb-2">
<a href="{{ $book->link }}" class="text-blue-600 hover:text-blue-800">
{{ $book->title }}
</a>
</h2>
<p class="text-gray-700">{{ $book->excerpt }}</p>
</article>
@endforeach
@else
<p>No books found.</p>
@endif
@endsection
See how page-books.blade.php now only focuses on displaying the $books variable? There’s no new WP_Query(), no have_posts(), the_post(), or while loops. The data is simply there, ready to be rendered. This makes your templates highly readable, testable, and reusable. If the data fetching logic for books ever changes, you only touch BookArchiveComposer.php, not dozens of presentation files.
This simple technique is a game-changer. It embodies Sage’s philosophy of bringing modern development practices – like robust separation of concerns and maintainability – to WordPress. By consistently using View Composers, you build a codebase that is predictable, scalable, and a genuine pleasure to work with, a stark contrast to the unwieldy monoliths that plague traditional WordPress development.