Slim 4 - Blade

Daniel Opitz
Daniel Opitz
18 Jun 2022

Table of contents

Requirements

Introduction

Blade is a template engine for PHP that is included with Laravel. Unlike other PHP templating engines, Blade does not restrict the use of plain PHP code in your templates. All Blade templates are compiled to normal PHP code and cached until they are changed, meaning that Blade essentially adds no additional overhead to your application.

In this article I would like to show a way to use the Laravel’s Blade Templating Engine outside of Laravel and how to integrate it into a Slim 4 project.

Installation

First, install the following packages using composer:

composer require illuminate/view
composer require illuminate/config

Blade template files use the .blade.php file extension and are usually stored in the templates/ directory.

Create a new directory templates in your project root directory.

By default, Blade template views are compiled on demand. When a request is executed that renders a view, Blade will determine if a compiled version of the view exists. If the file exists, Blade will then determine if the uncompiled view has been modified more recently than the compiled view. If the compiled view either does not exist, or the uncompiled view has been modified, Blade will recompile the view.

Create a new template cache directory {project}/tmp/templates in your project root directory. Make sure the directory {project}/tmp/templates exists and has read and write access permissions.

Configuration

Add the new configuration keys in your config/defaults.php file:

$settings['template'] = __DIR__ . '/../templates';
$settings['template_temp'] = __DIR__ . '/../tmp/templates';

Next, add the following DI container definitions:

<?php

use Illuminate\Config\Repository;
use Illuminate\Container\Container as IlluminateContainer;
use Illuminate\Contracts\Container\Container as IlluminateContainerInterface;
use Illuminate\Contracts\View\Factory as IlluminateViewFactoryInterface;
use Illuminate\Events\Dispatcher;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Facades\Facade;
use Illuminate\View\Compilers\BladeCompiler;
use Illuminate\View\ViewServiceProvider;
use Psr\Container\ContainerInterface;
// ...

return [

    // ...
    
    IlluminateContainerInterface::class => function (ContainerInterface $container) {
        $app = IlluminateContainer::getInstance();
        Facade::setFacadeApplication($app);

        $app->bindIf('files', function () {
            return new Filesystem();
        }, true);

        $app->bindIf('events', function () {
            return new Dispatcher();
        }, true);

        $app->singleton('config', function () {
            return new Repository();
        });

        return $app;
    },

    IlluminateViewFactoryInterface::class => function (ContainerInterface $container) {
        $illuminateContainer = $container->get(IlluminateContainerInterface::class);

        $settings = $container->get('settings');
        $viewPaths = (array)$settings['template'];
        $cachePath = (string)$settings['template_temp'];

        /** @var Repository $config */
        $config = $illuminateContainer->get('config');
        $config->set([
            'view.paths' => $viewPaths,
            'view.compiled' => $cachePath,
        ]);

        $viewServiceProvider = new ViewServiceProvider($illuminateContainer);
        $viewServiceProvider->register();

        $view = $illuminateContainer->get('view');
        $illuminateContainer->singleton(IlluminateViewFactoryInterface::class, function () use ($view) {
            return $view;
        });

        return $view;
    },
];

This alone would technically work to render a Blade template, but we also need to make it work with the PSR-7 response object.

For this purpose we create a special TemplateRenderer class which does this work for us.

So next create a file in src/Renderer/TemplateRenderer.php and copy/paste this code:

<?php

namespace App\Renderer;

use Illuminate\Contracts\View\Factory as IlluminateViewFactoryInterface;
use Psr\Http\Message\ResponseInterface;

final class TemplateRenderer
{
    private IlluminateViewFactoryInterface $view;

    public function __construct(IlluminateViewFactoryInterface $view)
    {
        $this->view = $view;
    }

    public function template(
        ResponseInterface $response,
        string $template,
        array $data = []
    ): ResponseInterface {
        $contents = $this->view->make($template, $data)->render();
        $response->getBody()->write($contents);

        return $response;
    }
}

Usage

Instead of using the Blade Engine directly we use the TemplateRenderer object to render the template into a PSR-7 compatible object.

A typical Action handler class might look like this to render a template with the name home.blade.php:

<?php

namespace App\Action\Home;

use App\Renderer\TemplateRenderer;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

final class HomeAction
{
    private TemplateRenderer $renderer;

    public function __construct(TemplateRenderer $renderer)
    {
        $this->renderer = $renderer;
    }

    public function __invoke(
        ServerRequestInterface $request,
        ResponseInterface $response
    ): ResponseInterface {
        // Passing data to views
        $viewData = [
            'name' => 'World',
        ];
        
        return $this->renderer->template($response, 'home', $viewData);
    }
}

To make it work, create a template file in templates/home.blade.php with this content:

Hello, .

If everything is configured correctly you should see the following output:

Hello, World.

Sharing Data With All Views

Occasionally, you may need to share data with all views that are rendered by your application. You may do so by adding a share method to the App\Renderer\TemplateRenderer class. Typically, you should place calls to the share method within a middleware process method.

/**
 * Add a piece of shared data to the environment.
 *
 * @param array|string $key
 * @param mixed $value
 *
 * @return void
 */
public function share($key, $value = null): void
{
    $this->view->share($key, $value);
}

Usage

$this->renderer->share('csrf_token', 'random_value');

… or pass an array:

$this->renderer->share(
    [
        'google_analytics', 'UA-123456789-1',
        'key2' => 'value2',
    ]
);

More information about the Blade templating engine can be found on https://laravel.com/docs/master/blade.

Components

Components and slots provide similar benefits to sections, layouts, and includes; however, some may find the mental model of components and slots easier to understand.

However, if you really want to use Components you also need to install the laravel/framework package. Otherwise, you would get an error message like this:

Target [Illuminate\Contracts\Foundation\Application] is not instantiable.

Furthermore, you would have to reconfigure the DI container to use the \Illuminate\Foundation\Application class instead of the usual Illuminate\Contracts\Container\Container. This is only possible with some tricks and will probably only work properly in a Laravel project. The usage of Components without Laravel is therefore not recommended. However, this functionality can also be implemented with the existing Blade features, so the use of Components is not really necessary.

Conclusion

The reason Blade is so popular is because of Laravel.

The Blade syntax stays much closer to PHP (which was a template engine itself 25 years ago). For example, Twig is by design another language, making it much more difficult to wrap your head around.

I think blade is not the best example of software design, because it’s really hard to get running without Laravel. Even then, you may never get all features (like Components) without installing the full Laravel framework package. The Laravel DI container is a complex “beast” that has to be configured within (and partly beside) the PSR-11 DI container. Unfortunately, the package adds a lot of global singletons (anti-pattern) to the project scope, which I really try to avoid.

Using Blade only makes sense in combination with Laravel. Outside of Laravel I would not use this package and would not recommend it to anyone.

Read more