Twig cache extension

Overview

Twig cache extension

This extension moved to the Twig organization. Check out the repository over there instead: https://github.com/twigphp/twig-cache-extension.

The missing cache extension for Twig. The extension allows for caching rendered parts of templates using several cache strategies.

Build Status

Installation

The extension is installable via composer:

{
    "require": {
        "asm89/twig-cache-extension": "~1.0"
    }
}

Quick start

Setup

A minimal setup for adding the extension with the LifeTimeCacheStrategy and doctrine array cache is as following:



use Doctrine\Common\Cache\ArrayCache;
use Asm89\Twig\CacheExtension\CacheProvider\DoctrineCacheAdapter;
use Asm89\Twig\CacheExtension\CacheStrategy\LifetimeCacheStrategy;
use Asm89\Twig\CacheExtension\Extension as CacheExtension;

$cacheProvider  = new DoctrineCacheAdapter(new ArrayCache());
$cacheStrategy  = new LifetimeCacheStrategy($cacheProvider);
$cacheExtension = new CacheExtension($cacheStrategy);

$twig->addExtension($cacheExtension);

Want to use a PSR-6 cache pool?

Instead of using the default DoctrineCacheAdapter the extension also has a PSR-6 compatible adapter. You need to instantiate one of the cache pool implementations as can be found on: http://php-cache.readthedocs.io/en/latest/

Example: Making use of the ApcuCachePool via the PsrCacheAdapter:

composer require cache/apcu-adapter


use Asm89\Twig\CacheExtension\CacheProvider\PsrCacheAdapter;
use Asm89\Twig\CacheExtension\CacheStrategy\LifetimeCacheStrategy;
use Asm89\Twig\CacheExtension\Extension as CacheExtension;
use Cache\Adapter\Apcu\ApcuCachePool;

$cacheProvider  = new PsrCacheAdapter(new ApcuCachePool());
$cacheStrategy  = new LifetimeCacheStrategy($cacheProvider);
$cacheExtension = new CacheExtension($cacheStrategy);

$twig->addExtension($cacheExtension);

Usage

To cache a part of a template in Twig surround the code with a cache block. The cache block takes two parameters, first an "annotation" part, second the "value" the cache strategy can work with. Example:

