Eoghan O'Brien

husband · father · developer at Brightspot · lead engineer on Ignite

Building a theme system in Laravel

The key to building a theme system in Laravel is making your active theme the first location that Laravel should look in order to load views. All of the rest of the functionality around a theme engine is opinionated, implementation detail.

In order to add a location to the view finder, we need to get an instance of Illuminate\View\Factory. In Laravel, there are a couple of ways to get access to a dependency. My favorite way is to auto-resolve via the constructor by type-hinting the class you need, for example:

<?php
namespace App;
use Illuminate\View\Factory;
class ThemeManager
{
    protected $view;

    public function __construct(Factory $view)
    {
        $this->view = $view;
    }
}

Once you have an instance of Illuminate\View\Factory, you have two options to add a view location. The first is to use addLocation() and while it will work, it appends the location rather than prepends.

public function setupTheme($themePath)
{
    $this->view->addLocation($themePath);
}

We want to prepend. Using the Illuminate\View\Factory instance we already have, we can get an instance of the Illuminate\View\ViewFinderInterface by calling getFinder(). By default, Laravel is configured to use a local file finder, specifically Illuminate\View\FileViewFinder, which implements a method prependLocation($location).

Ideally, the Illuminate\View\ViewFinderInterface would enforce the prependLocation() method we rely upon but it doesn't so we're going to have to insist that our theme engine will only support Illuminate\View\FileViewFinder instances or a subclass.

public function setupTheme($themePath)
{
    $this->view->getFinder()->prependLocation($themePath);
}

That's all there is, that's the secret, the rest is packaging up the service provider, some configuration and modeling the theme. The Stylist package is a good example of using this technique, if you don't want to roll your own. As always, you can contact me on Twitter @eoghanobrien with any questions.