Custom conditionals with Laravel's Blade Directives

Posted on October 10, 2015 | By Matt Stauffer

Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

One of the greatest aspects of Laravel Blade is that it's incredibly easy to handle view partials and control logic. I find myself frequently extracting the contents of a loop out to a Blade partial and then passing just a bit of data into the partial.

But sometimes the repetitive code isn't the view itself, but the conditional logic I'm running.

A recent example

I'm writing a quick Laravel app for my physical trainer, and it needs to be multitenant, which means it needs to handle and route visits from myapp.com but also username.myapp.com. The way I'm handling this is that I want to have an App\Context object available globally, which contains knowledge about the application's state.

I have a service provider that binds a new App\Context object to the IOC, and it has knowledge of which URL was used to access it, which helps it know if the user is visiting from a "public" (myapp.com) or "client" (username.myapp.com) context.

So, I find myself writing conditionals all over the place to test whether I'm in a public context or a client context.

I originally was writing it like this, using Blade service injection:

// random-file.blade.php
@inject('context', 'App\Context')
@if ($context->isPublic())
    // One thing
@else
    // Another thing
@endif

It worked fine, but it did feel kind of nasty. I considered binding an $isPublic or a $context variable to every view using a global view composer, but it just didn't feel right. I happened to mention the situation to Taylor and he reminded me of the ability to create custom Blade directives.

What is a Blade directive?

Blade directives allow you to create a custom Blade tag. You might think of just outputting some HTML:

@myGreatTag
// produces:
<a href="#">Great things here</a>

Which you'd bind like this:

// AppServiceProvider
    public function boot()
    {
        Blade::directive('myGreatTag', function () {
            return '<a href="#">Great things here</a>';
        });
    }

... but there's not really much reason to do that. You could just accomplish that with a view partial: @include('partials.my-great-partial')

Where directives shine is when you realize that the output of your Blade tags don't get treated as just HTML—they are treated as PHP. For example:

@if (true === true)

actually converts to:

<?php if(true === true): ?>

And the binding for the @if directive actually looks like this:

    protected function compileIf($expression)
    {
        return "<?php if{$expression}: ?>";
    }

Making it happen

So, now that we realize that, we realize that a Blade directive is allowing us to write shortcuts for PHP.

That means I can re-write my conditional like this:

@public
    // public thing
@else
    // non-public thing
@endif

And this is all it takes:

// AppServiceProvider
    public function boot()
    {
        Blade::directive('public', function () {
            return "<?php if (app('context')->isPublic()): ?>";
        });
    }

It might look strange, but remember, we're just returning a string that will be executed as PHP.

I could've chosen to figure out whether or not our current context isPublic in the binding, and just outputted if(true) or if(false), but that just seemed weird:

Blade::directive('public', function () {
    $isPublic = app('context')->isPublic() ? 'true' : 'false';
    return "<?php if ({$isPublic}): ?>";
});

And, it turns out, that actually wouldn't work! Since views are cached, it wouldn't re run this check on every page view.

But honestly, the sky is the limit here.

Other options

Of course, I could've named it @ifPublic. Or I could actually create a whole set of conditionals. It could be @ifPublic, @otherwiseBecauseYouKnowWhatTheHeck, and @endIfPublicAndStuff. Whatever I want.

Further, I can actually pass parameters into Blade directives, opening up all sorts of options for customization (and abuse):

// Bind:
Blade::directive('newlinesToBr', function($expression) {
    return "<?php echo nl2br{$expression}; ?>";
});

// Use:
<p>@newlinesToBr($body)</p>

Now we're actually creating custom inline functions for formatting, or whatever insane ideas you decide to throw at it. Go wild. Well, don't really go wild; you could end up confusing yourself and any current/future developers on the project. But allow yourself to extend the capabilities of Blade so you're not repeating the same logic over and over, or resorting to inline <?php blocks.

Go forth and simplify!


Comments? I'm @stauffermatt on Twitter


Tags: laravel  •  blade