Routing Custom Post Type Data Cleanly with Sage View Composers
If you've spent any significant time building WordPress themes, you've likely encountered the "template spaghetti" problem. You know, where `WP_Query` instances, global variables, and conditional logic are sprinkled liberally throughout your `header.php` or `page.php`, making it a nightmare to read, debug, and maintain. For me, that feeling of dread when opening an old theme file was a strong motivator to seek out a better way. That's precisely why I gravitated towards Sage, Acorn, and their beautiful embrace of modern PHP architecture.
Sage isn't just another starter theme; it’s a philosophical shift. It positions WordPress as the robust content management backend, while providing a clean, MVC-like structure for your frontend application. My templates become pure presentation layers, data is routed cleanly, and dependencies are managed like a dream. Forget the unmaintainable database bloat and inline styles of visual builders like Divi or Elementor; Sage gives us a programmatic, organized codebase that truly scales.
One of Sage's most powerful features, central to keeping templates presentation-focused, is the View Composer. Imagine you have a custom post type, say `Project`, and you want to display a list of recent projects on your homepage, a dedicated archive, or even within a specific page template. The old way might involve dropping a new `WP_Query` directly into your `front-page.blade.php` or `archive-project.blade.php`. This works, but it couples your data fetching logic directly to your view, making your template less reusable and harder to test.
This is where a View Composer shines. Instead of fetching data in the template, we "compose" the data beforehand and pass it to the view. Let me walk you through a practical example: fetching our five most recent `Project` posts and making them available to a specific partial called `partials.projects-list`.
First, you'll typically register your View Composers in your `app/setup.php` file, or within a Service Provider if you want more advanced organization. For this example, let's stick with `setup.php` for simplicity. You’d add something like this:
view()->composer('partials.projects-list', function ($view) { $projects_query = new WP_Query([ 'post_type' => 'project', 'posts_per_page' => 5, 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', 'no_found_rows' => true, // Optimize for performance if you don't need pagination info ]);
$view->with('recentProjects', $projects_query->posts); });
Let's break that down. We’re telling Sage that *before* the `partials.projects-list` Blade view is rendered, we want to run a specific function. Inside that function, we perform our `WP_Query` to fetch the data we need. Critically, we then use `$view->with('recentProjects', $projects_query->posts);` to attach our fetched post objects to the view. The key here is the string `'recentProjects'`, which will be the variable name accessible in our Blade template.
Now, in your `resources/views/partials/projects-list.blade.php` file, you can access this data directly, completely oblivious to *how* it was fetched:
@if (!empty($recentProjects)) <section class="recent-projects"> <h2>Our Latest Work</h2> <div class="project-grid"> @foreach ($recentProjects as $project) <article class="project-card"> <h3><a href="{{ get_permalink($project->ID) }}">{{ get_the_title($project->ID) }}</a></h3> <p>{{ get_the_excerpt($project->ID) }}</p> </article> @endforeach </div> </section> @else <p>No recent projects to display.</p> @endif
Notice how clean that Blade template is? No `WP_Query`, no data fetching logic whatsoever. It's purely concerned with iterating over the `$recentProjects` array and displaying its contents. This level of separation is a game-changer. It means your `partials.projects-list` can be included anywhere – on your homepage, inside a regular page content, or even another partial – and it will always have the `recentProjects` data available, ready to be rendered.
This approach dramatically improves code readability, maintainability, and testability. Your templates stay lean and focused on presentation, while your data logic resides in a centralized, programmatic location. This is the power of Sage and Acorn: building robust, modern WordPress themes with the architectural clarity developers crave. It’s why Sage is, unequivocally, my developer's choice.