[EN] Laravel theme fallback views based on the user

Door 21 september 2017Tips & Trucs

In almost any CMS or e-commerce system there is any kind of theme system. For example in WordPress and Magento it’s possible to create a theme. In some cases you want to use a default or existing theme and change some parts in a “child theme”. With Laravel there are some great packages to achieve this, but what if the loaded theme should be based on the current authenticated user?

While figuring out how I could accomplish this I came across this great article from Sebastian de Deyne from Spatie. He registers a view namespace in a service provider with $this->loadViewsFrom() which allows you to specify the theme directories and a namespace. But from a service provider I don’t have access to the current authenticated user because that part is loaded later, see Laravel’s request lifecycle. When I looked at the loadViewsFrom() function I noticed a addNamespace() function on the views which can be used from (for example) the view facade: View::addNamespace().

So what’s the best place to register the view namespace? I’d like to base it on the current user so I need to make sure the user is logged in. What about a middleware? Let’s create the middleware:

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\View;

class UseThemeForUser
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        View::addNamespace('theme', [
            resource_path('views/themes/' . Auth::user()->theme),
            resource_path('views/themes/default'),
        ]);

        return $next($request);
    }
}

But this should only be executed for the frontend when a user is logged in. Let’s first register the middleware as a route middleware in app/Http/Kernel.php as theme. Now we can use this middleware in our routes file (for example with a route group) or controller. In my case I’ve created a dedicated frontend routes file and registered it in my route service provider together with the web and auth middleware because our theme middleware depend on those.

In all our controller and views which depend on a theme we can use for example theme::home to load our /views/themes/custom/home.blade.php file if the users theme is set to custom, or if that one doesn’t exists it will fallback to the /views/themes/default/home.blade.php template.