{% cache 'v1/summary' 900 %}
    {# heavy lifting template stuff here, include/render other partials etc #}
{% endcache %}

Cache blocks can be nested:

{% cache 'v1' 900 %}
    {% for item in items %}
        {% cache 'v1' item %}
            {# ... #}
        {% endcache %}
    {% endfor %}
{% endcache %}

The annotation can also be an expression:

{% set version = 42 %}
{% cache 'hello_v' ~ version 900 %}
    Hello {{ name }}!
{% endcache %}

Cache strategies

The extension ships with a few cache strategies out of the box. Setup and usage of all of them is described below.

Lifetime

See the "Quick start" for usage information of the LifetimeCacheStrategy.

Generational

Strategy for generational caching.

In theory the strategy only saves fragments to the cache with infinite lifetime. The key of the strategy lies in the fact that the keys for blocks will change as the value for which the key is generated changes.

For example: entities containing a last update time, would include a timestamp in the key. For an interesting blog post about this type of caching see: http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works

Blackhole

Strategy for development mode.

In development mode it often not very useful to cache fragments. The blackhole strategy provides an easy way to not cache anything it all. It always generates a new key and does not fetch or save any fragments.

Setup

In order to use the strategy you need to setup a KeyGenerator class that is able to generate a cache key for a given value.

The following naive example always assumes the value is an object with the methods getId() and getUpdatedAt() method. The key then composed from the class name, the id and the updated time of the object:



use Asm89\Twig\CacheExtension\CacheStrategy\KeyGeneratorInterface;

class MyKeyGenerator implements KeyGeneratorInterface
{
    public function generateKey($value)
    {
        return get_class($value) . '_' . $value->getId() . '_' . $value->getUpdatedAt();
    }

}

Next the GenerationalCacheStrategy needs to be setup with the keygenerator.



use Asm89\Twig\CacheExtension\CacheStrategy\GenerationalCacheStrategy;
use Asm89\Twig\CacheExtension\Extension as CacheExtension;

$keyGenerator   = new MyKeyGenerator();
$cacheProvider  = /* see Quick start */;
$cacheStrategy  = new GenerationalCacheStrategy($cacheProvider, $keyGenerator, 0 /* = infinite lifetime */);
$cacheExtension = new CacheExtension($cacheStrategy);

$twig->addExtension($cacheExtension);

Usage

The strategy expects an object as value for determining the cache key of the block:

{% cache 'v1/summary' item %}
    {# heavy lifting template stuff here, include/render other partials etc #}
{% endcache %}

Using multiple strategies

Different cache strategies are useful for different usecases. It is possible to mix multiple strategies in an application with the IndexedChainingCacheStrategy. The strategy takes an array of 'name' => $strategy and delegates the caching to the appropriate strategy.

Setup



use Asm89\Twig\CacheExtension\CacheStrategy\IndexedChainingCacheStrategy;
use Asm89\Twig\CacheExtension\Extension as CacheExtension;

$cacheStrategy  = new IndexedChainingCacheStrategy(array(
    'time' => $lifetimeCacheStrategy,
    'gen'  => $generationalCacheStrategy,
));
$cacheExtension = new CacheExtension($cacheStrategy);

$twig->addExtension($cacheExtension);

Usage

The strategy expects an array with as key the name of the strategy which it needs to delegate to and as value the appropriate value for the delegated strategy.

{# delegate to lifetime strategy #}
{% cache 'v1/summary' {time: 300} %}
    {# heavy lifting template stuff here, include/render other partials etc #}
{% endcache %}

{# delegate to generational strategy #}
{% cache 'v1/summary' {gen: item} %}
    {# heavy lifting template stuff here, include/render other partials etc #}
{% endcache %}

Implementing a cache strategy

Creating separate caches for different access levels, languages or other usecases can be done by implementing a custom cache strategy. In order to do so implement the CacheProviderInterface. It is recommended to use composition and wrap a custom strategy around an existing one.

Authors

Alexander [email protected]

License

twig-cache-extension is licensed under the MIT License - see the LICENSE file for details

Comments
  • Add PSR-6 compatible adapter

    Add PSR-6 compatible adapter

    Next to the already available DoctrineCacheAdapter this PR adds a PSR-6 compatible adapter, which without any BC break allows to easily implement one of the pools available at:

    http://php-cache.readthedocs.io/

    In my opinion, and I think @Nyholm agrees with that too, it would be better to completely follow PSR-6 and get rid of all adapters, but that would be a huge BC break. I'd propose to release 1.3.0 after merging.

    opened by rvanlaak 12
  • fix deprecation message.

    fix deprecation message.

    Referencing the ... extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead

    opened by DenysMedvid 11
  • Fixed bug: Invalid recognition of extension name on Twig >= 1.26

    Fixed bug: Invalid recognition of extension name on Twig >= 1.26

    The "Asm89\Twig\CacheExtension\Extension" extension is not enabled.

    I just did a composer update and spent a lot of time on figuring out why the application is not working. Then next day in the morning I just got an idea and it worked. Here is the patch.

    The problem occurs when I'm using the emanueleminotto/twig-cache-bundle, it has a class that extends this one, and then probably the child class is registered as service.

    opened by blackandred 9
  • [WIP] Cache Digests

    [WIP] Cache Digests

    When working with designers it would be convenient if you could omit the versioning portion of the cache key, so you can use {% cache 'user' %} instead of {% cache 'user/v5' %}.

    The problem is, that if you have a complex structure of templates, where a tree of other templates is included inside the cache tag, then you have to version the key every time you change one of the included templates. Sometimes someone forgets to do this and this leads to unexpected results.

    Rails handles this by automatically including the hash of the contents and by all templates which are included as partials inside the cache tag. That's called "cache digests" and I found a post which also describes this problem: http://blog.remarkablelabs.com/2012/12/russian-doll-caching-cache-digests-rails-4-countdown-to-2013

    What do you think of this @asm89?

    opened by CHH 9
  • Allow configuring cache key prefix

    Allow configuring cache key prefix

    We'd like to have a namespace with our keys, the GenerationalCacheStrategy and LifetimeCacheStrategy already use some sort of prefix (e.g. __GCS__ and __LCS__). I'm aware that we easily can add it to the key in Twig itself, but that seems wrong.

    What about refactoring these "prefixes" to the interface and making it an optional constructor argument?

    interface CacheStrategyInterface
    {
        private $keyPrefix;
    
        // ...
    }
    
    class LifetimeCacheStrategy implements CacheStrategyInterface
    {
        private $keyPrefix = '__LCS__';
    
        // ...
    
        public function __construct(CacheProviderInterface $cache, $keyPrefix = null)
        {
            // ...
            if (!is_null($prefix)) {
                $this->keyPrefix = $keyPrefix;
            }
            // ...
        }
    }
    

    Our use-case is quite easy, we want to include the Symfony environment in our cache key. Basically I'd like to eventually have the prefix as a configuration parameter in the TwigCacheBundle.

    opened by rvanlaak 8
  • Post-process cache for reducing cache size

    Post-process cache for reducing cache size

    I'm rendering big list of categories using ul+li. Selected category should use span instead of a. Using standard way (cache key depends on active category id) we store N keys, N - categories count.

    This is inefficient:

    1. cache size growth
    2. cache miss rate big
    3. hard to invalidate cache. If we have something like products count near the category then we should invalidate all of the N keys when adding new product to one category. So cache miss rate will increase

    I'm proposing add some syntax for second pass for cached entry. Example:

    {# original - required N cache records #}
    {% cache 'sidebar-categories'~activeCatregory.id ttl %}
        {% set categories = categoriesHelper.loadCategories() %}
        <ul>
            {% for catrgory in categories %}
                <li>
                    {% if category.id == activeCatregory.id %}
                        <span>{{ catrgory.title }} ({{ category.productsCount }})</span>
                    {% else %}
                        <a href="{{ category.url }}">{{ category.title }} ({{ category.productsCount }})</a>
                    {% endif %}
                </li>
            {% endfor %}
        </ul>
    {% endcache %}
    
    {# with second pass syntax, only 1 cache record #}
    {% cache 'sidebar-categories' ttl {'activeCatergoryId': activeCatergory.id} %} {# pass activeCatregoryId to the cached rendered template #}
        {% set categories = categoriesHelper.loadCategories() %}
        <ul>
            {% for catrgory in categories %}
                <li>
                    {% cache_expression('if ('~category.id~' == $activeCatregoryId:') %}
                        <span>{{ catrgory.title }} ({{ category.productsCount }})</span>
                    {% cache_expression('else:') %}
                        <a href="{{ category.url }}">{{ category.title }} ({{ category.productsCount }})</a>
                    {% cache_expression('endif;') %}
                </li>
            {% endfor %}
        </ul>
    {% endcache %}
    
    // will compiled to smth like
    <ul>
        <li>
            <?php if (1 == $activeCategoryId): ?>
                <span>category 1 (59)</span>
            <?php else: ?>
                <a href="#ololo">category 1 (59)</a>
            <?php endif; ?>
        </li>
        <li>
            <?php if (2 == $activeCategoryId): ?>
                <span>category 2 (80)</span>
            <?php else: ?>
                <a href="#trololo">category 2 (80)</a>
            <?php endif; ?>
        </li>
    </ul>
    

    Syntax sux, but I hope you understand the goal.

    opened by Koc 7
  • Twig 2 compatibility

    Twig 2 compatibility

    The Twig_NodeInterface interface is deprecated in Twig 1.x and will be removed in Twig 2. Changing Twig_NodeInterface to Twig_Node should make the extension ready to support both Twig 1 and Twig 2 for now.

    opened by asm89 6
  • How this suppose to work?

    How this suppose to work?

    In php I'm defining time=time(); This is my twig code:

    {% cache 'v1' 10000 %}
        Hello! {{ time }}
    {% endcache %}
    

    Every time I hit refresh I have new timestamp. Doesn't it suppose to be fixed for 10 seconds?

    opened by piernik 5
  • Added BlackholeCacheStrategy for dev/debug-Mode to avoid any caching

    Added BlackholeCacheStrategy for dev/debug-Mode to avoid any caching

    In Development Mode it could be very annoying sometimes to have to clear the cache files at all when changing some twig and the previous cached version is rendered instead of the modified one.

    With this new Strategy one can overwrite the twig_cache-settings in config_dev.yml to avoid any caching: e.g.

    services.yml:

        twig.strategy.blackhole:
            class: Asm89\Twig\CacheExtension\CacheStrategy\BlackholeCacheStrategy
    

    config_dev.yml

    twig_cache:
        strategy: twig.strategy.blackhole
    
    opened by itinance 4
  • 500 error in embedded controller gets cached in prod mode?

    500 error in embedded controller gets cached in prod mode?

    When running in dev mode with debug off, this works as I'd expect:

    {% cache 'v1/blog' ~ pageNumber 900 %}
        {{ render(controller('AppBundle:Default:_blog', {'pageToken': pageToken, 'pageNumber': pageNumber})) }}
    {% endcache %}
    

    If an exception gets thrown inside that controller then it bubbles up as 500 and nothing gets cached.

    However, when I switch to prod mode with debug off I seem to just get a blank 200 page back if an underyling exception occurs. Then it seems to stay cached.

    These are the objects I'm using:

    app.cache_backend:
        class: Doctrine\Common\Cache\ApcCache
    
    app.cache_provider:
        class: Asm89\Twig\CacheExtension\CacheProvider\DoctrineCacheAdapter
        arguments: ['@app.cache_backend']
    
    app.lifetime_cache_strategy:
        class: Asm89\Twig\CacheExtension\CacheStrategy\LifetimeCacheStrategy
        arguments: ['@app.cache_provider']
    
    app.cache_extension:
        class: Asm89\Twig\CacheExtension\Extension
        arguments: ['@app.lifetime_cache_strategy']
        tags:
            - { name: twig.extension }
    
    opened by EdwardIII 3
  • Add toggle to Twig tag to toggle cache

    Add toggle to Twig tag to toggle cache

    I have a website that has a lot of festivals and concerts (events). I want to cache the whole output of the page to speed things up. But when a festival is in the past (expired) I don't want to serve it from cache but just fresh from the database. Those pages aren't visited very often so there is no need to cache it.

    Would it be possible to add an extra flag to the {% cache %} block to allow the toggle?

    For example:

    {% cache 'events' { key: 'event_' ~ event.id } event.shouldCache %}
      output
    {% endcache %}
    
    opened by ruudk 3
  • Add support for Twig 3.x

    Add support for Twig 3.x

    Hi,

    When, I want to download twig-cache-extension from packagist (repo TwigPHP), I have this error :

    Cette erreur survient avec la version 3.x de Twig, car elle n'est pas supporté

    Please can you add support for twig 3.x

    have a nice day !

    opened by Mael-91 0
  • Could not find a matching version of package psr/cache-implementation.

    Could not find a matching version of package psr/cache-implementation.

    asm89/twig-cache-extension suggests installing psr/cache-implementation (To make use of PSR-6 cache implementation via PsrCacheAdapter.)

    $ composer require psr/cache-implementation

    [InvalidArgumentException] Could not find a matching version of package psr/cache-implementation. Check the package spelling, your version constraint and t hat the package is available in a stability which matches your minimum-stability (stable).

    opened by jeffreyroberts 0
  • How to invalidate cache when data change?

    How to invalidate cache when data change?

    Hi,

    we want to cache templates that use lazily fetched data. Something like this:

    class ViewModel
    {
        public function getItem(): Item
        {
            $this->repository->findItem();
        }
    }
    
    {% cache 'v1/show_item' viewModel %}
            {{ viewModel.item.title }}
    {$ endcache %}
    

    Of course, most of the time our logic is more complicated than that. There is more template stuff to cache and more data stuff to cache. By using VM inside cached part of template, we have a lazily loaded entity (or entities, or other data), which seems cool enough to use this extension.

    The problem is: how do we invalidate the cache when values in Item change?

    We could use GenerationalCacheStrategy, but that would mean we cannot have Item (and other stuff) lazily–loaded.

    So we want to invalidate cache „manually” (but still in code ;)) when necessary. For example, there is code that changes title in some Item – then we'd like to call something like this: invalidateCache('show_item_'.$item->getId()).

    But what if there are more parameters to take care of? Like current user status, etc?

    Should we try cache tags? What's the best way to implement them here?

    Any other ideas?

    As always,

    There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton

    opened by MacDada 3
  • Deprecate in favor of twigphp/twig-cache-extension?

    Deprecate in favor of twigphp/twig-cache-extension?

    I see PR's open here without replies and there's actually even a bundle that still uses this repo. On the other hand twigphp/twig-cache-extension seems quiet.

    @asm89 maybe deprecate this repo and point out to twigphp/twig-cache-extension if that's what should happen in the end?

    opened by thanosp 4
  • nested generational cache

    nested generational cache

    hi, its somehow possible something like this?

    {% cache 'product' product %}
        {{ product.id }}
        {% cache 'category' category %}
            {{ category.id }}
        {% endcache %}
    {% endcache %}
    

    when i change category (i have own key generator, watching classname+id+updatedat, this is ok), i want change/regenerate only category block inside - but its not working for me, its possible to do this or i must regenerate top parent - product in this case? thanks

    opened by patie 10
Owner
Alexander
Alexander
The place to keep your cache.

Stash - A PHP Caching Library Stash makes it easy to speed up your code by caching the results of expensive functions or code. Certain actions, like d

Tedious Developments 944 Jan 4, 2023
PHP cache library, with adapters for e.g. Memcached, Redis, Couchbase, APC(u), SQL and additional capabilities (e.g. transactions, stampede protection) built on top.

Donate/Support: Documentation: https://www.scrapbook.cash - API reference: https://docs.scrapbook.cash Table of contents Installation & usage Adapters

Matthias Mullie 295 Nov 28, 2022
Cache slam defense using a semaphore to prevent dogpile effect.

metaphore PHP cache slam defense using a semaphore to prevent dogpile effect (aka clobbering updates, stampending herd or Slashdot effect). Problem: t

Przemek Sobstel 102 Sep 28, 2022
:zap: Simple Cache Abstraction Layer for PHP

⚡ Simple Cache Class This is a simple Cache Abstraction Layer for PHP >= 7.0 that provides a simple interaction with your cache-server. You can define

Lars Moelleken 27 Dec 8, 2022
Doctrine Cache component

Doctrine Cache Cache component extracted from the Doctrine Common project. Documentation This library is deprecated and will no longer receive bug fix

Doctrine 7.6k Jan 3, 2023
LRU Cache implementation in PHP

PHP LRU Cache implementation Intro WTF is a LRU Cache? LRU stands for Least Recently Used. It's a type of cache that usually has a fixed capacity and

Rogério Vicente 61 Jun 23, 2022
Simple cache abstraction layer implementing PSR-16

sabre/cache This repository is a simple abstraction layer for key-value caches. It implements PSR-16. If you need a super-simple way to support PSR-16

sabre.io 48 Sep 9, 2022
PSR-6 cache implementation adapting a given PSR-16 instance

PSR-6 cache implementation adapting PSR-16 This package provides a PSR-6 cache instance when you only have a PSR-16 cache at hand. As PSR-6 is more fe

null 1 Oct 15, 2021
More Than Just a Cache: Redis Data Structures

More Than Just a Cache: Redis Data Structures Redis is a popular key-value store, commonly used as a cache or message broker service. However, it can

Andy Snell 2 Oct 16, 2021
Simple cache

Simple cache

Róbert Kelčák 3 Dec 17, 2022
Elephant - a highly performant PHP Cache Driver for Kirby 3

?? Kirby3 PHP Cache-Driver Elephant - a highly performant PHP Cache Driver for Kirby 3 Commerical Usage Support open source! This plugin is free but i

Bruno Meilick 11 Apr 6, 2022
An improved helper for working with cache

Laravel Cache Installation To get the latest version of Laravel Cache, simply require the project using Composer: $ composer require dragon-code/larav

The Dragon Code 64 Sep 23, 2022
Zend Framework cache backend for MongoDB

Zend_Cache_Backend_Mongo Author: Anton Stöckl About Zend_Cache_Backend_Mongo is a Zend Framework Cache Backend for MongoDB. It supports tags and autoc

Anton Stöckl 12 Feb 19, 2020
PHP local cache

__ ____ _________ ______/ /_ ___ / __ \/ ___/ __ `/ ___/ __ \/ _ \ / /_/ / /__/ /_/ / /__/ / / / __/ / ._

Jayden Lie 48 Sep 9, 2022
A fast, lock-free, shared memory user data cache for PHP

Yac is a shared and lockless memory user data cache for PHP.

Xinchen Hui 815 Dec 18, 2022
A simple cache library. Implements different adapters that you can use and change easily by a manager or similar.

Desarolla2 Cache A simple cache library, implementing the PSR-16 standard using immutable objects. Caching is typically used throughout an applicatito

Daniel González 129 Nov 20, 2022
PHP Cache Duration

PHP Cache Duration Introduction A readable and fluent way to generate PHP cache time. Built and written by Ajimoti Ibukun Quick Samples Instead of thi

null 27 Nov 8, 2022
This is a Symfony bundle that lets you you integrate your PSR-6 compliant cache service with the framework

PSR-6 Cache bundle This is a Symfony bundle that lets you you integrate your PSR-6 compliant cache service with the framework. It lets you cache your

null 43 Oct 7, 2021