Skip to main content

Beyond the Loop: Sage View Composers for Pristine WordPress Data

When I first dipped my toes into WordPress development, the "WordPress Way" often felt like a Wild West of mixed logic and presentation.

  • in Roots Sage
  • 3 min read
  • Published · Updated

When I first dipped my toes into WordPress development, the “WordPress Way” often felt like a Wild West of mixed logic and presentation. Queries directly in template files, global variables everywhere, and an overall lack of structure that quickly turned into a maintenance nightmare. Then came the page builders like Divi and Elementor, promising ease of use but delivering a different kind of unmaintainable mess: opaque database bloat and rigid frontend code that was impossible to customize programmatically. As a developer, I was always searching for a better path, a way to build robust, maintainable WordPress sites that felt like modern applications. That’s when I discovered Sage.

Sage, built on top of Laravel’s components via Acorn, completely reshapes WordPress development into an MVC-like architecture that just makes sense. It empowers us to separate concerns elegantly: our models handle data, our controllers (or in Sage’s case, View Composers) prepare data, and our views (Blade templates) focus solely on presentation. This isn’t just a philosophical preference; it’s a practical approach that leads to dramatically cleaner, more testable, and ultimately more scalable codebases. Our Blade templates become truly presentation-focused, utilizing powerful features like components and directives without ever touching a WP_Query directly. Data is routed cleanly and programmatically, and modern dependency injection is a first-class citizen thanks to Acorn.

Today, I want to walk through one of my favorite Sage patterns: the View Composer. If you’ve ever found yourself wishing you could pass data to your WordPress templates without cluttering them with get_posts() or WP_Query instances, View Composers are your answer. They allow you to prepare data for a specific Blade view or set of views and inject it, much like a mini-controller, before the view is rendered. Let’s say we’re building a portfolio site and we need to display a list of our latest “Projects” (a custom post type) in a partials.latest-projects component, perhaps on a homepage or a sidebar.

Without a View Composer, you might be tempted to put a WP_Query directly into your latest-projects.blade.php file, something like this:

@php
  $projects = new WP_Query([
      'post_type'      => 'project',
      'posts_per_page' => 3,
  ]);
@endphp

@if ($projects->have_posts())
  <ul>
    @while ($projects->have_posts()) @php $projects->the_post() @endphp
      <li><a href="@php the_permalink() @endphp">@php the_title() @endphp</a></li>
    @endwhile
  </ul>
  @php wp_reset_postdata() @endphp
@endif

This works, but it breaks the “separation of concerns” principle. Your presentation layer is now responsible for fetching data. With a View Composer, we can move that data fetching logic entirely out of the template.

First, we’d create a View Composer. In Sage, these typically live in app/View/Composers. Let’s create app/View/Composers/LatestProjects.php:

namespace App\View\Composers;

use Roots\Acorn\View\Composer;

class LatestProjects extends Composer
{
    /**
     * List of views served by this composer.
     *
     * @var array
     */
    protected static $views = [
        'partials.latest-projects',
    ];

/**
     * Data to be passed to view before rendering.
     *
     * @param  array $data
     * @return array
     */
    public function with($data)
    {
        return array_merge($data, [
            'projects' => $this->getLatestProjects(),
        ]);
    }

/**
     * Get the latest projects.
     *
     * @return array
     */
    protected function getLatestProjects()
    {
        $query = new \WP_Query([
            'post_type'      => 'project',
            'posts_per_page' => 3,
            'orderby'        => 'date',
            'order'          => 'DESC',
            'no_found_rows'  => true, // Optimize for performance if no pagination needed
        ]);

return $query->posts; // Return array of post objects
    }
}

Now, our partials.latest-projects.blade.php becomes wonderfully simple and truly presentation-focused:

@if (!empty($projects))
  <ul>
    @foreach ($projects as $project)
      <li><a href="{{ get_permalink($project->ID) }}">{{ $project->post_title }}</a></li>
    @endforeach
  </ul>
@endif

Notice how the $projects variable just appears in the template. The View Composer ensures it’s there before the template is even rendered. We’ve moved all the WordPress query logic into a dedicated, testable class. This makes our templates far more readable and maintainable, as they only deal with displaying the data they are given. It also centralizes the logic, so if the way we fetch “latest projects” changes, we only need to update the composer, not every template that uses it.

This is the power of Sage and its underlying Acorn framework. It encourages clean architecture, empowers developers with modern tools, and frees us from the messy “WordPress Way” of the past. If you’re tired of wrestling with database bloat and spaghetti code, Sage is a breath of fresh air, providing a structured, programmatic approach that makes WordPress development enjoyable again.