Want to provide your customers with a quick and simple way of styling your Laravel application to match their corporate brand? This post is for you. If you've ever customised Bootstrap with Sass (or LESS) before, you already know how simple it is to drastically change the visual appearance of your Web apps and sites by merely changing a few variables. Simply set $brand-primary, run Laravel Mix and you're done - all of your buttons, panels and other Bootstrap components will now display in your colour scheme. But what if you wanted to extend this power to your customers, simply by submitting a form on your app, so that their colour is saved in the database and the entire appearance of your app changes to reflect their selection?

A naive implementation, and one that I've used in the past, would be to create a Blade template named something like styles.blade.php and add a metric ton of CSS overrides to change various properties of buttons, panels and other components based on a value in your database:

.btn-primary {
    background-color: {{ $customer->brand_primary }};
}

You'll quickly discover that this approach is not a lot of fun. When you mouse over the button, for example, you'll noticed that you also want to change the background color on the hover state to a darker shade of the $customer->brand_primary variable. But because you're working in PHP and CSS rather than Sass, you won't have the trusty darken() Sass function to hand. Sure, you can implement a crude variant of it in PHP and use that instead - but before long you'll find yourself looking for other features like colour conversion, mixins and more. All it offers is a shitload of misery.

Wouldn't it be great if you could just use Sass in your PHP code? Well, actually, you can - thanks to a fantastic library called scssphp.

Sass to the rescue

Installing the library is as simple as requiring a Composer package in your Laravel project:

$ composer require leafo/scssphp

Using it is pretty straightforward too:

