← All posts

Routing Data Cleanly with Sage View Composers: A Practical Guide

Routing Data Cleanly with Sage View Composers: A Practical Guide

If you’ve spent any serious time building WordPress themes, you know the struggle: templates riddled with `WP_Query` calls, `global $post` spaghetti, and `get_field()` sprinkled everywhere. It quickly turns into an unmaintainable mess, making it hard to debug, test, or even understand what data a given template is actually supposed to display. This is precisely why I gravitated towards Sage, and specifically why its View Composer pattern, powered by Acorn, has become an indispensable tool in my workflow. Forget the database bloat and unmanageable interfaces of Divi or Elementor; Sage gives us a clean, programmatic way to build robust sites.

Sage aims for an MVC-like architecture, encouraging a clear separation of concerns. Your Blade templates should be *purely* presentation. They display data, they don't fetch it. That's where View Composers shine. A View Composer is essentially a class that binds data to a specific Blade view or set of views *before* they are rendered. This means your data fetching and manipulation logic lives in a dedicated, testable class, completely separate from your markup.

Let's walk through a concrete example. Imagine you have a custom post type called 'Project' and you want to display a list of these projects on a dedicated "Our Projects" page. Without a View Composer, you might be tempted to drop a `WP_Query` directly into your `resources/views/pages/projects.blade.php` file. While it works, it immediately violates the principle of separation of concerns. The template becomes responsible for both querying the database and presenting the results.

Here’s how we do it the Sage way, using a View Composer:

First, let's create our composer class. You can place this in `app/View/Composers/ProjectComposer.php`:

```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 = [ 'pages.projects', // This refers to resources/views/pages/projects.blade.php ];

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

/** * Get a list of published projects. * * @return array */ protected function getProjects() { $args = [ 'post_type' => 'project', 'posts_per_page' => -1, // Get all projects 'post_status' => 'publish', 'orderby' => 'title', 'order' => 'ASC', ];

$query = new WP_Query($args);

return $query->posts; // Returns an array of WP_Post objects } } ```

In this `ProjectComposer` class, we define which views it serves (`pages.projects`). The `with()` method is where we define the data we want to pass to our view. Here, it calls a protected `getProjects()` method, which encapsulates all the `WP_Query` logic. This makes the data retrieval logic reusable and isolated.

Next, we need to tell Sage (or rather, Acorn) to use this composer for our `pages.projects` view. The most common way is to register it in your `config/view.php` file, within the `composers` array:

```php // config/view.php <?php

return [ // ... other config ... 'composers' => [ 'pages.projects' => \App\View\Composers\ProjectComposer::class, ], // ... other config ... ]; ```

Now, your `resources/views/pages/projects.blade.php` becomes incredibly clean and focused purely on presentation:

```blade @extends('layouts.app')

@section('content') <div class="container mx-auto my-8"> <h1 class="text-3xl font-bold mb-6">Our Projects</h1>

@if (!empty($projects)) <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> @foreach ($projects as $project) <div class="bg-white rounded-lg shadow-md p-6"> <h2 class="text-xl font-semibold mb-2">{{ $project->post_title }}</h2> <p class="text-gray-600 mb-4">{{ \wp_trim_words($project->post_content, 30) }}</p> <a href="{{ \get_permalink($project->ID) }}" class="text-blue-500 hover:underline">View Project</a> </div> @endforeach </div> @else <p class="text-gray-700">No projects found.</p> @endif </div> @endsection ```

Notice how `$projects` is directly available in the template? No `global $post`, no `WP_Query`! The template simply iterates over the `$projects` variable, assuming it has been provided. This is the power of View Composers. Your template designers can focus purely on HTML and CSS, while your developers handle the data logic.

This approach not only leads to vastly more organized and readable code but also significantly improves maintainability and testability. You can easily unit test your `ProjectComposer` to ensure it fetches the correct data without needing to render the view or even touch the WordPress database directly (with proper mocking). This kind of organized, programmatic codebase is a world away from the often-unmanageable database bloat and convoluted markup generated by page builders, proving Sage truly is the developer's choice for modern WordPress.