Laravel — P62: View Composers

Wire data into Blade with view composers

In the last article we touched on sharing data with multiple views, but we never looked at why we would want to do that. Before we dig through view composers, we really should understand what question they’re trying to answer.

What is a View Composer?

Is a View Composer a Laravel term? No. A view composer is a design pattern used in web development that helps to separate the concerns of generating view data and rendering views in a web application.

In a web application, views often require data from multiple sources, such as a database or API, to be rendered properly. A view composer allows you to encapsulate the logic for retrieving this data and prepare it for the view, so that it can be reused across different views without duplicating code.

Overall, a view composer helps to improve the maintainability and scalability of a web application, by providing a way to encapsulate and reuse view-specific logic, and by reducing the number of database or API requests required to render views.

The Way We Do It Now

Let’s focus on eliminating code-redundancy with the use of View Composers. In our very simple example, we’ll display how to implement a view composer for an application that extract data out of, you should be able to guess it by now, our cars table.

If you’ve been following along, you should already have a cars migration and model.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('cars', function (Blueprint $table) {
            $table->id();
            $table->string('make');
            $table->string('model');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('cars');
    }
};
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Car extends Model
{
    use HasFactory;

    protected $fillable = [
        'make', 'model',
    ];
}
We’ll want to do something with the data from this database. I’ve created a seeder and a factory already, so we can just use those.
<?php

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Car>
 */
class CarFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition()
    {
        return [
            'make' => $this->faker->word(),
            'model' => $this->faker->word(),
        ];
    }
}
<?php

namespace Database\Seeders;

use App\Models\Car;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;

class CarSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Car::factory()->count(50)->create();
    }
}
Time to seed the database.
# php artisan db:seed CarSeeder

   INFO  Seeding database.  

#

You can verify that the data does exist inside the database.

# php artisan tinker
Psy Shell v0.11.9 (PHP 8.1.13 — cli) by Justin Hileman
> Car::all();
[!] Aliasing 'Car' to 'App\Models\Car' for this Tinker session.
= Illuminate\Database\Eloquent\Collection {#4857
    all: [
      App\Models\Car {#4859
        id: 1,
        make: "et",
        model: "minima",
        created_at: "2023-02-07 18:56:39",
        updated_at: "2023-02-07 18:56:39",
      },
      App\Models\Car {#4860
        id: 2,
        make: "natus",
        model: "nemo",
        created_at: "2023-02-07 18:56:39",
        updated_at: "2023-02-07 18:56:39",
      },
      App\Models\Car {#4861
        id: 3,
        make: "nulla",
        model: "nihil",
        created_at: "2023-02-07 18:56:39",
        updated_at: "2023-02-07 18:56:39",
      },

Next, let’s create a controller with a couple of methods: index and show.

# php artisan make:controller FastCarController

   INFO  Controller [app/Http/Controllers/FastCarController.php] created successfully.  

# 

Our index method will grab all the cars from the database and display them in our views/fastcars/index.blade.php file. The show method will also need all of the cars, but this time so that we can switch to a different car. It will display one primary car up top.

index

For the index method, we’ll grab all of the cars from the database and pass it to fastcars.index view.

<?php

namespace App\Http\Controllers;

use App\Models\Car;
use Illuminate\Http\Request;

class FastCarController extends Controller
{
    public function index()
    {
        $cars = Car::all();

        return view('fastcars.index')->with('cars', $cars);
    }
}
The index view is pretty simple. It just loops through the cars and outputs them.
<h1>Cars</h1>

<ul>
    @foreach($cars as $car)
        <li>{{ $car->make }} {{ $car->model }}</li>
    @endforeach
</ul>

We simply now need a route to view it.

use App\Http\Controllers\FastCarController;

Route::get('/fast-cars', [FastCarController::class, 'index']);

Check the output by visiting the route.

show

Next, let’s create our show method.

<?php

namespace App\Http\Controllers;

use App\Models\Car;
use Illuminate\Http\Request;

class FastCarController extends Controller
{
    // ...

    public function show(Car $car)
    {
        $cars = Car::all();

        return view ('fastcars.show', [
            'fastcar' => $car,
            'cars' => $cars,
        ]);
    }
}
The show method accepts a car and also grabs all the cars at the same time. Both data-sets are then sent to the fastcars.show view. The view displays the car in question and loops through each of the other cars creating a link to the next car.
<h1>{{ $fastcar->make }} {{ $fastcar->model }}</h1>

<ul>
    @foreach($cars as $car)
        <li>
            <a href="/fast-cars/{{ $car->id }}">{{ $car->make }} {{ $car->model }}</a>
        </li>
    @endforeach
</ul>

We simply need a route now to test the code.

use App\Http\Controllers\FastCarController;

Route::get('/fast-cars/{car}', [FastCarController::class, 'show']);

Visiting the page, we can see that the vehicle is display at the top and we also get clickable links that will change the content up top.

The Laravel View Composer Way

You might have noticed that all of the cars being brought into the application are not sorted. Sorting them is pretty straightforward. We simply need to go into the index and show methods and add the orderby method.

<?php

namespace App\Http\Controllers;

use App\Models\Car;
use Illuminate\Http\Request;

class FastCarController extends Controller
{
    public function index()
    {
//        $cars = Car::all();
        $cars = Car::orderby('make')->orderby('model')->get();

        return view('fastcars.index')->with('cars', $cars);
    }

    public function show(Car $car)
    {
//        $cars = Car::all();
        $cars = Car::orderby('make')->orderby('model')->get();

        return view ('fastcars.show', [
            'fastcar' => $car,
            'cars' => $cars,
        ]);
    }
}

What if we needed to do this in more places and not just in one controller? That’s where view composers start to make sense.

We could just use the View::share like we did in the previous article, right?

<?php

namespace App\Providers;

use App\Models\Car;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        View::share('cars', Car::orderby('make')->orderby('model')->get());
    }
}