$scss = new Leafo\ScssPhp\Compiler();
$css = $scss->compile('
    $brand-primary: '.$customer->brand_primary.';

    .btn-primary {
        background-color: $brand-primary;
    }
');

echo $css;

Assuming the value of $customer->brand_primary is #E91E63, the above snippet would output the following CSS:

.btn-primary {
    background-color: #E91E63;
}

Nice. Even nicer, however, is that you can use the @import directive to load in Sass files from the filesystem. Want to bring in your existing project's Sass files, or even Sass files from a library like Bootstrap installed with npm? No problem - just tell scssphp where to look:

$scss = new Leafo\ScssPhp\Compiler();
$scss->addImportPath(realpath(app()->path() . '/../resources/assets/sass'));
$css = $scss->compile('
    @import "variables";
    $brand-primary: '.$customer->brand_primary.'
    @import "../../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap";
');

This will load the _variables.scss file from your Laravel app's resources/assets/sass directory, override the $brand-primary variable with the value from your customer model, and finally import Bootstrap itself. Essentially it's like running Laravel Mix, but using colour values stored in the database. One thing to note is that the tilde syntax for referencing the node_modules path will not work - you'll need to use the horribly ugly ../../../../node_modules style instead I'm afraid!

Storing the output

Now that you're generating this CSS code, the next concern is what to do with it. Storing it in a file that can be publicly accessed is a smart move, either on your server's filesystem, or better yet in a public readable Amazon S3 object. Whatever you decide to do, it's easy to achieve using the Storage facade. For the sake of example, I'm going to store it in the public disk, which is simply the public folder in storage/app symlinked into the public directory using the php artisan storage:link command.

I like to put this code directly in the model itself. Flame me to your hearts' content, it works for me - if you prefer to use repositories or service classes or any other approach, do what you need to do! The following is an example of my usage in a Laravel Eloquent model:

<?php

namespace App;

use Leafo\ScssPhp\Compiler;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Storage;

class Customer extends Model
{
    // ...

    public function getStylesheetUrlAttribute()
    {
        return Storage::disk('public')->url("css/$this->id/app.css");
    }

    // ...

    public function compileTheme()
    {
        $scss = new Compiler();
        $scss->addImportPath(realpath(app()->path() . '/../resources/assets/sass'));
        $scss->setFormatter('Leafo\ScssPhp\Formatter\Crunched');

        $output = $scss->compile('
            @import "variables";
            $brand-primary: '.$this->brand_primary.'
            @import "../../../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap";
        ');

        Storage::disk('public')->put("css/$this->id/app.css", $output);
    }
}

The getStylesheetUrlAttribute method allows me to easily get a URL to load the stylesheet using $customer->stylesheet_url. The compileTheme method will take the customer's primary colour, feed it into Bootstrap along with my application's other variables and store the resulting CSS file. I'm also telling scssphp that I want the output to be crunched so that comments are removed and the output is minified.

Deciding when to compile

So now you have a nice method for compiling a theme for a customer, but when should you call this method? I like to recompile any time the customer model is saved - whether a new customer is created or an existing one is updated. Something like the following in your customer model should do the trick:

protected static function boot()
{
    parent::boot();

    static::saved(function($customer) {
        $customer->compileTheme();
    });
}

This is nice, as any time the brand_primary attribute on the model is changed, the theme file will be regenerated. But what if you make changes to the resources/assets/_variables.scss or any other file you are loading in? These changes will not be reflected in your customer stylesheets until the next time the customer model is saved. That won't do - wouldn't it be nice if there was a simple way to recompile all customer styles in one swift go? Sounds like a good use case for an Artisan command to me. Run the following:

$ php artisan make:command CompileThemes

Change the $signature attribute to compile:themes, and modify the handle method to the following:

use App\Customer;

// ...

public function handle()
{
    $this->info("Compiling customer themes.");
    $customers = Customer::all();

    $bar = $this->output->createProgressBar(count($customers));

    foreach($customers as $customer) {
        $customer->compileTheme();

        $bar->advance();
    }

    $bar->finish();
    $bar->clear();
    $this->info("Customer themes compiled successfully.");
}

If you're using Laravel 5.5, you don't even need to register the command anywhere, it will automatically be picked up for you. So now to recompile all customer themes, all you have to do is run:

$ php artisan compile:themes

Beautiful, eh?

Watch it!

So all of this is great, but the next pain in the ass you'll encounter is that when you're working on the styling of your application you'll be running your shiny new Artisan command over and over again. If only there was a way to automatically run it every time your Sass code is recompiled when you run the Laravel Mix watch command npm run watch. I think you know where this is going...

To do this, we're going to need to merge in some custom Webpack configuration with Laravel Mix. We'll also need to bring in a Webpack plugin for calling shell commands. If that sounds complicated, it really isn't! First, grab webpack-shell-plugin from npm and add it as a dev dependency:

# If you're using npm
$ npm install --save-dev webpack-shell-plugin

# If you're using yarn
$ yarn add -D webpack-shell-plugin

Next, open up the webpack.mix.js file in the root of your project and change it so it looks like the following:

const WebpackShellPlugin = require('webpack-shell-plugin');
let mix = require('laravel-mix');

mix.webpackConfig({
    plugins: [
        new WebpackShellPlugin({
            onBuildExit:['php artisan compile:themes']
        })
    ],
})
    .js('resources/assets/js/app.js', 'public/js')
    .sass('resources/assets/sass/app.scss', 'public/css');

This essentially tells Mix to run the command php artisan compile:themes every time it finished compiling a build.

Now run npm run watch and you'll see the notifications and progress bar about customer themes being compiled, and you'll see it again any time you modify a Sass file in your project.

Are we there yet?

One more thing... While we now have a great workflow for while we're working on our project's Sass in development, we still have a problem when it comes to deploying those changes to production. When we compile our customer themes locally, it's only doing so for whatever customers exist in our local development database. How do we ensure that the customers in our production database have their stylesheets updated every time we deploy a new stylesheet to the server? The simplest way is to run php artisan compile:themes as part of your deployment script, much like you would run migrations or other commands.

If you're using bootstrap-sass or any other npm module as part of your Sass code, you'll need to ensure that this gets installed on your server when your application is deployed. By default bootstrap-sass is listed in the devDependencies block in the package.json file in the root of your Laravel project. Move this to the dependencies block (create it if it doesn't exist). Now in your deployment script, make sure you run the command npm install --production prior to running the php artisan compile:theme command, so the npm dependencies get installed first.

Using Laravel Forge? The following is a slightly modified version of the default deployment script:

cd /home/forge/yourapp.com
git pull origin master
composer install --no-interaction --prefer-dist --optimize-autoloader
npm install --production
echo "" | sudo -S service php7.1-fpm reload

if [ -f artisan ]
then
    php artisan migrate --force
    php artisan compile:themes
fi

Depending on the size of your stylesheets and how many customers you have, the scssphp compilation process could end up being resource intensive - so you should also strongly consider compiling in a queued job rather than synchronously.

Summary

So there you have it. A very straightforward (well, fairly straightforward) approach to providing your customers with a way to customise the appearance of your Laravel app simply by selecting a colour from a colour picker.