Laravel comes bundled with its own templating engine called Blade which is very powerful and a breeze to use, but there are times when it's not the best tool for the job. In my case, I first had to replace Blade with Twig in Laravel in order to use Miyagi for component development, and that made me spend a few hours out of my comfort zone learning how to integrate it in Laravel and because of that I'll share my lessons with you.

First you need to install Twig itself and a package called TwigBridge which  will allow you to seamlessly use it in Laravel. Open your terminal (who am I kidding, it's probably already open) and navigate to your application root folder, and run the following command:

composer install rcrowe/twigbridge twig/twig

After the installation is complete, you can run php artisan vendor:publish --provider="TwigBridge\ServiceProvider" which will publish the pacakge's configuration file at config/twigbridge.php. In this file you will be able to tweak a few things like which Laravel Facades are made available to Twig, as well as filters and global functions. With that, you are ready to start using Twig templates, so go ahead and create a new view inside resources/views called welcome.twig (if you don't already have one) to test that:

<!DOCTYPE html>
<html lang="{{ app.getLocale }}">
    <head>
        <!-- other regular head stuff -->
    </head>
    <body class="antialiased">
        <div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center sm:pt-0">        
            {% if Route.has('login') %}
                <div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
                    {% if auth_check() %}
                        <a href="{{ url('/home') }}" class="text-sm text-gray-700 underline">Home</a>
                    {% else %}
                        <a href="{{ route('login') }}" class="text-sm text-gray-700 underline">Login</a>

                        {% if Route.has('register') %}
                            <a href="{{ route('register') }}" class="ml-4 text-sm text-gray-700 underline">Register</a>
                        {% endif %}
                    {% endif %}
                </div>
            {% endif %}

            <div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
                <div class="mt-8 bg-white dark:bg-gray-800 overflow-hidden shadow sm:rounded-lg">
                    <!-- irrelevant content -->

                    <div class="ml-4 text-center text-sm text-gray-500 sm:text-right sm:ml-0">
                        Laravel v{{ app.version }}
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>

I adapted the original welcome.blade.php view that comes with a fresh installation to show you how it would like like when using Twig. Notice the "Route" facade is readily available but instead of using the static method call like Route::has() you use Twig's dot notation: Route.has(). Other global functions like route, url and app are also available.

If it's a fresh install of Laravel, your base route will already point to the "welcome" view, otherwise just create a route in the routes/web.php file like this:

Route::get('/', function () {
	return view('welcome');
});

Now, let's go a little bit further and use components in the application. Go ahead and create resources/views/components/navbar/navbar.twig and add this content:

<nav>
  <ul>
    <li>
      <a href="{{ url('/') }}">Home</a>
    </li>
    {% for item in items %}
      <li>
        <a href="{{ item.url }}">{{ item.label }}</a>
      </li>
    {% endfor %}
  </ul>
</nav>

and in the welcome.twig view, you can include it just after opening the body tag:

{% include "./components/navbar/navbar.twig" with {
    items: [
      {
        url: url('/nachos'),
        label: 'Nachos'
      },
      {
        url: url('/tacos'),
        label: 'Tacos'
      }
    ]
} %}

And just like that, now you're able to include, embed and extend Twig templates in Laravel. Though, the more your application grow and the more components you have, it might become unwieldy to write all those paths to components. To fight that, you can use Twig namespaces. Instead of writing ../../components/component-name.twig you can write it like this: @components/component-name.twig. All you need to do is add this snippet to the boot method in app/Providers/AppServiceProvider.php:


$loader = new \Twig\Loader\FilesystemLoader();
$loader->addPath(base_path() . '/resources/views/components', 'components');
\Twig::getLoader()->addLoader($loader);

where base_path() . '/resources/views/components' is the path to the component directory and components is how you will call it (i.e. @components). You can add multiple paths like this that point to different (or the same) directories.

If you need to add some custom functionality that is not provided by a built in filter or function, you can create it in a plain php file, something like app/Twig/Functions.php and inside you could add:

<?php
 
namespace App\Twig;
 
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
 
class Functions extends AbstractExtension
{
    public function getFunctions()
    {
        return [
            new TwigFunction('now', [$this, 'now']),
        ];
    }
 
    public static function now()
    {
      return date('d/m/Y H:i:s');
    }
}

Then, go to config/twigbridge.php and add 'App\Twig\Functions', to enabled extensions like:

'extensions' => [
    'enabled' => [
        // ... other enabled extensions ...
        'App\Twig\Functions'
    ]
]

And then in your views you can use your function like:

<p>{{ now() }}</p>

You can do the same with filters in app/Twig/Filters.php:

<?php
 
namespace App\Twig;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;

class Filters extends AbstractExtension
{
    public function getFilters()
    {
        return [
            new TwigFilter('count_words', [$this, 'countWords']),
        ];
    }
 
    public static function countWords($sentence)
    {
      return count(explode(' ', $sentence));
    }
}

then in config/twigbridge.php:

'extensions' => [
    'enabled' => [
        // ... other enabled extensions ...
        'App\Twig\Functions',
        'App\Twig\Filters'
    ]
]

and you will be able to use it like this in your views:

<p>{{ 'Laravel has wonderful, thorough documentation covering every aspect of the framework. Whether you are new to the framework or have previous experience with Laravel, we recommend reading all of the documentation from beginning to end.'|count_words }}</p>

And that pretty much covers lots of use cases you would need with Twig in Laravel. Hope this article helps you save some hours of searching for answers in StackOverflow and documentation. If you find any other use cases not covered in this post, let me know and I'll update it. Until the next post!