Technically, yes. What this does is makes the cars available to all the views, which means that a database query will occur any time that a view is called.

It’s not something that we want to do frequently, and you won’t see it frequently either, but it does work. We can now eliminate the need to fetch the data from our cars table from the FastCarController.

<?php

namespace App\Http\Controllers;

use App\Models\Car;
use Illuminate\Http\Request;

class FastCarController extends Controller
{
    public function index()
    {
        return view('fastcars.index');
    }

    public function show(Car $car)
    {
        return view ('fastcars.show')->with('fastcar', $car);
    }
}

With a view composer, we can attach data to specific views. We’ll still work in our AppServiceProvider,but instead of using the share method, we’ll use the composer method.

The composer method accepts either a string (the name of the view) or an array of strings (numerous view names). The second argument will be a callback function that accepts our view as an argument and attaches the data that we want to provide.

<?php

namespace App\Providers;

use App\Models\Car;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        View::composer(['fastcars.index', 'fastcars.show'], function ($view) {
            $view->with('cars', Car::orderby('make')->orderby('model')->get());
        });
    }
}

We can return to our views and refresh the page to verify that it is in fact working.

This is great for simple lines of code. If it’s rather intensive, you wouldn’t want to shove everything into the boot method of your AppServiceProvider.

Although the documentation does not state where you should place your view composers, but it does state the following: “you could create an app/View/Composers directory to house all of your application’s view composers.”

I’ll create a View/Composers directory and then create a CarComposer class inside there. There’s no artisan command for this one. We’ll simply need to create a class called CarComposer.

The composer has a required method compose and that compose method will inject an Illuminate\View\View dependency into it. In the AppServiceProvider we used the View facade.

The compose method will simply add the with method to our $view like we did in the AppServiceProvider.

<?php

namespace App\View\Composers;

use App\Models\Car;
use Illuminate\View\View;

class CarComposer
{
    public function compose(View $view): void
    {
        $view->with('cars', Car::orderby('make')->orderby('model')->get());
    }
}

What do we do now? We need to reference our view composer class in the AppServiceProvider.

<?php

namespace App\Providers;

use App\Models\Car;
use App\View\Composers\CarComposer;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        View::composer(['fastcars.index', 'fastcars.show'], CarComposer::class);
    }
}

If you check your routes again, you’ll notice that nothing has changed, which is exactly what we wanted.

That’s really all there is to view composers. It’s just another way to pass data around and make your code cleaner.

Laravel Series

Continue your Laravel Learning.

Laravel — P61: Sharing Data With All Views

Keep every Blade view in sync—share once, use everywhere

Laravel – P61: Sharing Data With All Views

In part 61 of our Laravel fundamentals series, learn techniques to share global data with every Blade view. Explore View::share, service-provider boot methods, view composers, and caching tips for consistent, performant layouts across your app.

Laravel — P62: View Composers

Wire data into Blade with view composers

Laravel – P62: View Composers

In part 62 of our Laravel fundamentals series, discover view composers—dedicated classes that push data into one or many Blade views automatically. Learn to register composers, keep controllers lean, and boost render performance.

 

Laravel — P63: Optimizing Views

Render faster, shine brighter

Laravel – P63: Optimizing Views

Part 63 of our Laravel fundamentals series dives into squeezing every millisecond out of your Blade views. Learn view caching, compiled templates, smart component design, and asset optimization to deliver snappy, user-friendly pages.

Leave a Reply