Initial commit: apps.prometeysev.ru

This commit is contained in:
root committed 2026-10-22 00:22:57 +03:00
commit 96ccabcd8b
6561 files changed
+1270349

No files matched your search

@@ -0,0 +1,4 @@
phpunit.xml
composer.lock
composer.phar
/vendor/
+44
View File
@@ -0,0 +1,44 @@
language: php
matrix:
include:
- php: 5.6
dist: xenial
env: 'COMPOSER_FLAGS="--prefer-stable --prefer-lowest" TWIG_VERSION="^1.0"'
- php: 5.6
dist: xenial
env: TWIG_VERSION="^1.0"
- php: 7.0
dist: xenial
env: TWIG_VERSION="^1.0"
- php: 7.1
dist: bionic
env: TWIG_VERSION="^1.0"
- php: 7.2
dist: bionic
env: TWIG_VERSION="^1.0"
- php: 7.3
dist: bionic
env: TWIG_VERSION="^1.0"
- php: 7.4
dist: bionic
env: TWIG_VERSION="^1.0"
- php: 7.0
dist: xenial
env: TWIG_VERSION="^2.0"
- php: 7.1
dist: bionic
env: TWIG_VERSION="^2.0"
- php: 7.2
dist: bionic
env: TWIG_VERSION="^2.0"
- php: 7.3
dist: bionic
env: TWIG_VERSION="^2.0"
- php: 7.4
dist: bionic
env: TWIG_VERSION="^2.0"
install: composer require twig/twig:${TWIG_VERSION}
script: composer test
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2013 Alexander <iam.asm89@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+238
View File
@@ -0,0 +1,238 @@
Twig cache extension
====================
The missing cache extension for Twig. The extension allows for caching rendered parts of
templates using several cache strategies.
[![Build Status](https://secure.travis-ci.org/asm89/twig-cache-extension.png?branch=master)](http://travis-ci.org/asm89/twig-cache-extension)
## Installation
The extension is installable via composer:
```json
{
"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:
```php
<?php
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`:
```bash
composer require cache/apcu-adapter
```
```php
<?php
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:
```jinja
{% cache 'v1/summary' 900 %}
{# heavy lifting template stuff here, include/render other partials etc #}
{% endcache %}
```
Cache blocks can be nested:
```jinja
{% cache 'v1' 900 %}
{% for item in items %}
{% cache 'v1' item %}
{# ... #}
{% endcache %}
{% endfor %}
{% endcache %}
```
The annotation can also be an expression:
```jinja
{% 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"](#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:
```php
<?php
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.
```php
<?php
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:
```jinja
{% 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
```php
<?php
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.
```jinja
{# 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 <iam.asm89@gmail.com>
## License
twig-cache-extension is licensed under the MIT License - see the LICENSE file for details
+43
View File
@@ -0,0 +1,43 @@
{
"name": "asm89/twig-cache-extension",
"description": "Cache fragments of templates directly within Twig.",
"keywords": ["twig", "cache", "extension"],
"homepage": "https://github.com/asm89/twig-cache-extension",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Alexander",
"email": "iam.asm89@gmail.com"
}
],
"require": {
"php": ">=5.3.2",
"twig/twig": "^1.0|^2.0"
},
"require-dev": {
"phpunit/phpunit": "^5.0 || ^4.8.10",
"doctrine/cache": "~1.0"
},
"scripts": {
"test": "phpunit"
},
"suggest": {
"psr/cache-implementation": "To make use of PSR-6 cache implementation via PsrCacheAdapter."
},
"autoload": {
"psr-4": {
"": "lib/"
}
},
"autoload-dev": {
"psr-4": {
"": "test/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
}
}
@@ -0,0 +1,49 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\CacheProvider;
use Asm89\Twig\CacheExtension\CacheProviderInterface;
use Doctrine\Common\Cache\Cache;
/**
* Adapter class to use the cache classes provider by Doctrine.
*
* @author Alexander <iam.asm89@gmail.com>
*/
class DoctrineCacheAdapter implements CacheProviderInterface
{
private $cache;
/**
* @param Cache $cache
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
/**
* {@inheritDoc}
*/
public function fetch($key)
{
return $this->cache->fetch($key);
}
/**
* {@inheritDoc}
*/
public function save($key, $value, $lifetime = 0)
{
return $this->cache->save($key, $value, $lifetime);
}
}
@@ -0,0 +1,68 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\CacheProvider;
use Asm89\Twig\CacheExtension\CacheProviderInterface;
use Psr\Cache\CacheItemPoolInterface;
/**
* Adapter class to make extension interoperable with every PSR-6 adapter.
*
* @see http://php-cache.readthedocs.io/
*
* @author Rvanlaak <rvanlaak@gmail.com>
*/
class PsrCacheAdapter implements CacheProviderInterface
{
/**
* @var CacheItemPoolInterface
*/
private $cache;
/**
* @param CacheItemPoolInterface $cache
*/
public function __construct(CacheItemPoolInterface $cache)
{
$this->cache = $cache;
}
/**
* @param string $key
* @return mixed|false
*/
public function fetch($key)
{
// PSR-6 implementation returns null, CacheProviderInterface expects false
$item = $this->cache->getItem($key);
if ($item->isHit()) {
return $item->get();
}
return false;
}
/**
* @param string $key
* @param string $value
* @param int|\DateInterval $lifetime
* @return bool
*/
public function save($key, $value, $lifetime = 0)
{
$item = $this->cache->getItem($key);
$item->set($value);
$item->expiresAfter($lifetime);
return $this->cache->save($item);
}
}
@@ -0,0 +1,36 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension;
/**
* Cache provider interface.
*
* @author Alexander <iam.asm89@gmail.com>
*/
interface CacheProviderInterface
{
/**
* @param string $key
*
* @return mixed False, if there was no value to be fetched. Null or a string otherwise.
*/
public function fetch($key);
/**
* @param string $key
* @param string $value
* @param integer $lifetime
*
* @return boolean
*/
public function save($key, $value, $lifetime = 0);
}
@@ -0,0 +1,53 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\CacheStrategy;
use Asm89\Twig\CacheExtension\CacheStrategyInterface;
/**
* CacheStrategy which doesn't cache at all
*
* This strategy can be used in development mode, e.g. editing twig templates,
* to prevent previously cached versions from being rendered.
*
* @see https://github.com/asm89/twig-cache-extension/pull/29
*
* @author Hagen Hübel <hhuebel@itinance.com>
*
* @package Asm89\Twig\CacheExtension\CacheStrategy
*/
class BlackholeCacheStrategy implements CacheStrategyInterface
{
/**
* {@inheritDoc}
*/
public function fetchBlock($key)
{
return false;
}
/**
* {@inheritDoc}
*/
public function generateKey($annotation, $value)
{
return microtime(true) . mt_rand();
}
/**
* {@inheritDoc}
*/
public function saveBlock($key, $block)
{
// fire and forget
}
}
@@ -0,0 +1,79 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\CacheStrategy;
use Asm89\Twig\CacheExtension\CacheProviderInterface;
use Asm89\Twig\CacheExtension\CacheStrategyInterface;
use Asm89\Twig\CacheExtension\Exception\InvalidCacheKeyException;
/**
* 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.
*
* @see http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
*
* @author Alexander <iam.asm89@gmail.com>
*/
class GenerationalCacheStrategy implements CacheStrategyInterface
{
private $keyGenerator;
private $cache;
private $lifetime;
/**
* @param CacheProviderInterface $cache
* @param KeyGeneratorInterface $keyGenerator
* @param integer $lifetime
*/
public function __construct(CacheProviderInterface $cache, KeyGeneratorInterface $keyGenerator, $lifetime = 0)
{
$this->keyGenerator = $keyGenerator;
$this->cache = $cache;
$this->lifetime = $lifetime;
}
/**
* {@inheritDoc}
*/
public function fetchBlock($key)
{
return $this->cache->fetch($key);
}
/**
* {@inheritDoc}
*/
public function generateKey($annotation, $value)
{
$key = $this->keyGenerator->generateKey($value);
if (null === $key) {
throw new InvalidCacheKeyException();
}
return $annotation . '__GCS__' . $key;
}
/**
* {@inheritDoc}
*/
public function saveBlock($key, $block)
{
return $this->cache->save($key, $block, $this->lifetime);
}
}
@@ -0,0 +1,76 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\CacheStrategy;
use Asm89\Twig\CacheExtension\CacheStrategyInterface;
use Asm89\Twig\CacheExtension\Exception\NonExistingStrategyException;
use Asm89\Twig\CacheExtension\Exception\NonExistingStrategyKeyException;
/**
* Combines several configured cache strategies.
*
* Useful for combining for example generational cache strategy with a lifetime
* cache strategy, but also useful when combining several generational cache
* strategies which differ on cache lifetime (infinite, 1hr, 5m).
*
* @author Alexander <iam.asm89@gmail.com>
*/
class IndexedChainingCacheStrategy implements CacheStrategyInterface
{
/**
* @var CacheStrategyInterface[]
*/
private $strategies;
/**
* @param array $strategies
*/
public function __construct(array $strategies)
{
$this->strategies = $strategies;
}
/**
* {@inheritDoc}
*/
public function fetchBlock($key)
{
return $this->strategies[$key['strategyKey']]->fetchBlock($key['key']);
}
/**
* {@inheritDoc}
*/
public function generateKey($annotation, $value)
{
if (!is_array($value) || null === $strategyKey = key($value)) {
throw new NonExistingStrategyKeyException();
}
if (!isset($this->strategies[$strategyKey])) {
throw new NonExistingStrategyException($strategyKey);
}
return array(
'strategyKey' => $strategyKey,
'key' => $this->strategies[$strategyKey]->generateKey($annotation, current($value)),
);
}
/**
* {@inheritDoc}
*/
public function saveBlock($key, $block)
{
return $this->strategies[$key['strategyKey']]->saveBlock($key['key'], $block);
}
}
@@ -0,0 +1,29 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\CacheStrategy;
/**
* Generates a key for a given value.
*
* @author Alexander <iam.asm89@gmail.com>
*/
interface KeyGeneratorInterface
{
/**
* Generate a cache key for a given value.
*
* @param mixed $value
*
* @return string
*/
public function generateKey($value);
}
@@ -0,0 +1,68 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\CacheStrategy;
use Asm89\Twig\CacheExtension\CacheProviderInterface;
use Asm89\Twig\CacheExtension\CacheStrategyInterface;
use Asm89\Twig\CacheExtension\Exception\InvalidCacheLifetimeException;
/**
* Strategy for caching with a pre-defined lifetime.
*
* The value passed to the strategy is the lifetime of the cache block in
* seconds.
*
* @author Alexander <iam.asm89@gmail.com>
*/
class LifetimeCacheStrategy implements CacheStrategyInterface
{
private $cache;
/**
* @param CacheProviderInterface $cache
*/
public function __construct(CacheProviderInterface $cache)
{
$this->cache = $cache;
}
/**
* {@inheritDoc}
*/
public function fetchBlock($key)
{
return $this->cache->fetch($key['key']);
}
/**
* {@inheritDoc}
*/
public function generateKey($annotation, $value)
{
if (!is_numeric($value)) {
throw new InvalidCacheLifetimeException($value);
}
return array(
'lifetime' => $value,
'key' => '__LCS__' . $annotation,
);
}
/**
* {@inheritDoc}
*/
public function saveBlock($key, $block)
{
return $this->cache->save($key['key'], $block, $key['lifetime']);
}
}
@@ -0,0 +1,49 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension;
/**
* Cache strategy interface.
*
* @author Alexander <iam.asm89@gmail.com>
*/
interface CacheStrategyInterface
{
/**
* Fetch the block for a given key.
*
* @param mixed $key
*
* @return mixed
*/
public function fetchBlock($key);
/**
* Generate a key for the value.
*
* @param string $annotation
* @param mixed $value
*
* @return mixed
*/
public function generateKey($annotation, $value);
/**
* Save the contents of a rendered block.
*
* @param mixed $key
* @param string $block
*
* @return mixed
*/
public function saveBlock($key, $block);
}
@@ -0,0 +1,20 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\Exception;
/**
* @todo Replace \RuntimeException with \InvalidArgumentException at version 2.0
*/
class BaseException extends \RuntimeException
{
}
@@ -0,0 +1,23 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\Exception;
class InvalidCacheKeyException extends BaseException
{
/**
* {@inheritdoc}
*/
public function __construct($message = 'Key generator did not return a key.', $code = 0, \Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
@@ -0,0 +1,23 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\Exception;
class InvalidCacheLifetimeException extends BaseException
{
/**
* {@inheritdoc}
*/
public function __construct($lifetime, $code = 0, \Exception $previous = null)
{
parent::__construct(sprintf('Value "%s" is not a valid lifetime.', gettype($lifetime)), $code, $previous);
}
}
@@ -0,0 +1,23 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\Exception;
class NonExistingStrategyException extends BaseException
{
/**
* {@inheritdoc}
*/
public function __construct($strategyKey, $code = 0, \Exception $previous = null)
{
parent::__construct(sprintf('No strategy configured with key "%s".', $strategyKey), $code, $previous);
}
}
@@ -0,0 +1,23 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\Exception;
class NonExistingStrategyKeyException extends BaseException
{
/**
* {@inheritdoc}
*/
public function __construct($message = 'No strategy key found in value.', $code = 0, \Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
@@ -0,0 +1,59 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension;
/**
* Extension for caching template blocks with twig.
*
* @author Alexander <iam.asm89@gmail.com>
*/
class Extension extends \Twig_Extension
{
private $cacheStrategy;
/**
* @param CacheStrategyInterface $cacheStrategy
*/
public function __construct(CacheStrategyInterface $cacheStrategy)
{
$this->cacheStrategy = $cacheStrategy;
}
/**
* @return CacheStrategyInterface
*/
public function getCacheStrategy()
{
return $this->cacheStrategy;
}
/**
* {@inheritDoc}
*/
public function getName()
{
if (version_compare(\Twig_Environment::VERSION, '1.26.0', '>=')) {
return __CLASS__;
}
return 'asm89_cache';
}
/**
* {@inheritDoc}
*/
public function getTokenParsers()
{
return array(
new TokenParser\Cache(),
);
}
}
@@ -0,0 +1,71 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\Node;
/**
* Cache twig node.
*
* @author Alexander <iam.asm89@gmail.com>
*/
class CacheNode extends \Twig_Node
{
private static $cacheCount = 1;
/**
* @param \Twig_Node_Expression $annotation
* @param \Twig_Node_Expression $keyInfo
* @param \Twig_NodeInterface $body
* @param integer $lineno
* @param string $tag
*/
public function __construct(\Twig_Node_Expression $annotation, \Twig_Node_Expression $keyInfo, \Twig_Node $body, $lineno, $tag = null)
{
parent::__construct(array('key_info' => $keyInfo, 'body' => $body, 'annotation' => $annotation), array(), $lineno, $tag);
}
/**
* {@inheritDoc}
*/
public function compile(\Twig_Compiler $compiler)
{
$i = self::$cacheCount++;
if (version_compare(\Twig_Environment::VERSION, '1.26.0', '>=')) {
$extension = 'Asm89\Twig\CacheExtension\Extension';
} else {
$extension = 'asm89_cache';
}
$compiler
->addDebugInfo($this)
->write("\$asm89CacheStrategy".$i." = \$this->env->getExtension('{$extension}')->getCacheStrategy();\n")
->write("\$asm89Key".$i." = \$asm89CacheStrategy".$i."->generateKey(")
->subcompile($this->getNode('annotation'))
->raw(", ")
->subcompile($this->getNode('key_info'))
->write(");\n")
->write("\$asm89CacheBody".$i." = \$asm89CacheStrategy".$i."->fetchBlock(\$asm89Key".$i.");\n")
->write("if (\$asm89CacheBody".$i." === false) {\n")
->indent()
->write("ob_start();\n")
->indent()
->subcompile($this->getNode('body'))
->outdent()
->write("\n")
->write("\$asm89CacheBody".$i." = ob_get_clean();\n")
->write("\$asm89CacheStrategy".$i."->saveBlock(\$asm89Key".$i.", \$asm89CacheBody".$i.");\n")
->outdent()
->write("}\n")
->write("echo \$asm89CacheBody".$i.";\n")
;
}
}
@@ -0,0 +1,59 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\TokenParser;
use Asm89\Twig\CacheExtension\Node\CacheNode;
/**
* Parser for cache/endcache blocks.
*
* @author Alexander <iam.asm89@gmail.com>
*/
class Cache extends \Twig_TokenParser
{
/**
* @param \Twig_Token $token
*
* @return boolean
*/
public function decideCacheEnd(\Twig_Token $token)
{
return $token->test('endcache');
}
/**
* {@inheritDoc}
*/
public function getTag()
{
return 'cache';
}
/**
* {@inheritDoc}
*/
public function parse(\Twig_Token $token)
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$annotation = $this->parser->getExpressionParser()->parseExpression();
$key = $this->parser->getExpressionParser()->parseExpression();
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
$body = $this->parser->subparse(array($this, 'decideCacheEnd'), true);
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
return new CacheNode($annotation, $key, $body, $lineno, $this->getTag());
}
}
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="test/bootstrap.php"
>
<testsuites>
<testsuite name="Twig Cache Extension Test Suite">
<directory>./test/Asm89/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">./lib/</directory>
</whitelist>
</filter>
</phpunit>
@@ -0,0 +1,46 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\Tests\CacheProvider;
use Asm89\Twig\CacheExtension\CacheProvider\DoctrineCacheAdapter;
class DoctrineCacheAdapterTest extends \PHPUnit_Framework_TestCase
{
public function testFetch()
{
$cacheMock = $this->createCacheMock();
$cacheMock->expects($this->any())
->method('fetch')
->will($this->returnValue('fromcache'));
$cache = new DoctrineCacheAdapter($cacheMock);
$this->assertEquals('fromcache', $cache->fetch('test'));
}
public function testSave()
{
$cacheMock = $this->createCacheMock();
$cacheMock->expects($this->once())
->method('save')
->with('key', 'value', 42);
$cache = new DoctrineCacheAdapter($cacheMock);
$cache->save('key', 'value', 42);
}
public function createCacheMock()
{
return $this->createMock('Doctrine\Common\Cache\Cache');
}
}
@@ -0,0 +1,80 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\Tests\CacheStrategy;
use Asm89\Twig\CacheExtension\CacheStrategy\GenerationalCacheStrategy;
class GenerationalCacheStrategyTest extends \PHPUnit_Framework_TestCase
{
private $keyGeneratorMock;
private $cacheProviderMock;
public function createCacheStrategy($lifetime = 0)
{
$this->keyGeneratorMock = $this->createKeyGeneratorMock();
$this->cacheProviderMock = $this->createCacheProviderMock();
return new GenerationalCacheStrategy($this->cacheProviderMock, $this->keyGeneratorMock, $lifetime);
}
public function testGenerateKeyContainsAnnotation()
{
$strategy = $this->createCacheStrategy();
$this->keyGeneratorMock->expects($this->any())
->method('generateKey')
->will($this->returnValue('foo'));
$this->assertEquals('v42__GCS__foo', $strategy->generateKey('v42', 'value'));
}
/**
* @expectedException \Asm89\Twig\CacheExtension\Exception\InvalidCacheKeyException
*/
public function testGenerationKeyThrowsExceptionWhenKeyGeneratorReturnsNull()
{
$strategy = $this->createCacheStrategy();
$strategy->generateKey('v42', 'value');
}
/**
* @dataProvider getLifeTimes
*/
public function testSaveBlockUsesConfiguredLifetime($lifetime)
{
$strategy = $this->createCacheStrategy($lifetime);
$this->cacheProviderMock->expects($this->any())
->method('save')
->with('key', 'value', $lifetime)
->will($this->returnValue('foo'));
$strategy->saveBlock('key', 'value');
}
public function getLifetimes()
{
return array(
array(0),
array(60),
);
}
public function createKeyGeneratorMock()
{
return $this->createMock('Asm89\Twig\CacheExtension\CacheStrategy\KeyGeneratorInterface');
}
public function createCacheProviderMock()
{
return $this->createMock('Asm89\Twig\CacheExtension\CacheProviderInterface');
}
}
@@ -0,0 +1,80 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\Tests\CacheStrategy;
use Asm89\Twig\CacheExtension\CacheStrategy\IndexedChainingCacheStrategy;
class IndexedChainingCacheStrategyTest extends \PHPUnit_Framework_TestCase
{
private $cacheStrategyMocks = array();
public function createCacheStrategy()
{
foreach($this->getStrategies() as $config) {
list($key, $return) = $config;
$cacheStrategyMock = $this->createCacheStrategyMock();
$cacheStrategyMock->expects($this->any())
->method('generateKey')
->will($this->returnValue($return));
$this->cacheStrategyMocks[$key] = $cacheStrategyMock;
}
return new IndexedChainingCacheStrategy($this->cacheStrategyMocks);
}
/**
* @dataProvider getStrategies
*/
public function testGenerateKeyProxiesToAppropriateStrategy($key, $return)
{
$strategy = $this->createCacheStrategy();
$generatedKey = $strategy->generateKey('v42', array($key => 'proxied_value'));
$this->assertEquals($return, $generatedKey['key']);
$this->assertEquals($key, $generatedKey['strategyKey']);
}
/**
* @expectedException \Asm89\Twig\CacheExtension\Exception\NonExistingStrategyKeyException
*/
public function testGenerateKeyThrowsExceptionOnMissingKey()
{
$strategy = $this->createCacheStrategy();
$strategy->generateKey('v42', 'proxied_value');
}
/**
* @expectedException \Asm89\Twig\CacheExtension\Exception\NonExistingStrategyException
* @expectedExceptionMessage No strategy configured with key "unknown"
*/
public function testGenerateKeyThrowsExceptionOnUnknownKey()
{
$strategy = $this->createCacheStrategy();
$strategy->generateKey('v42', array('unknown' => 'proxied_value'));
}
public function getStrategies()
{
return array(
array('foo', 'foo_key'),
array('bar', 'bar_key'),
);
}
public function createCacheStrategyMock()
{
return $this->createMock('Asm89\Twig\CacheExtension\CacheStrategyInterface');
}
}
@@ -0,0 +1,68 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\Tests\CacheStrategy;
use Asm89\Twig\CacheExtension\CacheStrategy\LifetimeCacheStrategy;
class LifetimeCacheStrategyTest extends \PHPUnit_Framework_TestCase
{
private $cacheProviderMock;
public function createCacheStrategy()
{
$this->cacheProviderMock = $this->createCacheProviderMock();
return new LifetimeCacheStrategy($this->cacheProviderMock);
}
public function testGenerateKeyUsesGivenLifetime()
{
$strategy = $this->createCacheStrategy();
$key = $strategy->generateKey('v42', 42);
$this->assertEquals(42, $key['lifetime']);
}
public function testGenerateKeyAnnotatesKey()
{
$strategy = $this->createCacheStrategy();
$key = $strategy->generateKey('the_annotation', 42);
$this->assertContains('the_annotation', $key['key']);
}
/**
* @dataProvider getInvalidLifetimeValues
* @expectedException \Asm89\Twig\CacheExtension\Exception\InvalidCacheLifetimeException
*/
public function testGenerateKeyThrowsExceptionWhenNoLifetimeProvided($value)
{
$strategy = $this->createCacheStrategy();
$strategy->generateKey('v42', $value);
}
public function getInvalidLifetimeValues()
{
return array(
array('foo'),
array(array('foo')),
);
}
public function createCacheProviderMock()
{
return $this->createMock('Asm89\Twig\CacheExtension\CacheProviderInterface');
}
}
@@ -0,0 +1,182 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Twig\CacheExtension\Tests;
use Asm89\Twig\CacheExtension\CacheProvider\DoctrineCacheAdapter;
use Asm89\Twig\CacheExtension\CacheStrategy\KeyGeneratorInterface;
use Asm89\Twig\CacheExtension\CacheStrategy\GenerationalCacheStrategy;
use Asm89\Twig\CacheExtension\CacheStrategy\IndexedChainingCacheStrategy;
use Asm89\Twig\CacheExtension\CacheStrategy\LifetimeCacheStrategy;
use Asm89\Twig\CacheExtension\Extension;
use Doctrine\Common\Cache\ArrayCache;
use Twig_Loader_Filesystem;
use Twig_Environment;
class FunctionalExtensionTest extends \PHPUnit_Framework_TestCase
{
private $cache;
protected function createCacheProvider()
{
$this->cache = new ArrayCache();
return new DoctrineCacheAdapter($this->cache);
}
protected function createCacheStrategy($name = null)
{
$cacheProvider = $this->createCacheProvider();
$keyGenerator = $this->createKeyGenerator();
$lifetime = 0;
switch ($name) {
case 'time':
return new LifetimeCacheStrategy($cacheProvider);
case 'indexed':
return new IndexedChainingCacheStrategy(array(
'gcs' => new GenerationalCacheStrategy($cacheProvider, $keyGenerator, $lifetime),
'time' => new LifetimeCacheStrategy($cacheProvider),
));
default:
return new GenerationalCacheStrategy($cacheProvider, $keyGenerator, $lifetime);
}
}
protected function createKeyGenerator()
{
return new KeyGenerator();
}
protected function createTwig($cacheStrategyName = null)
{
$loader = new Twig_Loader_Filesystem(__DIR__ . '/fixtures/');
$twig = new Twig_Environment($loader);
$cacheExtension = new Extension($this->createCacheStrategy($cacheStrategyName));
$twig->addExtension($cacheExtension);
return $twig;
}
protected function getValue($value, $updatedAt)
{
return new Value($value, $updatedAt);
}
public function testCachesWithSameCacheKey()
{
$twig = $this->createTwig();
$rendered = $twig->render('gcs_value.twig', array('value' => $this->getValue('asm89', 1)));
$this->assertEquals('Hello asm89!', $rendered);
$rendered2 = $twig->render('gcs_value.twig', array('value' => $this->getValue('fabpot', 1)));
$this->assertEquals('Hello asm89!', $rendered2);
}
public function testDifferentCacheOnDifferentAnnotation()
{
$twig = $this->createTwig();
$rendered = $twig->render('gcs_value.twig', array('value' => $this->getValue('asm89', 1)));
$this->assertEquals('Hello asm89!', $rendered);
$rendered2 = $twig->render('gcs_value.twig', array('value' => $this->getValue('fabpot', 1)));
$this->assertEquals('Hello asm89!', $rendered2);
$rendered3 = $twig->render('gcs_value_v2.twig', array('value' => $this->getValue('fabpot', 1)));
$this->assertEquals('Hello fabpot!', $rendered3);
}
public function testLifetimeCacheStrategy()
{
$twig = $this->createTwig('time');
$rendered = $twig->render('lcs_value.twig', array('value' => $this->getValue('asm89', 1)));
$this->assertEquals('Hello asm89!', $rendered);
$rendered2 = $twig->render('lcs_value.twig', array('value' => $this->getValue('fabpot', 1)));
$this->assertEquals('Hello asm89!', $rendered2);
$this->cache->flushAll();
$rendered3 = $twig->render('lcs_value.twig', array('value' => $this->getValue('fabpot', 1)));
$this->assertEquals('Hello fabpot!', $rendered3);
}
public function testIndexedChainingStrategy()
{
$twig = $this->createTwig('indexed');
$rendered = $twig->render('ics_value.twig', array('value' => $this->getValue('asm89', 1)));
$this->assertEquals('Hello asm89!', $rendered);
$rendered2 = $twig->render('ics_value.twig', array('value' => $this->getValue('fabpot', 1)));
$this->assertEquals('Hello asm89!', $rendered2);
$this->cache->flushAll();
$rendered3 = $twig->render('ics_value.twig', array('value' => $this->getValue('fabpot', 1)));
$this->assertEquals('Hello fabpot!', $rendered3);
}
/**
* @expectedException Twig_Error_Runtime
* @expectedExceptionMessage An exception has been thrown during the rendering of a template ("No strategy key found in value.")
*/
public function testIndexedChainingStrategyNeedsKey()
{
$twig = $this->createTwig('indexed');
$twig->render('ics_no_key.twig', array('value' => $this->getValue('asm89', 1)));
}
public function testAnnotationExpression()
{
$twig = $this->createTwig('indexed');
$rendered = $twig->render('annotation_expression.twig', array('value' => $this->getValue('asm89', 1), 'version' => 1));
$this->assertEquals('Hello asm89!Hello asm89!', $rendered);
}
}
class KeyGenerator implements KeyGeneratorInterface
{
public function generateKey($value)
{
return get_class($value) . '_' . $value->getUpdatedAt();
}
}
class Value
{
private $value;
private $updatedAt;
public function __construct($value, $updatedAt)
{
$this->value = $value;
$this->updatedAt = $updatedAt;
}
public function getUpdatedAt()
{
return $this->updatedAt;
}
public function __toString()
{
return $this->value;
}
}
@@ -0,0 +1,3 @@
{%- set k = "v'" ~ version -%}
{%- cache k {time: 10} -%}Hello {{ value }}!{%- endcache -%}
{%- cache 'bob'|capitalize {gcs: value} %}Hello {{ value }}!{%- endcache -%}
@@ -0,0 +1,2 @@
{% set annotation = 'v1' %}
{% cache annotation value %}Hello {{ value }}!{% endcache %}
@@ -0,0 +1 @@
{% cache 'v1' value %}Hello {{ value }}!{% endcache %}
@@ -0,0 +1 @@
{% cache 'v2' value %}Hello {{ value }}!{% endcache %}
@@ -0,0 +1 @@
{% cache 'v1' 10 %}Hello {{ value }}!{% endcache %}
@@ -0,0 +1 @@
{% cache 'v1' {time: 10} %}Hello {{ value }}!{% endcache %}
@@ -0,0 +1 @@
{% cache 'v1' 10 %}Hello {{ value }}!{% endcache %}
+16
View File
@@ -0,0 +1,16 @@
<?php
/*
* This file is part of twig-cache-extension.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (file_exists($file = __DIR__.'/../vendor/autoload.php')) {
$autoload = require_once $file;
} else {
throw new RuntimeException('Install dependencies to run test suite.');
}