Keeping Your Templates Clean with Sage's View Composers
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 </div> @else <p>No books found.</p> @endif </div> @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.