View Composers Keep Controllers Thin

๐Ÿ“– 2 minutes read

The Problem

Your admin panel has a product filter form. Every controller method that renders this view needs to pass the same dropdown data:

// ProductController@index
public function index()
{
    $categories = Category::pluck('name', 'id');
    $brands = Brand::pluck('name', 'id');
    $statuses = ['active', 'draft', 'archived'];
    
    return view('products.index', compact('categories', 'brands', 'statuses'));
}

// ProductController@create
public function create()
{
    $categories = Category::pluck('name', 'id');
    $brands = Brand::pluck('name', 'id');
    $statuses = ['active', 'draft', 'archived'];
    
    return view('products.create', compact('categories', 'brands', 'statuses'));
}

// ProductController@edit
public function edit($id)
{
    $product = Product::findOrFail($id);
    $categories = Category::pluck('name', 'id');
    $brands = Brand::pluck('name', 'id');
    $statuses = ['active', 'draft', 'archived'];
    
    return view('products.edit', compact('product', 'categories', 'brands', 'statuses'));
}

Duplicated logic across multiple methods. When you add a new filter dropdown, you update 5+ methods.

The Fix

Use a view composer to bind data automatically:

// app/Providers/ViewServiceProvider.php
use Illuminate\Support\Facades\View;
use App\Models\Category;
use App\Models\Brand;

public function boot()
{
    View::composer('products.*', function ($view) {
        $view->with([
            'categories' => Category::pluck('name', 'id'),
            'brands' => Brand::pluck('name', 'id'),
            'statuses' => ['active', 'draft', 'archived'],
        ]);
    });
}

Now your controllers stay thin:

public function index()
{
    return view('products.index');
}

public function create()
{
    return view('products.create');
}

public function edit($id)
{
    $product = Product::findOrFail($id);
    return view('products.edit', compact('product'));
}

How It Works

The composer runs before every view that matches products.*. The data is automatically injected, so your Blade templates can access $categories, $brands, and $statuses without the controller passing them.

When to Use It

  • Shared dropdown data across multiple views (categories, statuses, countries)
  • Default filter values that every page needs
  • Navigation menus or sidebars that appear on many pages
  • Current user permissions for conditional UI elements

Trade-Offs

The data is fetched on every view render, even if the template doesn’t use it. If your composer queries are expensive, cache them or use class-based composers with dependency injection for better control.

But for simple dropdown data, view composers eliminate duplication and keep your controllers focused on actions, not view preparation.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *