How to compile a string using Laravel Blade

First Posted: 05-October-2020
Last Updated: 05-October-2020

Laravel's templating engine Blade1 is a beautiful and powerful tool. But the blade engine will only compile templates that are actually written to a file somewhere (as far as I know). This is not a meaningful limitation in everyday uses cases, but there may come a day where you need to compile a string.

To illustrate the basic steps involved let's look at the example below and see how we could compile this string using the Blade engine.

$title = "Blade is awesome";

$template = '# {{ Str::title($title) }}';

The first thing we do to is put this content into a temporary file. So the question becomes where should we create this file? You can theoretically store this file anywhere, but I like to create a new tmp folder in the storage directory.

storage/
├── framework/
│──── views/
│────── tmp/

So we know where we will store in, we now need a name for the file, I prefer to use the uniqid2 function like the example below, I prefix the file with blade_ just for convenience in debugging.

$filename = uniqid('blade_');

Notice that I did not append .blade.php, this is because when Blade attempts to find the file it will automatically append the .blade.php extension.

To generate the full path to the desired file I use one of the path helper functions3:

$filepath = storage_path("framework/views/tmp/$filename.blade.php");

When Blade is looking for the view file it will only look in places where you have told it to look. If you look at your default config/view.php file you find a paths array, this is an array of all the places that Blade will look for templates that you want it to compile.

If we want Blade to find our generated files at the path we just generated we need to tell it to also check in that location, so we will add it to the view.php file:

'paths' => [
    resource_path('views'),

    storage_path("framework/views/tmp"),
],

We now have the filename and path, we just need to ensure that the directory exists

if (!file_exists(storage_path("framework/views/tmp"))) {
    mkdir(storage_path("framework/views/tmp"), 0777, true);
}

So we know the directory exists, we just need to write the template to the filepath:

file_put_contents($filepath, trim($template));

Finally we have our template in a file, we can now compile it using the View class!

use Illuminate\Support\Facades\View;

$rendered = (View::make($filename, [
    'title' => $title,
]))->render();

// output => # Blade Is Awesome

The last thing we need to do is clean up that temporary file:

unlink($filepath);

That's really it, not to bad once you break it down. If you want to keep reading, we will refactor this into a nice helper function for your toolbag.

Refactoring

We can rewrite the above steps into a helper function like below.

/**
 * Render a given blade template with the optionally given data
 */
function blade($template, $data = []): string
{
    $filename = uniqid('blade_');

    $path = config('view.tmp_directory');

    View::addLocation($path);

    $filepath = $path . DIRECTORY_SEPARATOR . "$filename.blade.php";

    if (!file_exists($path)) {
        mkdir($path, 0777, true);
    }

    file_put_contents($filepath, trim($template));

    $rendered = (View::make($filename, $data))->render();

    unlink($filepath);

    return $rendered;
}

Notice instead of adding our path to the paths array, we are using the addLocation method to tell Blade to check that path at runtime. If we just added it to the paths array, Blade may check there when compiling our normal views which just decreases performance. We also made the temporary directory configurable by adding the tmp_directory to our view.php file.

Conclusion

One thing to consider in real world applications is caching your compiled templates and checking if it already exists before recompiling it, it might look something like below:

public function getContentAttribute($value)
{
    if (app()->environment('production')) {
        if(Cache::has('blog.posts.content.' . $this->id)) {
            return Cache::get('blog.posts.content.' . $this->id);
        }
    }

    $contents = blade($value, $this->getViewData());

    if (app()->environment('production')) {
        Cache::put('blog.posts.content.' . $this->id, $contents);
    }

    return $contents;
}

I hope that this helps you out if you ever find yourself needing to compile a string using Blade. If have any questions or ideas to improve this flow let me know on twitter @wyattcastaned44

References

  1. https://laravel.com/docs/8.x/blade
  2. https://www.php.net/manual/en/function.uniqid.php
  3. https://laravel.com/docs/8.x/helpers#method-storage-path
  4. https://github.com/laravel/framework/blob/8.x/src/Illuminate/View/FileViewFinder.php