Installation:
composer require rcrowe/twigbridge
php artisan vendor:publish --provider="TwigBridge\ServiceProvider"
This publishes the default config file to config/twig.php.
First Twig View:
Create a .twig file in resources/views/ (e.g., welcome.twig):
<h1>{{ 'Hello, ' ~ user.name }}!</h1>
Render it via a route:
Route::get('/', fn() => view('welcome', ['user' => new class { public $name = 'Twig'; }]));
Key Config:
Update config/twig.php to define:
paths (custom view paths, e.g., ['custom' => resource_path('views/custom')]).cache (enable/disable Twig cache for development/production).extensions (register custom Twig extensions).Replace a Blade template with Twig for a complex email template (e.g., resources/views/emails/welcome.twig):
{% extends 'layouts.email.twig' %}
{% block content %}
<p>Hi {{ user.name }},</p>
<p>Your verification link: <a href="{{ url('verify', token) }}">Verify</a></p>
{% endblock %}
Pass data via a controller:
Mail::send('emails.welcome', ['user' => $user, 'token' => $token]);
Hybrid Blade/Twig Projects:
// config/mail.php
'markdown' => [
'engine' => Twig::class,
],
Dynamic Template Rendering:
// Controller
return view('dashboard.twig', [
'stats' => $this->getStats(),
'filters' => $request->filters,
]);
{# dashboard.twig #}
{% for stat in stats %}
<div>{{ stat.label }}: {{ stat.value|upper }}</div>
{% endfor %}
Reusable Components:
_partials/header.twig) and extends:
{# _partials/header.twig #}
<header>
<h1>{{ title|default('Default Title') }}</h1>
</header>
{# page.twig #}
{% include '_partials/header.twig' with {'title': 'Home'} %}
Form Handling:
<form method="POST">
<input type="text" name="email" value="{{ form.email.value }}">
{% for error in form.email.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</form>
Pass the Form object from Laravel:
return view('contact.twig', ['form' => $form]);
API Responses:
return response()->json(['html' => view('email_body.twig', $data)->render()]);
Laravel Mix/Vite:
TwigBridge plays well with asset pipelines. Use {{ asset('js/app.js') }} in Twig as usual.
Example:
<script src="{{ asset('js/app.js') }}"></script>
Service Providers: Bind custom Twig extensions in a service provider:
public function boot()
{
Twig::getEnvironment()->addExtension(new \App\Twig\CustomExtension());
}
Testing: Mock Twig views in PHPUnit:
$view = new \TwigBridge\ViewFactory();
$html = $view->make('welcome.twig', ['user' => $user])->render();
$this->assertStringContainsString('Hello', $html);
Caching Quirks:
php artisan twig:clear
'cache' => false, // config/twig.php
Namespace Collisions:
home.blade.php vs. home.twig). Use distinct namespaces or prefixes.Extension Loading Order:
config/twig.php under extensions must implement Twig\Extension\ExtensionInterface. Load them after the default extensions to avoid overrides.Laravel Helpers in Twig:
old(), csrf_field()) are auto-available in Twig. Use the TwigBridge\Twig\Extension\LaravelExtension or manually register:
Twig::getEnvironment()->addFunction(new \Twig\TwigFunction('csrf_field', fn() => csrf_token()));
Form Request Binding:
Illuminate\Http\Request objects passed to Twig, use old() carefully:
<input value="{{ old('email', form.email.value) }}">
Template Not Found:
Ensure .twig files are in resources/views/ or a path listed in config/twig.php['paths']. Check for typos in view names.
Undefined Variables:
Twig is strict about undefined variables. Use default filter:
{{ user.name|default('Guest') }}
Syntax Errors:
Twig errors often point to line numbers in the compiled template (not the original .twig file). Check the storage/framework/views directory for compiled files.
Custom Filters: Create a filter for Laravel-specific logic:
// app/Twig/AppExtension.php
use Twig\TwigFilterMethod;
class AppExtension extends \Twig\Extension\AbstractExtension
{
public function getFilters()
{
return [
new TwigFilterMethod($this, 'laravelRoute', ['is_safe' => ['html']]),
];
}
public function laravelRoute($name, $params = [])
{
return route($name, $params);
}
}
Register in config/twig.php:
'extensions' => [
\App\Twig\AppExtension::class,
],
Global Variables: Add globals via the service provider:
public function boot()
{
Twig::share('app_name', config('app.name'));
Twig::share('settings', $this->app->make('App\Services\SettingsService'));
}
Use in Twig:
<title>{{ app_name }} - {{ settings.title }}</title>
Overriding Default Extensions:
Disable built-in extensions (e.g., LaravelExtension) and replace them:
'extensions' => [
\App\Twig\CustomLaravelExtension::class,
],
Precompile Templates: For production, precompile templates to reduce runtime overhead:
php artisan twig:compile
Avoid Complex Logic: Offload heavy logic to PHP controllers. Use Twig for presentation only:
{# Bad: #}
{% if user.isPremium and user.subscription.active and now < user.subscription.expires %}
<div>Premium Content</div>
{% endif %}
{# Good: #}
{% if user.isPremium %}
{% include 'premium_content.twig' %}
{% endif %}
How can I help you explore Laravel packages today?