Tools For Better Developers

Framework: Blade Traits

2021 November Osm Framework

6 months ago ∙ 2 minutes read

Currently, I'm working on Osm Admin package, and I need a module to inject its HTML markup around some well-known place in a Blade template. However, Blade template extensibility is not a problem that's specific to Osm Admin project. It's a generic problem. Let's solve that.


New messages module will provide JavaScript functions for showing automatically disappearing messages. In order to do that, it has to render a <template> tag in the end of HTML page during server-side page rendering, and then create HTML from it in JavaScript code.

All page templates are based on <x-std-pages::layout> Blade component:


The <x-std-pages::layout> uses std-pages::layout template:

<!doctype html>
<html lang="en">
        {{ $footer }}

The $footer here is a Blade component slot that may be filled in by the caller template. Let's say that it will be rendered unconditionally:

{{ $footer }}

The messages module could render its <template> tag just after the {{ footer }} slot. However, currently there is no way for a module to inject its markup into an existing template.

@around And @proceed Directives

A module can inject PHP code into any class method using a dynamic trait. In a similar fashion, a module could inject its markup use a kind of Blade template trait.

Let's say that the messages module defines a Blade template trait in themes/_base/views/messages/traits/std-pages/layout.blade.php:

@around({{ $footer }})
    <template id="message-template">

The file path specifies that it's a trait by being in the traits/ subdirectory, and that it extends the std-pages::layout template.

New @around directive specifies a text to search - {{ $footer }}, and a text to replace it with - everything till @endaround.

New @proceed directive specifies the position of the injected markup relative to the original slot content. In this example, the injected <template> tag is injected after the original content.

This way, any module could inject their content before and after the original content.

The same file can contain several @around directives. If @around directive goes without parameters, the whole template is replaced:

    <!-- Everything here goes after closing `</html>` tag -->


Said and done!

I had to dig into how Blade actually works. Before rendering every template, it compiles it into plain PHP template using Compiler::compileString() method.

Luckily, I already subclassed the Compiler class earlier, so I overwrote the compileString() method. The overwritten method checks if any module has a matching file in views/{module}/traits/{path} directory, and if so, it reads and applies all the @around directives to the currently compiled Blade template.

Full implementation is 50-60 lines of code, a lot less than I expected! See Compiler class for more details.