Using Laravel Blade with Statamic

December 21st, 2020

Statamic v3 has added support for other template languages in addition to their own "Antlers" templating language. Recently when creating a new website for one of our clients, we ran into some performance issues with Antlers. This really only happens when you have a large amount of partials and big templates, since Antlers does all its parsing & rendering on the fly with Regex.

Laravel Blade however compiles straight to native PHP, it picks up changes in your template, recompiles it to PHP and stores it inside the storage/views folder of your project. This makes Blade one of the fastest solutions to templating in combination with Laravel.

This made our choice easy to choose Blade instead of Antlers with this new, larger website. Implementing everything with Blade however is not that easy as Statamic assumes that everything goes to Antlers in a few different ways. Better compatibility with Blade & Twig is on the roadmap.

In this post I'll list a few examples on how things are done in Antlers, and how you would solve them with Blade in combination with Statamic.

Layouts & Templates

Your page templates in Antlers don't need any indication of what layout they use and injects their content in the layout. Statamic is pretty smart in this regard that it has a cascade of views it checks to use as the layout. This is different in Blade.

A basic layout & template in Antlers

<!DOCTYPE html>
<html lang="en">
<head>
    ...
</head>
<body>
    {{ template_content }}
</body>
</html>
<div>Some content here</div>

A basic layout & template in Blade

<!DOCTYPE html>
<html lang="en">
<head>
    ...
</head>
<body>
    @yield('content')
</body>
</html>
@extends('layout')

@section('content')
<div>Some content here</div>
@endsection

Modifying values

Antlers has a very dev-friendly syntax for modifying values. It borrows this syntax from Twig where you can add modifiers using a pipe delimited syntax. Blade doesn't have any of this built-in, but luckily for us it allows just writing PHP inside the tags.

{{ "Some text" | slugify | upper }} <!-- SOME-TEXT -->

What Antlers does behind the scenes, is put this value through a series of methods, Statamic provides an easy way to do this in PHP using the Statamic\Modifiers\Modify class which allows you to chain an infinite amount of modifiers after the initial value.

{!! \Statamic\Modifiers\Modify::value('Some text')->slugify()->upper() !!} <!-- SOME-TEXT -->

This isn't as concise or pretty as the Antlers version, but it works. What I have done in our projects is added a helper function to our project that wraps this up nicely:

use Statamic\Modifiers\Modify;

function modify($value): Modify
{
    return Modify::value($value);
}

This then allows you to use the helper in your Blade templates

{!! modify('Some text')->slugify()->upper() !!} <!-- SOME-TEXT -->

Displaying page content

Statamic automagically wires the visited URLs to the correct pages and injects the data into the view. Luckily this is done for Blade templates as well, so manual fetching of content can be kept to a minimum. Displaying the data however is a bit different, as in Blade all values are wrapped in a Statamic\Fields\Value class. If the value is a string you can just display it with the default {{ }} tags of Blade, if not, you'll first need to extract the value.

{{ title }} <!-- Antlers -->
{{ $title }} <!-- Blade -->

Array & other data types are different, notice the ->value() call we need to fetch the values.

<ul>
    {{ list }}
        <li>{{ value }}</li>
    {{ /list }}
</ul>
<ul>
    @foreach ($list->value() as $value)
        <li>{{ $value }}</li>
    @endforeach
</ul>

Fetching content

A CMS wouldn't be a CMS without the ability to fetch a list of entries and displaying them. In Antlers there's the collection tag which is used for retrieving entries.

In Blade, we'll have to dig into PHP and retrieve the entries manually using the Entry facade.

In the examples below we're displaying a list of the 3 most recent blogposts.

{{ collection:blog limit="3" }}
    <div>
        <a href="{{ url }}">{{ title }}</a>
    </div>
{{ /collection:blog }}
@foreach (\Statamic\Facades\Entry::query()->where('collection', 'blog')->where('status', 'published')->limit(3)->get() as $entry)
    <div>
        <a href="{{ $entry->url() }}">
            {{ $entry->augmentedValue('title') }} // Note: we're working with an Entry object here, not an array, so we have to augment the values manually
        </a>
    </div>
@endforeach

As you can see, the Blade version doesn't really spark joy. And there's a lot you need to consider since the query returns all entries, regardless of publish status by default.

Thankfully, there's a better way, which doesn't require you to know the internals of every tag either.

A helper to fetch any Antlers tag's data

What I've been using is the following helper, which takes a string of the tag you want to render, combined with a set of parameters, which then allows you to create the same blog listing above in Blade:

use Statamic\Tags\Loader as TagLoader;
use Statamic\View\Antlers\Parser;

function tag(string $name, array $params = [])
{
    if ($pos = strpos($name, ':')) {
        $original_method = substr($name, $pos + 1);
        $method = Str::camel($original_method);
        $name = substr($name, 0, $pos);
    } else {
        $method = $original_method = 'index';
    }

    $tag = app(TagLoader::class)->load($name, [
        'parser'     => app(Parser::class),
        'params'     => $params,
        'content'    => '',
        'context'    => [],
        'tag'        => $name.':'.$original_method,
        'tag_method' => $original_method,
    ]);

    return $tag->$method();
}
@foreach (tag('collection:blog', ['limit' => 3]) as $entry)
    <div>
        <a href="{{ $entry->url() }}">
            {{ $entry->augmentedValue('title') }} // Note: we're working with an Entry object here, not an array, so we have to augment the values manually
        </a>
    </div>
@endforeach

I haven't found any issues with this helper yet, though doing a quick dd() of what the tag returns helps a lot in how to process the data.

Displaying a navigation

With the helper above, displaying a navigation is just as easy as using the Antlers tag.

@foreach (tag('nav:main') as $navItem)
    <a href="{{ $navItem['url'] }}" {{ $navItem['is_current'] ? 'active' : '' }}>
        {{ $navItem['title'] }}
    </a>
@endforeach

An addon with Blade directives

Erin has created a very helpful Statamic Blade addon that adds a few Blade directives you can use in templating, the blog listing above would look like this:

@collection('blog', ['limit' => 3])
    <div>
        <a href="{{ $entry['url'] }}">{{ $entry['title'] }}</a>
    </div>
@endcollection

What's very helpful about the addon is that it augments all values and you're always working with arrays. A downside can be that not all parameters or functions are supported yet. But the addon is improving rapidly!

https://github.com/edalzell/statamic-blade

Debugging & preventing errors

Antlers has a very helpful {{ dump }} tag & modifier that displays all the data currently available. To do this in Blade, you can use {{ dump(get_defined_vars()) }} which dumps, as it says, all defined variables.

Try and be liberal with Laravel's optional($value) helper in Blade, Antlers handles undefined and null values very gracefully by just not displaying anything when it goes wrong. Blade will throw an error, this especially happens often when using live preview in combination with a Bard or replicator fieldtype, which adds a set with empty values.

A note on performance

Below you'll find some screenshots of the performance timings of a site before (Antlers) & after (Blade) with exactly the same content & number of partials.

antlers.png
Antlers
blade.png
Blade

Again, this is on a site with a large amount of partials, templates & code. A smaller site will not see a big benefit in performance. Consider if moving to Blade is worth the time investment, as it will take more time to implement and work without the niceties of the Statamic-Antlers integration.

Wrapping up

I'm very excited that better Blade support is on the roadmap, as a developer that does more Laravel project work than Statamic site work, it's at least a welcome addition that it's possible to use Blade at all.

If you have any other tips & trick on using Blade with Statamic, be sure to let me know and I'll add them to this blogpost.

MENU