jarednova/timber — pull request #80 by mgmartel (2013-10-16 20:46:16+00:00)
Title: Object cache

Hi again! My current project was starting to lag a bit on my local server, because I'm loading and looping over a lot of files in a single view. Though using the built-in Twig compilation cache makes quite a difference already, it did not quite do the trick yet. This pull request implements two methods of caching:

## 1. Top-level caching
This one is quite simple. Inspired by the PodsView class of the Pods Framework, TimberLoader::render (and by proxy Timber::render) implements caching using either object cache (default), transients or site transients (for multisite).

In Pods, I never liked how the caching is based on just the file path. Instead, TimberLoader now hashes public fields in the view context. This means that as soon as the data changes, the cache is automatically invalidated (yay!).

Usage:

```php
Timber::render(
$filenames,
$data,
$echo,
$expires, /** Default: false. False disables cache altogether. When passed an array, the first value is used for non-logged in visitors, the second for users **/
$cache_mode /** Any of the cache mode constants defined in TimberLoader **/
);
```

The cache modes are:
```php
TimberLoader::CACHE_NONE /** Disable caching **/
TimberLoader::CACHE_OBJECT /** WP Object Cache **/
TimberLoader::CACHE_TRANSIENT /** Transients **/
TimberLoader::CACHE_SITE_TRANSIENT /** Network wide transients **/
TimberLoader::CACHE_USE_DEFAULT /** Use whatever caching mechanism is set as the default for TimberLoader **/
```

The default is set by filtering `timber_cache_mode`, or by extending TimberLoader and setting $cache_mode. Defaults to object cache.

This method is very effective, but crude - the whole template is cached. So if you have any context dependent sub-views (eg. current user), this mode won't do.

## 2. Template based caching

This method implements the [Twig Cache Extension](https://github.com/asm89/twig-cache-extension). It adds the cache tag, for use in templates. Best shown by example:

```
{% cache 'index/content' posts %}
{% for post in posts %}
{% include ['tease-'~post.post_type~'.twig', 'tease.twig'] %}
{% endfor %}
{% endcache %}
```

The mechanism behind it is the same as with render - the cache key is determined based on a hash of the object/array passed in (in the above example `posts`).

The cache method used is always the default mode, set using the bespoke filter (by default, object cache).

This method allows for very fine-grained control over what parts of templates are being cached and which are not. When applied on resource-intensive sections, the performance difference is huge.

### Extra: TimberKeyGeneratorInterface

Instead of hashing a whole object, you can specify the cache key in the object itself. If the object implements `TimberKeyGeneratorInterface`, it can pass a unique key through the method get_cache_key. That way a class can for example simply pass last_updated as the unique key.
If arrays contain the key `_cache_key`, that one is used as cache key.

This may save yet another few processor cycles.

# Notes

* I was not sure about what to do with the file structure. I added a cache directory in the functions dir, and used it for the cache classes and the Twig Cache extension. This is somewhat analogous to how the router is included in Timber. Because there were a lot of files to be included, I added a simple little autoloader for the Cache Extension and Timber\Cache namespaces.

What would you think about refactoring the file structure and implement Composer to manage dependencies? (We should open an issue to discuss this.. it seems relevant to this PR though)

* Though the cache is invalidated when the data changes, the cache item isn't actually flushed. This is fine when using a back-end like Memcached, that throws out oldest accessed cache entries when memory runs out. When using transients without expiration, however, this may lead to database bloat in the long run. I'm not sure how much of a problem this really is (or if that's the responsibility of the programmer choosing to use transients for the Timber cache...). Something to think about...
* Developers using these mechanisms need to be aware that their templates can not use any functions that have side effects from generating a view. Eg. a naive view counter that counts every time the footer is loaded will break. This is primarily something to keep in mind for documentation.

Let me know what you think! (Sorry for the lengthy pull request ;) ) If you want to merge, I'll write up proper documentation for using the cache.