API Use symfony/cache (fixes #6252)

This commit is contained in:
Ingo Schommer 2017-02-24 08:39:57 +13:00
parent 84ee2c1448
commit d220ca3f67
24 changed files with 535 additions and 481 deletions

View File

@ -1,6 +1,6 @@
<?php
use SilverStripe\Core\Cache;
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Dev\Deprecation;
use SilverStripe\View\Parsers\ShortcodeParser;
@ -32,12 +32,6 @@ ShortcodeParser::get('regenerator')
// @todo
// ->register('dbfile_link', array('DBFile', 'handle_shortcode'))
// Zend_Cache temp directory setting
$_ENV['TMPDIR'] = TEMP_FOLDER; // for *nix
$_ENV['TMP'] = TEMP_FOLDER; // for Windows
Cache::set_cache_lifetime('GDBackend_Manipulations', null, 100);
// If you don't want to see deprecation errors for the new APIs, change this to 3.2.0-dev.
Deprecation::notification_version('3.2.0');

22
_config/cache.yml Normal file
View File

@ -0,0 +1,22 @@
---
Name: corecache
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Core\Cache\CacheFactory:
class: 'SilverStripe\Core\Cache\DefaultCacheFactory'
constructor:
directory: `TEMP_FOLDER`
Psr\SimpleCache\CacheInterface.GDBackend_Manipulations:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: "GDBackend_Manipulations"
defaultLifetime: 100
Psr\SimpleCache\CacheInterface.cacheblock:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: "cacheblock"
defaultLifetime: 600
Psr\SimpleCache\CacheInterface.LeftAndMain_CMSVersion:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: "LeftAndMain_CMSVersion"

View File

@ -17,7 +17,7 @@ use SilverStripe\Control\Controller;
use SilverStripe\Control\PjaxResponseNegotiator;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Cache;
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\Deprecation;
@ -2020,9 +2020,9 @@ class LeftAndMain extends Controller implements PermissionProvider
// Tries to obtain version number from composer.lock if it exists
$composerLockPath = BASE_PATH . '/composer.lock';
if (file_exists($composerLockPath)) {
$cache = Cache::factory('LeftAndMain_CMSVersion');
$cacheKey = filemtime($composerLockPath);
$versions = $cache->load($cacheKey);
$cache = Injector::inst()->get(CacheInterface::class . '.LeftAndMain_CMSVersion');
$cacheKey = (string)filemtime($composerLockPath);
$versions = $cache->get($cacheKey);
if ($versions) {
$versions = json_decode($versions, true);
} else {
@ -2038,7 +2038,7 @@ class LeftAndMain extends Controller implements PermissionProvider
$versions[$package->name] = $package->version;
}
}
$cache->save(json_encode($versions), $cacheKey);
$cache->set($cacheKey, json_encode($versions));
}
}
}

View File

@ -23,6 +23,7 @@
"symfony/yaml": "~2.7",
"embed/embed": "^2.6",
"swiftmailer/swiftmailer": "~5.4",
"symfony/cache": "^3.3@dev",
"symfony/config": "^2.8",
"symfony/translation": "^2.8",
"vlucas/phpdotenv": "^2.4"

View File

@ -30,8 +30,7 @@ When we render `$Counter` to the template we would expect the value to increase
## Partial caching
Partial caching is a feature that allows the caching of just a portion of a page. Instead of fetching the required data
from the database to display, the contents of the area are fetched from the `TEMP_FOLDER` file-system pre-rendered and
ready to go. More information about Partial caching is in the [Performance](../performance) guide.
from the database to display, the contents of the area are fetched from a [cache backend](../performance/caching).
:::ss
<% cached 'MyCachedContent', LastEdited %>

View File

