Steve Grunwell

Open-source contributor, speaker, and coffee snob

A close up of a pair of stainless steel knife blades

Writing Custom Laravel Blade Directives

Though it’s far from the top of the list of most celebrated features, Laravel’s Blade templating engine makes it really nice to work with data on the front-end of our applications. With built-in helpers for handling loops, conditionals, and sub-views, Blade gives us a nice way to write dynamic templates that don’t feel like a bunch of PHP mixed in with HTML.

Were you aware you can author your own Laravel Blade directives? The syntax is probably a little under-documented, but it can be an incredibly useful tool if you find yourself applying the same patterns over and over. In this post, I want to show you a Blade directive I find myself using in pretty much every application I build: @activeIfInRouteGroup.

What does it do?

In practice, @activeIfInRouteGroup is pretty simple — we often want to apply a specific class to “active” links in our primary navigation; if we’re on the “My Account” page, for example, it’s nice to distinguish that link as active in the site navigation.

Normally, we’d accomplish this by retrieving the current route name (or URL), then comparing it against the link to see if the link is pointing to the page we’re already on. While this works, it gets awfully repetitive. What if instead you could specify “mark this as an active link if we’re on any page with a route matching this pattern?”

This is where @activeIfInRouteGroup comes in! By adding a single line to each link, we’re able to simplify the process!

Whether we’re on reports.index, reports.show, reports.edit, or reports.literallyAnythingElse, the <li> surrounding the “Reports” link will always be given the class of “active“!

Defining our custom Blade directive

Custom Blade directives are registered in the boot() method of the AppServiceProvider, located in app/Providers/AppServiceProvider.php:

The magic here comes from Laravel’s Route::currentRouteNamed() method, which accepts one or more patterns and returns whether or not the current route matches the pattern(s).

Assuming you’re naming your routes within Laravel, this enables you to say “I’m at some route that starts with ‘reports’, so I want to make sure that the ‘Reports’ link is highlighted.”

By the way, that’s the entire Blade directive. Let’s look at it again:

The Blade::directive() method accepts two arguments:

  1. The directive name, which will used to create the Blade directive beginning with “@” (e.g. @activeIfInRouteGroup)
  2. A callback that accepts the value that was passed into the directive and returns a PHP expression.

This is where writing custom Laravel Blade directives gets a little confusing. Remember: we’re not writing the output of the directive, but rather the PHP that should appear in the page.

Laravel’s Blade syntax by itself isn’t valid HTML, so it has to be transpiled down into regular HTML so that a browser can understand it. Blade is what’s commonly referred to as “syntactic sugar”: it makes writing the code nicer, but under the hood it’s really using the core features of the language.

For example, imagine the following Blade file:

When you load the page, Laravel’s Blade engine is going to attempt to create a standard PHP template from this file, which will get stored in storage/framework/views:

It’s the same code — and you could write regular PHP like that in your views and skip over Blade entirely — but Blade is a little nicer to write. In our case, our @activeIfInRouteGroup directive turns into:

Remember: the $expression that’s passed to the directive doesn’t get handled by the directive definition, but is the value that will be passed to the PHP statement we’re defining. Instead, the directive definition tells the Blade engine what PHP should be injected.

Testing Laravel Blade directives

Another stumbling block is writing unit tests for custom Laravel Blade directives: since our Blade::directive() call is registering a callback that’s responsible for returning a PHP statement, testing Blade directives requires getting a little bit meta:

In this test class, we have four methods:

  1. setUp(), which is a standard PHPUnit fixture method. The only thing special we’re doing is ensuring that the Blade compiler is loaded and available at $this->blade.
  2. activeIfInRouteGroup_should_print_active_when_true(), which verifies that “active” is echoed when Route::currentRouteName() returns true.
  3. activeIfInRouteGroup_should_print_active_when_false(), which verifies our negative case (e.g. we’re not in the given route group)
  4. assertDirectiveOutput(), a custom PHPUnit assertion that compiles the Blade directive, then compares the output against our expected output.

In each of the two test methods, we’re using the Route facade’s built-in Mockery capabilities to create a test double — when the Blade directive calls Route::currentRouteNamed(), we’re able to stub the value. With our test double in place, we don’t have to worry about what the actual current route is, as we’ve told the Route facade what it should return.

The assertDirectiveOutput() method gets a little gnarly, as we need to rely on the infamous, “Oh God, I hope I never have to use this” eval() PHP construct. We test the directive output by following three steps:

  1. Compile the Blade directive into PHP that we can execute.
  2. Start PHP’s output buffering, then extract some variables into the scope (e.g. make sure $group isn’t seen as undefined)
  3. Run eval() on our compiled Blade directive, then retrieve the output buffer.

If all goes according to plan, we’ll have either “active” or an empty string in the $output variable, depending on whether or not Route::currentRouteNamed() returned true or false, respectively.

That’s it!

Congratulations, we’ve written (and tested) our first custom Laravel Blade directive!

As you go on to write more directives of your own, remember: the callback passed to Blade::directive() should return the PHP expression to be evaluated, not the actual result! Once you’re able to remember that, you’ve unlocked the secret to writing your own custom Blade directives!

Previous

Paid Support for Legacy Libraries

Next

Demystifying Regular Expressions

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Be excellent to each other.