Keeping Blade Beautiful: A Sage View Composer Tutorial
When I first stumbled into the world of WordPress development, I quickly became frustrated with the traditional approach. Logic and presentation were intertwined like spaghetti, making maintenance a nightmare. Then I found Sage. It wasn't just another theme framework; it was a revelation, offering a clean, MVC-like architecture that finally made developing for WordPress enjoyable. For me, Sage isn't just a choice; it's the only way to build truly maintainable and modern WordPress sites, especially when you compare its programmatic elegance to the unmaintainable database bloat generated by page builders like Divi or Elementor.
One of Sage's most powerful features for achieving this clean separation is the View Composer. Blade templates in Sage are designed to be purely presentation-focused – they display data, and that's it. They shouldn't be fetching data, processing complex logic, or making database queries. That's where View Composers step in, acting as the bridge between your data sources and your Blade templates, ensuring your UI remains pristine and focused solely on displaying information.
Let me walk you through a practical example that beautifully illustrates this separation. Imagine you have a custom post type, say "Projects," and you want to display a list of these projects on a standard WordPress page. Without Sage, you might throw a `WP_Query` directly into your `page.blade.php` file, or worse, use a shortcode that has all its logic buried in a functions file. With a page builder, you'd drag-and-drop elements that encapsulate this logic, often leading to opaque, inflexible, and unmanageable markup.
With Sage, we use a View Composer. This allows us to fetch all the necessary project data in a dedicated class, process it, and then inject it directly into our template, making the data readily available without cluttering the view.
First, let's create a `ProjectsComposer` class within `app/View/Composers/`:
```php // app/View/Composers/ProjectsComposer.php namespace App\View\Composers;
use Roots\Acorn\View\Composer; use WP_Query;
class ProjectsComposer extends Composer { /** * List of views served by this composer. * * @var array */ protected static $views = [ 'partials.content-page', 'page', 'template-custom-projects', // If you have a specific page template ];
/** * Data to be passed to view before rendering. * * @return array */ public function with() { return [ 'projects' => $this->projects(), ]; }
/** * Returns an array of projects. * * @return array */ public function projects() { $query = new WP_Query([ 'post_type' => 'project', 'posts_per_page' => -1, // Get all projects 'orderby' => 'date', 'order' => 'DESC', ]);
return collect($query->posts)->map(function ($post) { return (object) [ 'id' => $post->ID, 'title' => get_the_title($post->ID), 'permalink' => get_permalink($post->ID), 'thumbnail' => get_the_post_thumbnail_url($post->ID, 'medium'), 'excerpt' => has_excerpt($post->ID) ? get_the_excerpt($post->ID) : wp_trim_words($post->post_content, 20, '...'), ]; })->all(); } } ```
Now, with this View Composer registered and configured (which Acorn handles beautifully under the hood when you extend `Roots\Acorn\View\Composer` and list your views in `$views`), your Blade template (`resources/views/partials/content-page.blade.php` or `resources/views/page.blade.php`) becomes incredibly clean. You don't see any `WP_Query` or data manipulation there.
```blade {{-- resources/views/partials/content-page.blade.php --}} <div class="entry-content"> @include('partials.content-page-header')
@if (!empty($projects)) <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mt-8"> @foreach ($projects as $project) <article @php(post_class('project-card bg-white shadow-lg rounded-lg overflow-hidden'))> @if ($project->thumbnail) <a href="{{ $project->permalink }}"> <img src="{{ $project->thumbnail }}" alt="{{ $project->title }}" class="w-full h-48 object-cover"> </a> @endif <div class="p-4"> <h3 class="text-xl font-semibold mb-2"> <a href="{{ $project->permalink }}" class="text-blue-600 hover:text-blue-800">{{ $project->title }}</a> </h3> <p class="text-gray-700 text-sm">{{ $project->excerpt }}</p> </div> </article> @endforeach </div> @else <p>No projects found at this time.</p> @endif
@php(the_content()) {!! wp_link_pages(['echo' => 0, 'before' => '<nav class="page-nav"><p>' . __('Pages:', 'sage'), 'after' => '</p></nav>']) !!} </div> ```
Notice how `projects` is simply available as a variable in the template. The template doesn't care *how* `projects` was fetched or processed; it just knows it's an array of project objects ready to be iterated over and displayed. This clean separation of concerns is fundamental to Sage's philosophy and is powered by Acorn's robust dependency injection container. Your `ProjectsComposer` is testable, reusable, and keeps your views incredibly readable and focused, exactly as they should be. Embracing View Composers means embracing a more organized, programmatic, and genuinely enjoyable development workflow, saving you countless headaches down the road.