@ -1,10 +1,10 @@
# Caching
## Built-In Caches
## Overview
The framework uses caches to store infrequently changing values.
By default, the storage mechanism is simply the filesystem, although
other cache backends can be configured. All caches use the [api:Cache] API.
By default, the storage mechanism chooses the most performant adapter available
(PHP7 opcache, APC, or filesystem). Other cache backends can be configured.
The most common caches are manifests of various resources:
@ -21,136 +21,170 @@ executing the action is limited to the following cases when performed via a web
* A user is logged in with ADMIN permissions
* An error occurs during startup
## The Cache API
## Configuration
The [api:Cache] class provides a bunch of static functions wrapping the Zend_Cache system
in something a little more easy to use with the SilverStripe config system.
We are using the [PSR-16](http://www.php-fig.org/psr/psr-16/) standard ("SimpleCache")
for caching, through the [symfony/cache](https://symfony.com/doc/current/components/cache.html) library.
Note that this library describes usage of [PSR-6](http://www.php-fig.org/psr/psr-6/) by default,
but also exposes caches following the PSR-16 interface.
A `Zend_Cache` has both a frontend (determines how to get the value to cache,
and how to serialize it for storage) and a backend (handles the actual
storage).
Cache objects are configured via YAML
and SilverStripe's [dependency injection](/developer-guides/extending/injector) system.
Rather than require library code to specify the backend directly, cache
consumers provide a name for the cache backend they want. The end developer
can then specify which backend to use for each name in their project's
configuration. They can also use 'all' to provide a backend for all named
caches.
:::yml
SilverStripe\Core\Injector\Injector:
Psr\SimpleCache\CacheInterface.myCache:
factory: SilverStripe\Core\Cache\CacheFactory
constructor:
namespace: "myCache"
End developers provide a set of named backends, then pick the specific
backend for each named cache. There is a default File cache set up as the
'default' named backend, which is assigned to 'all' named caches.
Cache objects are instantiated through a [CacheFactory](SilverStripe\Core\Cache\CacheFactory),
which determines which cache adapter is used (see "Adapters" below for details).
This factory allows us you to globally define an adapter for all cache instances.
## Using Caches
:::php
use Psr\SimpleCache\CacheInterface
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
Caches can be created and retrieved through the `Cache::factory()` method.
The returned object is of type `Zend_Cache`.
Caches are namespaced, which might allow granular clearing of a particular cache without affecting others.
In our example, the namespace is "myCache", expressed in the service name as
`Psr\SimpleCache\CacheInterface.myCache`. We recommend the `::class` short-hand to compose the full service name.
Clearing caches by namespace is dependant on the used adapter: While the `FilesystemCache` adapter clears only the namespaced cache,
a `MemcachedCache` adapter will clear all caches regardless of namespace, since the underlying memcached
service doesn't support this. See "Invalidation" for alternative strategies.
## Usage
Cache objects follow the [PSR-16](http://www.php-fig.org/psr/psr-16/) class interface.
:::php
// foo is any name (try to be specific), and is used to get configuration
// & storage info
$cache = Cache::factory('foo');
if (!($result = $cache->load($cachekey))) {
$result = caluate some how;
$cache->save($result, $cachekey);
}
return $result;
use Psr\SimpleCache\CacheInterface
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
Normally there's no need to remove things from the cache - the cache
backends clear out entries based on age and maximum allocated storage. If you
include the version of the object in the cache key, even object changes
don't need any invalidation. You can force disable the cache though,
e.g. in development mode.
// create a new item by trying to get it from the cache
$myValue = $cache->get('myCacheKey');
// set a value and save it via the adapter
$cache->set('myCacheKey', 1234);
// retrieve the cache item
if (!$cache->has('myCacheKey')) {
// ... item does not exists in the cache
}
## Invalidation
:::php
// Disables all caches
Cache::set_cache_lifetime('any', -1, 100);
Caches can be invalidated in different ways. The easiest is to actively clear the
entire cache. If the adapter supports namespaced cache clearing,
this will only affect a subset of cache keys ("myCache" in this example):
You can also specifically clean a cache.
Keep in mind that `Zend_Cache::CLEANING_MODE_ALL` deletes all cache
entries across all caches, not just for the 'foo' cache in the example below.
:::php
use Psr\SimpleCache\CacheInterface
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
// remove all items in this (namespaced) cache
$cache->clear();
You can also delete a single item based on it's cache key:
:::php
$cache = Cache::factory('foo');
$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
:::php
use Psr\SimpleCache\CacheInterface
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
// remove the cache item
$cache->delete('myCacheKey');
A single element can be invalidated through its cache key.
Individual cache items can define a lifetime, after which the cached value is marked as expired:
:::php
$cache = Cache::factory('foo');
$cache->remove($cachekey);
:::php
use Psr\SimpleCache\CacheInterface
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
// remove the cache item
$cache->set('myCacheKey', 'myValue', 300); // cache for 300 seconds
If a lifetime isn't defined on the `set()` call, it'll use the adapter default.
In order to increase the chance of your cache actually being hit,
it often pays to increase the lifetime of caches ("TTL").
It defaults to 10 minutes (600s) in SilverStripe, which can be
quite short depending on how often your data changes.
Keep in mind that data expiry should primarily be handled by your cache key,
e.g. by including the `LastEdited` value when caching `DataObject` results.
it often pays to increase the lifetime of caches.
You can also set your lifetime to `0`, which means they won't expire.
Since many adapters don't have a way to actively remove expired caches,
you need to be careful with resources here (e.g. filesystem space).
:::php
// set all caches to 3 hours
Cache::set_cache_lifetime('any', 60*60*3);
:::yml
SilverStripe\Core\Injector\Injector:
Psr\SimpleCache\CacheInterface.cacheblock:
constructor:
defaultLifetime: 3600
## Alternative Cache Backends
In most cases, invalidation and expiry should be handled by your cache key.
For example, including the `LastEdited` value when caching `DataObject` results
will automatically create a new cache key when the object has been changed.
The following example caches a member's group names, and automatically
creates a new cache key when any group is edited. Depending on the used adapter,
old cache keys will be garbage collected as the cache fills up.
By default, SilverStripe uses a file-based caching backend.
Together with a file stat cache like [APC](http://us2.php.net/manual/en/book.apc.php)
this is reasonably quick, but still requires access to slow disk I/O.
The `Zend_Cache` API supports various caching backends ([list](http://framework.zend.com/manual/1.12/en/zend.cache.backends.html))
which can provide better performance, including APC, Xcache, ZendServer, Memcached and SQLite.
:::php
use Psr\SimpleCache\CacheInterface
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
// Automatically changes when any group is edited
$cacheKey = implode(['groupNames', $member->ID, Groups::get()->max('LastEdited')]);
$cache->set($cacheKey, $member->Groups()->column('Title'));
## Cleaning caches on flush=1 requests
If `?flush=1` is requested in the URL, this will trigger a call to `flush()` on
any classes that implement the [Flushable](/developer_guides/execution_pipeline/flushable/)
interface. Use this interface to trigger `clear()` on your caches.
If `?flush=1` is requested in the URL, e.g. http://mysite.com?flush=1, this will trigger a call to `flush()` on
any classes that implement the `Flushable` interface. Using this, you can trigger your caches to clean.
## Adapters
See [reference documentation on Flushable](/developer_guides/execution_pipeline/flushable/) for implementation details.
SilverStripe tries to identify the most performant cache available on your system
through the [DefaultCacheFactory](api:SilverStripe\Core\Cache\DefaultCacheFactory) implementation:
### Memcached
* - `PhpFilesCache` (PHP 5.6 or PHP 7 with [opcache](http://php.net/manual/en/book.opcache.php) enabled).
This cache has relatively low [memory defaults](http://php.net/manual/en/opcache.configuration.php#ini.opcache.memory-consumption).
We recommend increasing it for large applications, or enabling the
[file_cache fallback](http://php.net/manual/en/opcache.configuration.php#ini.opcache.file-cache)
* - `ApcuCache` (requires APC) with a `FilesystemCache` fallback (for larger cache volumes)
* - `FilesystemCache` if none of the above is available
The library supports various [cache adapters](https://github.com/symfony/cache/tree/master/Simple)
which can provide better performance, particularly in multi-server environments with shared caches like Memcached.
This backends stores cache records into a [memcached](http://www.danga.com/memcached/)
server. memcached is a high-performance, distributed memory object caching system.
To use this backend, you need a memcached daemon and the memcache PECL extension.
Since we're using dependency injection to create caches,
you need to define a factory for a particular adapter,
following the `SilverStripe\Core\Cache\CacheFactory` interface.
Different adapters will require different constructor arguments.
We've written factories for the most common cache scenarios:
`FilesystemCacheFactory`, `MemcachedCacheFactory` and `ApcuCacheFactory`.
:::php
// _config.php
Cache::add_backend(
'primary_memcached',
'Memcached',
array(
'servers' => array(
'host' => 'localhost',
'port' => 11211,
'persistent' => true,
'weight' => 1,
'timeout' => 5,
'retry_interval' => 15,
'status' => true,
'failure_callback' => null
)
)
);
Cache::pick_backend('primary_memcached', 'any', 10);
Example: Configure core caches to use [memcached](http://www.danga.com/memcached/),
which requires the [memcached PHP extension](http://php.net/memcached),
and takes a `MemcachedClient` instance as a constructor argument.
### APC
:::yml
---
After:
- '#corecache'
---
SilverStripe\Core\Injector\Injector:
MemcachedClient:
class: 'Memcached'
calls:
- [ addServer, [ 'localhost', 11211 ] ]
SilverStripe\Core\Cache\CacheFactory:
class: 'SilverStripe\Core\Cache\MemcachedCacheFactory'
constructor:
client: '%$MemcachedClient
This backends stores cache records in shared memory through the [APC](http://pecl.php.net/package/APC)
(Alternative PHP Cache) extension (which is of course need for using this backend).
## Additional Caches
:::php
Cache::add_backend('primary_apc', 'APC');
Cache::pick_backend('primary_apc', 'any', 10);
Unfortunately not all caches are configurable via cache adapters.
### Two-Levels
This backend is an hybrid one. It stores cache records in two other backends:
a fast one (but limited) like Apc, Memcache... and a "slow" one like File or Sqlite.
:::php
Cache::add_backend('two_level', 'Two-Levels', array(
'slow_backend' => 'File',
'fast_backend' => 'APC',
'slow_backend_options' => array(
'cache_dir' => TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache'
)
));
Cache::pick_backend('two_level', 'any', 10);
* [SSViewer](api:SilverStripe\View\SSViewer) writes compiled templates as PHP files to the filesystem
(in order to achieve opcode caching on `include()` calls)
* [ConfigManifest](api:SilverStripe\Core\Manifest\ConfigManifest) is hardcoded to use `FilesystemCache`
* [ClassManifest](api:SilverStripe\Core\Manifest\ClassManifest) and [ThemeManifest](api:SilverStripe\View\ThemeManifest)
are using a custom `ManifestCache`
* [i18n](api:SilverStripe\i18n\i18n) uses `Symfony\Component\Config\ConfigCacheFactoryInterface` (filesystem-based)

View File

@ -16,6 +16,7 @@ guide developers in preparing existing 3.x code for compatibility with 4.0
* [Filesystem API](#overview-filesystem)
* [Template and Form API](#overview-template)
* [i18n](#overview-i18n)
* [Cache](#overview-cache)
* [Email and Mailer](#overview-mailer)
* [Commit History](#commit-history)
@ -49,6 +50,7 @@ guide developers in preparing existing 3.x code for compatibility with 4.0
* Themes are now configured to cascade, where you can specify a list of themes, and have the template engine
search programatically through a prioritised list when resolving template and CSS file paths.
* i18n Updated to use symfony/translation over zend Framework 1. Zend_Translate has been removed.
* Replaced `Zend_Cache` and the `Cache` API with a PSR-16 implementation (symfony/cache)
* _ss_environment.php files have been removed in favour of `.env` and "real" environment variables.
## <a name="upgrading"></a>Upgrading
@ -1473,6 +1475,93 @@ New `TimeField` methods replace `getConfig()` / `setConfig()`
* `i18n::get_common_locales()` removed.
* `i18n.common_locales` config removed
### <a name="overview-cache"></a>Cache API
We have replaced the unsupported `Zend_Cache` library with [symfony/cache](https://github.com/symfony/cache).
This also allowed us to remove SilverStripe's `Cache` API and use dependency injection with a standard
[PSR-16](http://www.php-fig.org/psr/psr-16/) cache interface instead.
#### Usage Changes
Caches should be retrieved through `Injector` instead of `Cache::factory()`,
and have a slightly different API (e.g. `set()` instead of `save()`).
Before:
:::php
$cache = Cache::factory('myCache');
// create a new item by trying to get it from the cache
$myValue = $cache->load('myCacheKey');
// set a value and save it via the adapter
$cache->save(1234, 'myCacheKey');
// retrieve the cache item
if (!$cache->load('myCacheKey')) {
// ... item does not exists in the cache
}
// Remove a cache key
$cache->remove('myCacheKey');
After:
:::php
use Psr\SimpleCache\CacheInterface;
$cache = Injector::inst()->get(CacheInterface::class . '.myCache');
// create a new item by trying to get it from the cache
$myValue = $cache->get('myCacheKey');
// set a value and save it via the adapter
$cache->set('myCacheKey', 1234);
// retrieve the cache item
if (!$cache->has('myCacheKey')) {
// ... item does not exists in the cache
}
$cache->delete('myCacheKey');
#### Configuration Changes
Caches are now configured through dependency injection services instead of PHP.
See our ["Caching" docs](/developer-guides/performance/caching) for more details.
Before (`mysite/_config.php`):
:::php
Cache::add_backend(
'primary_memcached',
'Memcached',
array(
'servers' => array(
'host' => 'localhost',
'port' => 11211,
)
)
);
Cache::pick_backend('primary_memcached', 'any', 10);
After (`mysite/_config/config.yml`):
:::yml
---
After:
- '#corecache'
---
SilverStripe\Core\Injector\Injector:
MemcachedClient:
class: 'Memcached'
calls:
- [ addServer, [ 'localhost', 11211 ] ]
SilverStripe\Core\Cache\CacheFactory:
class: 'SilverStripe\Core\Cache\MemcachedCacheFactory'
constructor:
client: '%$MemcachedClient
### <a name="overview-mailer"></a>Email and Mailer
#### <a name="overview-mailer-api"></a>Email Additions / Changes

View File

@ -4,12 +4,11 @@ namespace SilverStripe\Assets;
use SilverStripe\Assets\Storage\AssetContainer;
use SilverStripe\Assets\Storage\AssetStore;
use SilverStripe\Core\Cache;
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Core\Object;
use SilverStripe\Core\Flushable;
use InvalidArgumentException;
use Zend_Cache;
use Zend_Cache_Core;
/**
* A wrapper class for GD-based images, with lots of manipulation functions.
@ -25,7 +24,7 @@ class GDBackend extends Object implements Image_Backend, Flushable
protected $gd;
/**
* @var Zend_Cache_Core
* @var \Psr\SimpleCache\CacheInterface
*/
protected $cache;
@ -66,7 +65,7 @@ class GDBackend extends Object implements Image_Backend, Flushable
public function __construct(AssetContainer $assetContainer = null)
{
parent::__construct();
$this->cache = Cache::factory('GDBackend_Manipulations');
$this->cache = Injector::inst()->get(CacheInterface::class . '.GDBackend_Manipulations');
if ($assetContainer) {
$this->loadFromContainer($assetContainer);
@ -219,7 +218,7 @@ class GDBackend extends Object implements Image_Backend, Flushable
public function failedResample($arg = null)
{
$key = sha1(implode('|', func_get_args()));
return (bool)$this->cache->load($key);
return (bool)$this->cache->get($key);
}
/**
@ -259,7 +258,7 @@ class GDBackend extends Object implements Image_Backend, Flushable
protected function markFailed($arg = null)
{
$key = sha1(implode('|', func_get_args()));
$this->cache->save('1', $key);
$this->cache->set($key, '1');
}
/**
@ -270,7 +269,7 @@ class GDBackend extends Object implements Image_Backend, Flushable
protected function markSucceeded($arg = null)
{
$key = sha1(implode('|', func_get_args()));
$this->cache->save('0', $key);
$this->cache->set($key, '0');
}
@ -762,8 +761,7 @@ class GDBackend extends Object implements Image_Backend, Flushable
public static function flush()
{
// Clear factory
$cache = Cache::factory('GDBackend_Manipulations');
$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
$cache = Injector::inst()->get(CacheInterface::class . '.GDBackend_Manipulations');
$cache->clear();
}
}

View File

@ -1,201 +0,0 @@
<?php
namespace SilverStripe\Core;
use Zend_Cache;
use Zend_Cache_Core;
/**
* The `[api:Cache]` class provides a bunch of static functions wrapping the Zend_Cache system
* in something a little more easy to use with the SilverStripe config system.
*
* @see https://docs.silverstripe.org/en/3.4/developer_guides/performance/caching/
*/
class Cache
{
/**
* @var array $backends
*/
protected static $backends = array();
/**
* @var array $backend_picks
*/
protected static $backend_picks = array();
/**
* @var array $cache_lifetime
*/
protected static $cache_lifetime = array();
/**
* Initialize the 'default' named cache backend.
*/
protected static function init()
{
if (!isset(self::$backends['default'])) {
$cachedir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache';
if (!is_dir($cachedir)) {
mkdir($cachedir);
}
/** @skipUpgrade */
self::$backends['default'] = array(
'File',
array(
'cache_dir' => $cachedir
)
);
self::$cache_lifetime['default'] = array(
'lifetime' => 600,
'priority' => 1
);
}
}
/**
* Add a new named cache backend.
*
* @see http://framework.zend.com/manual/en/zend.cache.html
*
* @param string $name The name of this backend as a freeform string
* @param string $type The Zend_Cache backend ('File' or 'Sqlite' or ...)
* @param array $options The Zend_Cache backend options
*/
public static function add_backend($name, $type, $options = array())
{
self::init();
self::$backends[$name] = array($type, $options);
}
/**
* Pick a named cache backend for a particular named cache.
*
* The priority call with the highest number will be the actual backend
* picked. A backend picked for a specific cache name will always be used
* instead of 'any' if it exists, no matter the priority.
*
* @param string $name The name of the backend, as passed as the first argument to add_backend
* @param string $for The name of the cache to pick this backend for (or 'any' for any backend)
* @param integer $priority The priority of this pick
*/
public static function pick_backend($name, $for, $priority = 1)
{
self::init();
$current = -1;
if (isset(self::$backend_picks[$for])) {
$current = self::$backend_picks[$for]['priority'];
}
if ($priority >= $current) {
self::$backend_picks[$for] = array(
'name' => $name,
'priority' => $priority
);
}
}
/**
* Return the cache lifetime for a particular named cache.
*
* @param string $for
*
* @return string
*/
public static function get_cache_lifetime($for)
{
if (isset(self::$cache_lifetime[$for])) {
return self::$cache_lifetime[$for];
}
return null;
}
/**
* Set the cache lifetime for a particular named cache
*
* @param string $for The name of the cache to set this lifetime for (or 'any' for all backends)
* @param integer $lifetime The lifetime of an item of the cache, in seconds, or -1 to disable caching
* @param integer $priority The priority. The highest priority setting is used. Unlike backends, 'any' is not
* special in terms of priority.
*/
public static function set_cache_lifetime($for, $lifetime = 600, $priority = 1)
{
self::init();
$current = -1;
if (isset(self::$cache_lifetime[$for])) {
$current = self::$cache_lifetime[$for]['priority'];
}
if ($priority >= $current) {
self::$cache_lifetime[$for] = array(
'lifetime' => $lifetime,
'priority' => $priority
);
}
}
/**
* Build a cache object.
*
* @see http://framework.zend.com/manual/en/zend.cache.html
*
* @param string $for The name of the cache to build
* @param string $frontend (optional) The type of Zend_Cache frontend
* @param array $frontendOptions (optional) Any frontend options to use.
* @return Zend_Cache_Core The cache object
*/
public static function factory($for, $frontend = 'Output', $frontendOptions = null)
{
self::init();
$backend_name = 'default';
$backend_priority = -1;
$cache_lifetime = self::$cache_lifetime['default']['lifetime'];
$lifetime_priority = -1;
foreach (array('any', $for) as $name) {
if (isset(self::$backend_picks[$name])) {
if (self::$backend_picks[$name]['priority'] > $backend_priority) {
$backend_name = self::$backend_picks[$name]['name'];
$backend_priority = self::$backend_picks[$name]['priority'];
}
}
if (isset(self::$cache_lifetime[$name])) {
if (self::$cache_lifetime[$name]['priority'] > $lifetime_priority) {
$cache_lifetime = self::$cache_lifetime[$name]['lifetime'];
$lifetime_priority = self::$cache_lifetime[$name]['priority'];
}
}
}
$backend = self::$backends[$backend_name];
$basicOptions = array('cache_id_prefix' => $for);
if ($cache_lifetime >= 0) {
$basicOptions['lifetime'] = $cache_lifetime;
} else {
$basicOptions['caching'] = false;
}
$frontendOptions = $frontendOptions ? array_merge($basicOptions, $frontendOptions) : $basicOptions;
require_once 'Zend/Cache.php';
return Zend_Cache::factory(
$frontend,
$backend[0],
$frontendOptions,
$backend[1]
);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace SilverStripe\Core\Cache;
use SilverStripe\Core\Injector\Injector;
use Symfony\Component\Cache\Simple\ApcuCache;
use Memcached;
class ApcuCacheFactory implements CacheFactory
{
/**
* @var string
*/
protected $version;
/**
* @param string $version
*/
public function __construct($version = null)
{
$this->version = $version;
}
/**
* @inheritdoc
*/
public function create($service, array $params = array())
{
return Injector::inst()->create(ApcuCache::class, false, [
(isset($args['namespace'])) ? $args['namespace'] : '',
(isset($args['defaultLifetime'])) ? $args['defaultLifetime'] : 0,
$this->version
]);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace SilverStripe\Core\Cache;
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Injector\Factory as InjectorFactory;
interface CacheFactory extends InjectorFactory
{
/**
* Note: While the returned object is used as a singleton (by the originating Injector->get() call),
* this cache object shouldn't be a singleton itself - it has varying constructor args for the same service name.
*
* @param string $class
* @param array $args
* @return CacheInterface
*/
public function create($service, array $params = array());
}

View File

@ -0,0 +1,79 @@
<?php
namespace SilverStripe\Core\Cache;
use SilverStripe\Core\Injector\Injector;
use Symfony\Component\Cache\Simple\FilesystemCache;
use Symfony\Component\Cache\Simple\ApcuCache;
use Symfony\Component\Cache\Simple\ChainCache;
use Symfony\Component\Cache\Simple\PhpFilesCache;
use Symfony\Component\Cache\Adapter\ApcuAdapter;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
/**
* Returns the most performant combination of caches available on the system:
* - `PhpFilesCache` (PHP 7 with opcache enabled)
* - `ApcuCache` (requires APC) with a `FilesystemCache` fallback (for larger cache volumes)
* - `FilesystemCache` if none of the above is available
*
* Modelled after `Symfony\Component\Cache\Adapter\AbstractAdapter::createSystemCache()`
*/
class DefaultCacheFactory implements CacheFactory
{
/**
* @var string Absolute directory path
*/
protected $directory;
/**
* @var string APC version for apcu_add()
*/
protected $version;
/**
* @param string $directory
* @param string $version
*/
public function __construct($directory, $version = null)
{
$this->directory = $directory;
$this->version = $version;
}
/**
* @inheritdoc
*/
public function create($service, array $params = array())
{
$namespace = (isset($args['namespace'])) ? $args['namespace'] : '';
$defaultLifetime = (isset($args['defaultLifetime'])) ? $args['defaultLifetime'] : 0;
$version = $this->version;
$directory = $this->directory;
$apcuSupported = null;
$phpFilesSupported = null;
if (null === $apcuSupported) {
$apcuSupported = ApcuAdapter::isSupported();
}
if (!$apcuSupported && null === $phpFilesSupported) {
$phpFilesSupported = PhpFilesAdapter::isSupported();
}
if ($phpFilesSupported) {
$opcache = Injector::inst()->create(PhpFilesCache::class, false, [$namespace, $defaultLifetime, $directory]);
return $opcache;
}
$fs = Injector::inst()->create(FilesystemCache::class, false, [$namespace, $defaultLifetime, $directory]);
if (!$apcuSupported) {
return $fs;
}
$apcu = Injector::inst()->create(ApcuCache::class, false, [$namespace, (int) $defaultLifetime / 5, $version]);
return Injector::inst()->create(ChainCache::class, false, [[$apcu, $fs]]);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace SilverStripe\Core\Cache;
use SilverStripe\Core\Injector\Injector;
use Symfony\Component\Cache\Simple\FilesystemCache;
class FilesystemCacheFactory implements CacheFactory
{
/**
* @var string Absolute directory path
*/
protected $directory;
/**
* @param string $directory
*/
public function __construct($directory)
{
$this->directory = $directory;
}
/**
* @inheritdoc
*/
public function create($service, array $params = array())
{
return Injector::inst()->create(FilesystemCache::class, false, [
(isset($args['namespace'])) ? $args['namespace'] : '',
(isset($args['defaultLifetime'])) ? $args['defaultLifetime'] : 0,
$this->directory
]);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace SilverStripe\Core\Cache;
use SilverStripe\Core\Injector\Injector;
use Symfony\Component\Cache\Simple\MemcachedCache;
use Memcached;
class MemcachedCacheFactory implements CacheFactory
{
/**
* @var Memcached
*/
protected $memcachedClient;
/**
* @param Memcached $memcachedClient
*/
public function __construct(Memcached $memcachedClient)
{
$this->memcachedClient = $memcachedClient;
}
/**
* @inheritdoc
*/
public function create($service, array $params = array())
{
return Injector::inst()->create(MemcachedCache::class, false, [
$this->memcachedClient,
(isset($args['namespace'])) ? $args['namespace'] : '',
(isset($args['defaultLifetime'])) ? $args['defaultLifetime'] : 0
]);
}
}

View File

@ -54,14 +54,6 @@ mb_regex_encoding('UTF-8');
*/
gc_enable();
/**
* Include the Zend autoloader. This will be removed in the near future.
*/
if (file_exists('thirdparty/Zend/Loader/Autoloader.php')) {
require_once 'thirdparty/Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::getInstance();
}
// Initialise the dependency injector as soon as possible, as it is
// subsequently used by some of the following code
$injector = new Injector(array('locator' => 'SilverStripe\\Core\\Injector\\SilverStripeServiceConfigurationLocator'));

View File

@ -7,10 +7,9 @@ use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\DAG;
use SilverStripe\Core\Config\DAG_CyclicException;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Cache;
use Symfony\Component\Cache\Simple\FilesystemCache;
use Symfony\Component\Yaml\Parser;
use Traversable;
use Zend_Cache_Core;
/**
* A utility class which builds a manifest of configuration items
@ -28,7 +27,7 @@ class ConfigManifest
protected $includeTests;
/**
* @var Zend_Cache_Core
* @var FilesystemCache
*/
protected $cache;
@ -114,15 +113,15 @@ class ConfigManifest
$this->key = sha1($base).'_';
$this->includeTests = $includeTests;
// Get the Zend Cache to load/store cache into
// Get a cache singleton
$this->cache = $this->getCache();
// Unless we're forcing regen, try loading from cache
if (!$forceRegen) {
// The PHP config sources are always needed
$this->phpConfigSources = $this->cache->load($this->key.'php_config_sources');
$this->phpConfigSources = $this->cache->get($this->key.'php_config_sources');
// Get the variant key spec
$this->variantKeySpec = $this->cache->load($this->key.'variant_key_spec');
$this->variantKeySpec = $this->cache->get($this->key.'variant_key_spec');
}
// If we don't have a variantKeySpec (because we're forcing regen, or it just wasn't in the cache), generate it
@ -136,14 +135,12 @@ class ConfigManifest
/**
* Provides a hook for mock unit tests despite no DI
* @return Zend_Cache_Core
* @return \Psr\SimpleCache\CacheInterface
*/
protected function getCache()
{
return Cache::factory('SS_Configuration', 'Core', array(
'automatic_serialization' => true,
'lifetime' => null
));
// TODO Replace with CoreConfigCreator, see https://github.com/silverstripe/silverstripe-framework/pull/6641/files#diff-f8c9b17e06432278197a7d5c3a1043cb
return new FilesystemCache('SS_Configuration', 0, getTempFolder());
}
/**
@ -264,9 +261,9 @@ class ConfigManifest
$this->buildVariantKeySpec();
if ($cache) {
$this->cache->save($this->phpConfigSources, $this->key.'php_config_sources');
$this->cache->save($this->yamlConfigFragments, $this->key.'yaml_config_fragments');
$this->cache->save($this->variantKeySpec, $this->key.'variant_key_spec');
$this->cache->set($this->key.'php_config_sources', $this->phpConfigSources);
$this->cache->set($this->key.'yaml_config_fragments', $this->yamlConfigFragments);
$this->cache->set($this->key.'variant_key_spec', $this->variantKeySpec);
}
}
@ -650,12 +647,12 @@ class ConfigManifest
// given variant is stale compared to the complete set of fragments
if (!$this->yamlConfigFragments) {
// First try and just load the exact variant
if ($this->yamlConfig = $this->cache->load($this->key.'yaml_config_'.$this->variantKey())) {
if ($this->yamlConfig = $this->cache->get($this->key.'yaml_config_'.$this->variantKey())) {
$this->yamlConfigVariantKey = $this->variantKey();
return;
} // Otherwise try and load the fragments so we can build the variant
else {
$this->yamlConfigFragments = $this->cache->load($this->key.'yaml_config_fragments');
$this->yamlConfigFragments = $this->cache->get($this->key.'yaml_config_fragments');
}
}
@ -684,7 +681,7 @@ class ConfigManifest
}
if ($cache) {
$this->cache->save($this->yamlConfig, $this->key.'yaml_config_'.$this->variantKey());
$this->cache->set($this->key.'yaml_config_'.$this->variantKey(), $this->yamlConfig);
}
// Since yamlConfig has changed, call any callbacks that are interested

View File

@ -743,10 +743,10 @@ class SSTemplateParser extends Parser implements TemplateParser
// Get any condition
$condition = isset($res['condition']) ? $res['condition'] : '';
$res['php'] .= 'if ('.$condition.'($partial = $cache->load('.$key.'))) $val .= $partial;' . PHP_EOL;
$res['php'] .= 'if ('.$condition.'($partial = $cache->get('.$key.'))) $val .= $partial;' . PHP_EOL;
$res['php'] .= 'else { $oldval = $val; $val = "";' . PHP_EOL;
$res['php'] .= $sub['php'] . PHP_EOL;
$res['php'] .= $condition . ' $cache->save($val); $val = $oldval . $val;' . PHP_EOL;
$res['php'] .= $condition . ' $cache->set('.$key.', $val); $val = $oldval . $val;' . PHP_EOL;
$res['php'] .= '}';
}

View File

@ -4020,10 +4020,10 @@ class SSTemplateParser extends Parser implements TemplateParser
// Get any condition
$condition = isset($res['condition']) ? $res['condition'] : '';
$res['php'] .= 'if ('.$condition.'($partial = $cache->load('.$key.'))) $val .= $partial;' . PHP_EOL;
$res['php'] .= 'if ('.$condition.'($partial = $cache->get('.$key.'))) $val .= $partial;' . PHP_EOL;
$res['php'] .= 'else { $oldval = $val; $val = "";' . PHP_EOL;
$res['php'] .= $sub['php'] . PHP_EOL;
$res['php'] .= $condition . ' $cache->save($val); $val = $oldval . $val;' . PHP_EOL;
$res['php'] .= $condition . ' $cache->set('.$key.', $val); $val = $oldval . $val;' . PHP_EOL;
$res['php'] .= '}';
}

View File

@ -4,7 +4,7 @@ namespace SilverStripe\View;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Cache;
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Convert;
use SilverStripe\Core\Flushable;
use SilverStripe\Core\Injector\Injector;
@ -14,9 +14,6 @@ use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\Security\Permission;
use InvalidArgumentException;
use Zend_Cache_Backend_ExtendedInterface;
use Zend_Cache;
use Zend_Cache_Core;
/**
* Parses a template file with an *.ss file extension.
@ -466,17 +463,8 @@ class SSViewer implements Flushable
public static function flush_cacheblock_cache($force = false)
{
if (!self::$cacheblock_cache_flushed || $force) {
$cache = Cache::factory('cacheblock');
$backend = $cache->getBackend();
if ($backend instanceof Zend_Cache_Backend_ExtendedInterface
&& ($capabilities = $backend->getCapabilities())
&& $capabilities['tags']
) {
$cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, $cache->getTags());
} else {
$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
}
$cache = Injector::inst()->get(CacheInterface::class . '.cacheblock');
$cache->clear();
self::$cacheblock_cache_flushed = true;
@ -484,14 +472,14 @@ class SSViewer implements Flushable
}
/**
* @var Zend_Cache_Core
* @var CacheInterface
*/
protected $partialCacheStore = null;
/**
* Set the cache object to use when storing / retrieving partial cache blocks.
*
* @param Zend_Cache_Core $cache
* @param CacheInterface $cache
*/
public function setPartialCacheStore($cache)
{
@ -501,11 +489,11 @@ class SSViewer implements Flushable
/**
* Get the cache object to use when storing / retrieving partial cache blocks.
*
* @return Zend_Cache_Core
* @return CacheInterface
*/
public function getPartialCacheStore()
{
return $this->partialCacheStore ? $this->partialCacheStore : Cache::factory('cacheblock');
return $this->partialCacheStore ? $this->partialCacheStore : Injector::inst()->get(CacheInterface::class . '.cacheblock');
}
/**

View File

@ -3,7 +3,8 @@
namespace SilverStripe\Assets\Tests;
use SilverStripe\Assets\GDBackend;
use SilverStripe\Core\Cache;
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest;
/**
@ -192,9 +193,9 @@ class GDTest extends SapphireTest
$gd->loadFrom($fullPath);
// Cache should refer to this file
$cache = Cache::factory('GDBackend_Manipulations');
$cache = Injector::inst()->get(CacheInterface::class . '.GDBackend_Manipulations');
$key = sha1(implode('|', array($fullPath, filemtime($fullPath))));
$data = $cache->load($key);
$data = $cache->get($key);
$this->assertEquals('1', $data);
}

View File

@ -1,77 +0,0 @@
<?php
namespace SilverStripe\Core\Tests;
use SilverStripe\Core\Cache;
use SilverStripe\Dev\SapphireTest;
class CacheTest extends SapphireTest
{
public function testCacheBasics()
{
$cache = Cache::factory('test');
$cache->save('Good', 'cachekey');
$this->assertEquals('Good', $cache->load('cachekey'));
}
public function testCacheCanBeDisabled()
{
Cache::set_cache_lifetime('test', -1, 10);
$cache = Cache::factory('test');
$cache->save('Good', 'cachekey');
$this->assertFalse($cache->load('cachekey'));
}
public function testCacheLifetime()
{
Cache::set_cache_lifetime('test', 0.5, 20);
$cache = Cache::factory('test');
$this->assertEquals(0.5, $cache->getOption('lifetime'));
$cache->save('Good', 'cachekey');
$this->assertEquals('Good', $cache->load('cachekey'));
// As per documentation, sleep may not sleep for the amount of time you tell it to sleep for
// This loop can make sure it *does* sleep for that long
$endtime = time() + 2;
while (time() < $endtime) {
// Sleep for another 2 seconds!
// This may end up sleeping for 4 seconds, but it's awwwwwwwright.
sleep(2);
}
$this->assertFalse($cache->load('cachekey'));
}
public function testCacheSeperation()
{
$cache1 = Cache::factory('test1');
$cache2 = Cache::factory('test2');
$cache1->save('Foo', 'cachekey');
$cache2->save('Bar', 'cachekey');
$this->assertEquals('Foo', $cache1->load('cachekey'));
$this->assertEquals('Bar', $cache2->load('cachekey'));
$cache1->remove('cachekey');
$this->assertFalse($cache1->load('cachekey'));
$this->assertEquals('Bar', $cache2->load('cachekey'));
}
public function testCacheDefault()
{
Cache::set_cache_lifetime('default', 1200);
$default = Cache::get_cache_lifetime('default');
$this->assertEquals(1200, $default['lifetime']);
$cache = Cache::factory('somethingnew');
$this->assertEquals(1200, $cache->getOption('lifetime'));
}
}

View File

@ -7,7 +7,7 @@ use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Manifest\ConfigManifest;
use SilverStripe\Dev\SapphireTest;
use ReflectionProperty;
use Zend_Cache_Core;
use Symfony\Component\Cache\Simple\ArrayCache;
class ConfigManifestTest extends SapphireTest
{
@ -34,13 +34,13 @@ class ConfigManifestTest extends SapphireTest
/**
* A helper method to return a mock of the cache in order to test expectations and reduce dependency
*
* @return Zend_Cache_Core
* @return \PHPUnit_Framework_MockObject_MockObject
*/
protected function getCacheMock()
{
return $this->getMock(
'Zend_Cache_Core',
array('load', 'save'),
ArrayCache::class,
array('set', 'get'),
array(),
'',
false
@ -51,7 +51,7 @@ class ConfigManifestTest extends SapphireTest
* A helper method to return a mock of the manifest in order to test expectations and reduce dependency
*
* @param $methods
* @return ConfigManifest
* @return \PHPUnit_Framework_MockObject_MockObject
*/
protected function getManifestMock($methods)
{
@ -82,7 +82,7 @@ class ConfigManifestTest extends SapphireTest
// Set up a cache where we expect load to never be called
$cache = $this->getCacheMock();
$cache->expects($this->never())
->method('load');
->method('get');
$manifest->expects($this->any())
->method('getCache')
@ -95,7 +95,7 @@ class ConfigManifestTest extends SapphireTest
$cache = $this->getCacheMock();
$cache->expects($this->atLeastOnce())
->method('save');
->method('set');
$manifest->expects($this->any())
->method('getCache')
@ -119,7 +119,7 @@ class ConfigManifestTest extends SapphireTest
// Load should be called twice
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('load');
->method('get');
$manifest->expects($this->any())
->method('getCache')
@ -133,7 +133,7 @@ class ConfigManifestTest extends SapphireTest
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('load')
->method('get')
->will($this->onConsecutiveCalls(false, false));
$manifest->expects($this->any())
@ -151,7 +151,7 @@ class ConfigManifestTest extends SapphireTest
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('load')
->method('get')
->will($this->onConsecutiveCalls(array(), array()));
$manifest->expects($this->any())
@ -186,7 +186,7 @@ class ConfigManifestTest extends SapphireTest
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->will($this->returnValue(false))
->method('load');
->method('get');
$manifest->expects($this->any())
->method('getCache')
@ -204,7 +204,7 @@ class ConfigManifestTest extends SapphireTest
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('load')
->method('get')
->will($this->returnCallback(function ($parameter) {
if (strpos($parameter, 'variant_key_spec') !== false) {
return false;
@ -227,7 +227,7 @@ class ConfigManifestTest extends SapphireTest
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('load')
->method('get')
->will($this->returnCallback(function ($parameter) {
if (strpos($parameter, 'php_config_sources') !== false) {
return false;

View File

@ -5,8 +5,8 @@ namespace SilverStripe\ORM\Tests;
require_once __DIR__ . "/ImageTest.php";
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Cache;
use Zend_Cache;
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Injector\Injector;
class GDImageTest extends ImageTest
{
@ -32,8 +32,9 @@ class GDImageTest extends ImageTest
public function tearDown()
{
$cache = Cache::factory('GDBackend_Manipulations');
$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
$cache = Injector::inst()->get(CacheInterface::class . '.GDBackend_Manipulations');
$cache->clear();
parent::tearDown();
}
}

View File

@ -2,11 +2,14 @@
namespace SilverStripe\View\Tests;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\Versioning\Versioned;
use SilverStripe\Core\Cache;
use Psr\SimpleCache\CacheInterface;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Control\Director;
use SilverStripe\View\SSViewer;
use Symfony\Component\Cache\Simple\FilesystemCache;
use Symfony\Component\Cache\Simple\NullCache;
// Not actually a data object, we just want a ViewableData object that's just for us
@ -27,8 +30,15 @@ class SSViewerCacheBlockTest extends SapphireTest
{
$this->data = new SSViewerCacheBlockTest\TestModel();
Cache::factory('cacheblock')->clean();
Cache::set_cache_lifetime('cacheblock', $cacheOn ? 600 : -1);
$cache = null;
if ($cacheOn) {
$cache = new FilesystemCache('cacheblock', 0, getTempFolder()); // cache indefinitely
} else {
$cache = new NullCache();
}
Injector::inst()->registerService($cache, CacheInterface::class . '.cacheblock');
Injector::inst()->get(CacheInterface::class . '.cacheblock')->clear();
}
protected function _runtemplate($template, $data = null)