twig/cache-extra
Twig extension integrating Symfony Cache to cache template fragments. Adds a single cache tag for easy fragment caching in Twig views, improving performance with configurable cache backends via the Symfony Cache component.
composer require twig/cache-extra
config/twig.php):
'extensions' => [
// ...
Twig\Extra\Cache\CacheExtension::class,
],
.env:
CACHE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
php artisan vendor:publish --provider="Symfony\Cache\CacheBundle\CacheBundle" --tag="config"
Cache a static-but-expensive fragment (e.g., a product grid or navigation bar) in a Twig template:
{% cache 'product_grid_' ~ category_id, 300 %}
{% include 'partials/product-grid.html.twig' %}
{% endcache %}
'product_grid_' ~ category_id (unique per category).300 seconds (5 minutes).redis-cli keys '*'
You should see a key like twig_cache:product_grid_123.redis-cli DEL twig_cache:product_grid_123
{# Template: sidebar.html.twig #}
{% cache 'sidebar_' ~ user.role, 600 %}
{% include 'partials/sidebar-' ~ user.role ~ '.html.twig' %}
{% endcache %}
user.role, tenant_id) to avoid stale data.{% cache 'products:grid_' ~ category_id ~ ':tags=products', 300 %}
{% include 'partials/product-grid.html.twig' %}
{% endcache %}
Cache::tags(['products'])->flush();
{% if app.debug %}
{% include 'partials/product-grid.html.twig' %}
{% else %}
{% cache 'product_grid_' ~ category_id, 300 %}
{% include 'partials/product-grid.html.twig' %}
{% endcache %}
{% endif %}
{% cache 'dashboard_' ~ user.id ~ '_' ~ app.locale, 900 %}
{% include 'partials/dashboard.html.twig' %}
{% endcache %}
Use Laravel’s Cache Facade for Invalidation:
// Invalidate a specific cache key
Cache::forget('product_grid_123');
// Invalidate by tag (Redis only)
Cache::tags(['products'])->flush();
Leverage Laravel’s Redis Config:
config/cache.php uses the redis driver:
'default' => env('CACHE_DRIVER', 'redis'),
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
],
],
Cache Warming:
use Symfony\Component\Cache\Adapter\RedisAdapter;
$cache = new RedisAdapter(
Redis::connection('cache')->getClient(),
'twig_cache',
0 // Default lifetime
);
$cache->get('product_grid_123', function() {
// Expensive operation to generate the fragment
return renderFragment('product-grid');
});
Short TTLs for Dynamic Content:
60 seconds) for fragments that change frequently.Avoid Over-Caching:
Monitor Cache Hit/Miss Ratios:
redis-cli --scan --pattern 'twig_cache:*'
Redis Dependency for Tags:
Cache::tags()->flush()) only works with Redis. File/database drivers ignore tags.Cache::forget() for non-Redis setups or document the limitation in an ADR.Key Collisions:
{# Bad: Static key #}
{% cache 'sidebar', 300 %} ... {% endcache %}
{# Good: Context-aware #}
{% cache 'sidebar_' ~ user.role, 300 %} ... {% endcache %}
Debugging Cached Fragments:
APP_DEBUG=true) to bypass caching during development.Cache Stampedes:
symfony/cache-lock) for shared caches.Symfony Cache Overhead:
composer why symfony/cache.optimize command to reduce autoload overhead:
php artisan optimize
Inspect Cache Keys:
redis-cli keys 'twig_cache:*'
redis-cli DEL twig_cache:product_grid_123
Enable Twig Debug Mode:
{% if app.debug %}
{% set _cache = false %}
{% endif %}
Log Cache Hits/Misses:
CacheExtension to log statistics:
use Twig\Extension\AbstractExtension;
use Psr\Log\LoggerInterface;
class CacheStatsExtension extends AbstractExtension
{
public function __construct(private LoggerInterface $logger) {}
public function getTokenParsers(): array
{
return [
new \Twig\TokenParser\CacheTokenParser($this->logger),
];
}
}
Custom Cache Adapter:
use Symfony\Component\Cache\Adapter\RedisAdapter;
use Illuminate\Support\Facades\Redis;
$cache = new RedisAdapter(
Redis::connection('cache')->getClient(),
'twig_cache',
0
);
Dynamic TTLs:
{% set ttl = user.is_premium ? 3600 : 300 %}
{% cache 'dashboard_' ~ user.id, ttl %}
...
{% endcache %}
Event-Based Invalidation:
ProductUpdated) to invalidate caches:
use Illuminate\Support\Facades\Cache;
event(new ProductUpdated
How can I help you explore Laravel packages today?