← All posts

Sage View Composers: Taming Your Custom Post Type Data for Cleaner Templates

Sage View Composers: Taming Your Custom Post Type Data for Cleaner Templates

If you’ve ever found yourself sifting through a WordPress theme’s template-parts directory, only to encounter a spaghetti mess of WP_Query calls interspersed with HTML, you know the frustration. It’s hard to read, harder to debug, and nearly impossible to maintain. This is precisely why I champion Sage for WordPress development. It brings a robust, modern architecture, letting us build truly maintainable sites. Today, I want to dive into one of its most powerful features for keeping your templates pristine: View Composers.

Imagine you have a custom post type called "Portfolio" and you need to display a list of these projects on a dedicated archive page, say `page-portfolio-archive.blade.php`. In a traditional WordPress setup, you'd likely dump a `new WP_Query()` right into that template file. While it works, it violates the principle of separation of concerns – your presentation layer is now directly handling data fetching. This isn't just ugly; it makes your templates brittle and harder to test.

Sage, powered by Acorn and Laravel's Blade templating engine, offers a far more elegant solution: View Composers. A View Composer is essentially a class that prepares data and passes it to a specific view before that view is rendered. It acts as a bridge, ensuring your Blade templates remain entirely focused on *how* data is displayed, not *where* it comes from or *how* it's fetched.

Let's walk through an example. First, we'll create our `PortfolioComposer` class. Inside your `app/View/Composers` directory, create `PortfolioComposer.php`:

namespace App\View\Composers;

use Roots\Acorn\View\Composer; use WP_Query;

class PortfolioComposer extends Composer { /** * List of views served by this composer. * * @var array */ protected static $views = [ 'page-portfolio-archive', ];

/** * Data to be passed to view before rendering. * * @param array $data * @return array */ public function with($data) { $query = new WP_Query([ 'post_type' => 'portfolio', 'posts_per_page' => -1, // Get all portfolio items 'orderby' => 'menu_order', 'order' => 'ASC', ]);

return array_merge($data, [ 'portfolios' => $query->posts, ]); } }

In this composer, we're defining which views it applies to (`page-portfolio-archive` in this case – note that Sage automatically translates `page-portfolio-archive.blade.php` to `page-portfolio-archive`). The `with()` method performs the `WP_Query` and then returns an array merging the new `'portfolios'` variable with any existing data that might be passed to the view.

Next, we need to tell Sage that this composer exists and should be applied. The most robust place for this is within your `ThemeServiceProvider`, which resides in `app/Providers/ThemeServiceProvider.php`. Open that file and add our `PortfolioComposer` to the `$composers` property:

namespace App\Providers;

use App\View\Composers\PortfolioComposer; use Roots\Acorn\Sage\SageServiceProvider;

class ThemeServiceProvider extends SageServiceProvider { // ... existing register and boot methods ...

/** * The application's view composers. * * @var array */ protected $composers = [ // Register all View composers here that match specific views. // For example: // \App\View\Composers\SiteComposer::class, PortfolioComposer::class, // Add our PortfolioComposer here ]; }

By adding `PortfolioComposer::class` to the `$composers` array, Acorn automatically registers it for the views specified in the composer's `$views` property. This is clean, scalable, and keeps your registration logic organized.

Finally, in your `resources/views/page-portfolio-archive.blade.php` template, you can now access the `$portfolios` variable directly, without a single `WP_Query` line:

@extends('layouts.app')

@section('content') <div class="container"> <h1>Our Portfolio</h1>

@if (!empty($portfolios)) <div class="grid grid-cols-1 md:grid-cols-3 gap-8"> @foreach ($portfolios as $portfolio) <article class="portfolio-item"> <h2> <a href="{{ get_permalink($portfolio->ID) }}">{{ get_the_title($portfolio->ID) }}</a> </h2> {{-- Add more portfolio details here --}} </article> @endforeach </div> @else <p>No portfolio items found.</p> @endif </div> @endsection

Look at that template! It's pure presentation. There's no WordPress API call, no database interaction – just a straightforward loop through data that's already been prepared for it. This is the power of Sage’s clean architecture. It moves data logic out of your templates and into dedicated, testable classes. Contrast this with the inline `WP_Query` hell, or the opaque, database-bloating shortcodes and custom fields generated by page builders like Divi or Elementor. Sage empowers you to build a programmatic, maintainable codebase, giving you full control and a codebase you’ll actually enjoy working with. Embrace View Composers and elevate your WordPress development.