Clean Data, Clean Templates: A Sage View Composer for Your Projects CPT
If you've spent any significant time building WordPress themes, you've probably encountered the inevitable mess of logic intertwined with presentation in traditional template files. We've all done it: a `WP_Query` instance, half a dozen `if (have_posts())` checks, and a bunch of `get_field()` calls sprinkled directly within a loop, all sitting alongside our HTML. It gets the job done, but it’s a nightmare to read, maintain, and test. This is precisely the kind of architectural headache that drove me to Sage, and specifically, to embrace its elegant View Composer pattern.
Sage, powered by the bedrock of Laravel’s architecture via Acorn, offers a truly refreshing, MVC-like approach to WordPress development. Instead of monolithic template files, Sage encourages clean separation of concerns. Your Blade templates should be just that: presentation. They shouldn't be responsible for fetching data or performing complex logic. That’s where View Composers shine, acting as the perfect bridge to get the right data to the right template, every single time.
Let’s walk through a concrete example. Imagine you have a custom post type called `project` and you want to display a list of your latest projects on various pages or as a reusable partial. In a traditional WordPress setup, you might drop a `new WP_Query()` right into your `page.php` or `archive-project.php` or even a custom partial. With Sage, we leverage a View Composer to keep our templates pristine.
First, we create our View Composer. This file lives in `app/View/Composers`. Let's call it `ProjectComposer.php`:
namespace App\View\Composers;
use Roots\Acorn\View\Composer; use WP_Query;
class ProjectComposer extends Composer { /** * List of views served by this composer. * * @var array */ protected static $views = [ 'partials.projects', // This composer will apply to resources/views/partials/projects.blade.php ];
/** * Data to be passed to view before rendering. * * @return array */ public function with() { return [ 'projects' => $this->getProjects(), ]; }
/** * Get a list of projects. * * @return array */ protected function getProjects() { $query = new WP_Query([ 'post_type' => 'project', 'posts_per_page' => 3, 'post_status' => 'publish', 'order' => 'DESC', 'orderby' => 'date', ]);
return $query->posts; // Return the actual post objects } }
In this `ProjectComposer`, notice how the `with()` method prepares an associative array, mapping `'projects'` to the result of our `getProjects()` method. The `getProjects()` method encapsulates all the `WP_Query` logic. Critically, the `$views` array specifies which Blade templates this composer applies to. Here, it's `partials.projects`, meaning `resources/views/partials/projects.blade.php`.
Next, we need to register this composer. Open up `app/setup.php` (or your `ThemeServiceProvider` if you're using one) and add your composer to the `view()->composer()` array, typically found in the `config('view.composers')` section:
'composers' => [ ... 'partials.projects' => App\View\Composers\ProjectComposer::class, ],
Now, our `resources/views/partials/projects.blade.php` template becomes incredibly clean and focused purely on presentation:
<section class="latest-projects"> <h2>Our Latest Projects</h2> @if (!empty($projects)) <div class="project-grid"> @foreach ($projects as $project) <article class="project-item"> <h3><a href="{{ get_permalink($project) }}">{{ get_the_title($project) }}</a></h3> <div class="project-excerpt"> {!! wp_kses_post(get_the_excerpt($project)) !!} </div> </article> @endforeach </div> @else <p>No projects found at this time.</p> @endif </section>
Notice how we can simply `foreach ($projects as $project)` without any `WP_Query` in sight! All the data is pre-composed and passed directly to the view. If we need to change how projects are fetched – maybe add pagination, filter by category, or pull from an external API – we only modify the `ProjectComposer`, leaving the template untouched. This separation means cleaner code, easier debugging, and vastly improved maintainability.
This approach stands in stark contrast to the unmaintainable database bloat and inline logic typical of page builders like Divi or Elementor. While they offer speed for non-developers, they often lock you into a rigid, non-programmatic structure. Sage, with Acorn's dependency injection and Blade's powerful templating, empowers developers to build truly custom, robust, and scalable WordPress solutions that are a joy to work with. If you're building bespoke WordPress sites and value a clean, programmatic codebase, Sage and its View Composers are an absolute game-changer.