Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SS4 caching: update Zend_Cache or PSR-6? #6252

Closed
11 tasks done
chillu opened this issue Oct 31, 2016 · 39 comments
Closed
11 tasks done

SS4 caching: update Zend_Cache or PSR-6? #6252

chillu opened this issue Oct 31, 2016 · 39 comments
Assignees

Comments

@chillu
Copy link
Member

chillu commented Oct 31, 2016

UPDATE: Ticket description has been extended/replaced by @chillu on 10th of Feb

Tasks

Goals

  • Replace outdated Zend_Cache implementation
  • Use a standards-based implementation (PSR-6 or PSR-16)
  • Minimise upgrade pain (changes to non-core cache uses)
  • Retain or improve cache access and write performance

Scope

  • Required
    • PSR-6/PSR-16: Store data in a cache pools
    • PSR-6/PSR-16: Store and retrieve common data types via cache keys
    • PSR-6/PSR-16: Define cache expiry on each item
    • PSR-6/PSR-16: Batched cache writes
    • PSR-6/PSR-16: Hit/miss detection
    • PSR-6/PSR-16: Invalidate all caches in a pool
    • Define named cache pools
    • Configure cache drivers (e.g. connection details)
    • Support different drivers
    • Configure through YAML (custom code can modify core caching behaviour)
    • Use for SilverStripe manifests (prior to YAML config being available)
  • Desireable
    • Configure cache drivers per cache pool
    • Cascading cache drivers
    • Stampede Protection
    • Cache Key Grouping
    • Support for at least one driver which can be shared between distributed webservers (e.g. memcache)
    • PSR-6: Deferred cache writes (separate commit phase)
    • Maintenance: Active cache purging (e.g. through cronjobs for filesystem caches)
  • Out of scope
    • Cache introspection (track hits/misses, list cached items)

Usage Comparison

// SS_Cache
$cache = SS_Cache::factory('myPool'); 
$itemValue = $cache->load('myKey');
$cache->save('myVal', 'myKey');

// PSR-6
$cache = new MyCacheAdapter();
$item = $cache->getItem('myKey');
$itemValue = $item->get();
$item->set('myVal');
$cache->save($item);

// PSR-16
$cache = new MyCacheAdapter();
$itemValue = $cache->get('myKey');
$cache->set('myKey', 'myVal');

Libraries

  • Symfony Cache
    • Both PSR-6 and PSR-16 (via adapters)
    • Dependencies: psr/cache, psr/log, psr/simple-cache
    • 4645 NCLOC
    • 290k composer installs
    • 26 github stars (it's a subtree split, expect less stars)
    • Tag-based cache invalidation (only with some adapters)
    • Cascading drivers through ChainAdapter
    • PSR-16 use is not documented
  • StashPHP
    • PSR-6
    • Dependencies: psr/cache
    • 3041 NCLOC
    • 600k composer installs
    • 731 github stars
    • Stampede protection (early cache regeneration, locking)
    • Cascading drivers through Composite
  • Doctrine Cache
    • Not PSR-6
  • Scrapbook Cash
    • PSR-6 and PSR-16
    • Dependencies: psr/cache, psr/simple-cache
    • 32k composer installs
    • 180 github stars
    • Stampede Protection

Options

Option 1a: Replace SS_Cache with a PSR-6 library

  • Pro: Standardization
  • Pro: Maximum flexibility (e.g. batch/deferred cache key writes)
  • Pro: Larger choice of libraries
  • Con: Slightly more code for standard cache operations
  • Con: Upgrade pain
  • Con: Directly exposing a PSR-6 library makes us dependant on additional APIs exposed by them (such as lock() in stashphp)

Option 1b: Adapt SS_Cache to use a PSR-6 library internally

  • Pro: Less upgrade pain
  • Con: API doesn't cleanly translate (no distinction between frontend/backend, or frontendOptions parameters)

Option 2a: Replace SS_Cache with a PSR-16 library ("simplecache")

  • Pro: Less code for standard cache operations
  • Con: Less library choice
  • Con: Upgrade pain

Option 2b: Adapt SS_Cache with a PSR-16 library internally

  • Pro: Less upgrade pain
  • Con: No point using a simplified cache API if it's only used "behind the scenes"
  • Con: API doesn't cleanly translate (same as Option 1b)

Option 2c: Refactor SS_Cache to become a PSR-16 library

  • Con: More code to maintain

Configuration

The majority of cached should be configured through YAML
(apart from the class and config manifest caches which need to be in place before YAML can be parsed).

This configuration leverages the SilverStripe dependency injection system
for configuring instances, alongside service names like Cache.driver.memcached.
The configuration assumes PSR-6 (referring to "pools"), but could be adapted to PSR-16.

Before:

_config.php

SS_Cache::add_backend(
    'filesystem', 
    'Filesystem',
    array(
        'path' => TEMP_FOLDER
    )
);
SS_Cache::pick_backend('filesystem', 'i18n', 10);
SS_Cache::set_cache_lifetime('i18n', 300);

SS_Cache::add_backend(
    'memcached', 
    'Memcached',
    array(
        'servers' => array(
            'host' => 'localhost', 
            'port' => 11211, 
        )
    )
);
SS_Cache::pick_backend('memcached', 'cacheblock', 10);

After:

config.yml

SilverStripe\Core\Injector\Injector:
  Cache.driver.default:
    class: 'My\Library\Drivers\Filesystem'
    constructor:
      path: `TEMP_FOLDER`
  Cache.driver.memcached:
    class: 'My\Library\Drivers\Memcached'
    constructor:
      host: localhost
      port: 11211
  Cache.pool.i18n:
    class: 'My\Library\CachePool'
    properties:
      driver: '%$Cache.driver.default' # optional, uses default
  Cache.pool.cacheblock:
    class: 'My\Library\CachePool'
    properties:
      driver: '%$Cache.driver.memcached'
i18n:
  cache_ttl: 300

Usage for class/config manifest (before YAML is available):

Core.php

$injector = new Injector(array('locator' => 'SilverStripe\\Core\\Injector\\SilverStripeServiceConfigurationLocator'));
Injector::set_inst($injector);

// new
if (file_exists('boot-cache.php')) {
  require('boot-cache.php');
}

$manifest = new ClassManifest(BASE_PATH, false, $flush);
// ClassManifest->getCache() calls Cache::factory('Cache.pool.classmanifest')

boot-cache.php

Injector::inst()->registerService(
  new \My\Library\Drivers\Memcache('localhost', 11211)
  'Cache.pool.classmanifest'
);

Note: Additional boot files could be handled in a more generic fashion,
this just aims to illustrate the principle (inject procedural code early during boot).
It's going to be different to handle this via environment variables alone,
since different drivers have different construtor arguments, each of which
would need to be reflected in environment variables.

Pool Management

In Option 1a and 2a, SilverStripe developers are expected to directly interact with
a PSR-6 or PSR-16 library.

Before:

$cache = Cache::factory('i18n', 'myFrontend', array(
    'automatic_serialization' => true,
    'lifetime' => null
));
$itemValue = $cache->load('myKey');

After (with PSR-6):

$cache = Cache::factory('i18n');
$item = $cache->getItem('myKey');
$itemValue = $item->get();

If different drivers ('frontends') could be set on the cache pool instance,
a direct choice via the factory() should be avoided.
Most use cases should be covered by configuring drivers and driver
options per cache pool definition, not per cache pool instance.

Notes

  • Terminology: Zend calls it a "backend", Symfony an "adapter", and Stash a "driver"
  • The silverstripe/cacheinclude module is an alternative to partial template caches, and uses doctrine/cache. A nice side effect of this library is that you can list and expire individual cache keys via a manager user interface
  • Batch/deferred cache key setting might become important for external cache backends (e.g. memcache), combined with granular cache use. Currently, @micmania1's config rewrite sets a cache key per config block, which would be extremely slow when performed without batching with memcached network requests. Both PSR-6 and PSR-16 support this.
  • Cache expirations wouldn't be configured for the whole the pool, since they need to be passed into each item in both PSR-6 (CacheItemInterface->expiresAfter()) and PSR-16 (CacheInterface->function set($key, $value, $ttl = null)). If APIs like i18n want to expose one global value, they should make it configurable in their own namespace (e.g. i18n.cache_ttl)
  • Without a wrapper around the PSR-6/PSR-16 libs, we can't inject additional behaviour
    like logging cache hits, or providing structured data about cache access to a developer toolbar. StashPHP has a setLogger() method
  • ircmaxells post about PSR caching is worth reading (some motivations for PSR-16): "As simple as possible, as complex as necessary."

Further reading

Related Issues

@chillu chillu added this to the CMS 4.0.0-alpha4 milestone Oct 31, 2016
@sminnee
Copy link
Member

sminnee commented Oct 31, 2016

I think we should switch to using PSR-6 for this, and then providing a default PSR-6 library as a framework dependency. This is what we did with PSR-3 / monolog, too.

I think that we should refactor SS_Cache to accept any PSR-6 implementation and ditch the Zend_Cache support.

@sminnee
Copy link
Member

sminnee commented Oct 31, 2016

This library seems to be one of the more popular PSR-6 implementations:
http://www.stashphp.com/

@sminnee sminnee changed the title Upgrade or replace Zend_Cache SS4 caching: update Zend_Cache or PSR-6? Oct 31, 2016
@kinglozzer
Copy link
Member

IIRC, one of the goals of simplecache was that it adds a thin API layer on top of PSR-6 to simplify it. Whether that’s something that the library would provide, I’m not sure - but I don’t think the two standards are mutually exclusive/competing.

+1 for PSR-6.

@sminnee
Copy link
Member

sminnee commented Oct 31, 2016

Another implementation is symfony/cache, but stash seems to be more popular (https://packagist.org/providers/psr/cache-implementation)

@tractorcow
Copy link
Contributor

👍 for simplecache over psr-6

@sminnee
Copy link
Member

sminnee commented Nov 1, 2016

SimpleCache is a layer on top of PSR-6, that doesn't current have very widespread implementations. So "SimpleCache over PSR-6" doesn't really make sense. Using SimpleCache means using PSR-6.

That said, I think that SS classes directly accessing PSR-6 is a bit overblown. There needs to be some layer between the two, and we have a few choices for that:

  • Continue to use SS_Cache in more or less its current form, with SS_Cache connecting to a PSR-6 backend
  • Remove SS_Cache and replace it with a mechanism for fetching a SimpleCache class. The uncertainty here is that there aren't many popular SimpleCache implementation yet.
  • Refactor SS_Cache to become a SimpleCache implementation. Remember that SimpleCache doesn't actually do any caching itself, it's syntatic sugar for PSR-6. This might be appropriate if the existing SimpleCache implementations aren't sufficiently lightweight.

@sminnee
Copy link
Member

sminnee commented Nov 1, 2016

This is the only SimpleCache implementation, apparently: http://www.scrapbook.cash/. It provides it as part of a caching library.

At 20,000 Packagist downloads it's less popular than Stash and Symfony cache.

@dhensby
Copy link
Contributor

dhensby commented Nov 4, 2016

Surely the point of using a PSR-6 caching lib is that we can use SS_Cache and have it powered by any PSR-6 compliant library? So why not just do that and we pick a default backend (symfony seems fine to me)...

@kinglozzer
Copy link
Member

If there are no SimpleCache implementations yet, my vote goes to refactoring SS_Cache to be one (assuming it’s not going to be a massive undertaking - which reading between the lines it shouldn’t be). If SimpleCache implementations are then released by other maintainers later, it’ll be a piece of cake to drop SS_Cache and switch to one of them.

@sminnee
Copy link
Member

sminnee commented Nov 6, 2016

So, what it sounds like the implication of what Loz is suggesting is:

To be honest I would probably split out our simplecache implementation as silverstripe/simplecache – it may be of use to other developers, given that a standalone PSR-16 implementation doesn't currently exist (it only comes bundled with another caching library). It would be a PSR-16 compatible wrapper around a PSR-6 caching back-end.

What's not decided yet is how we define the caches. We could provide injected services such as SilverStripe\Core\Cache.<backend-name>. Or we could provide a range of injected services that live within the namespaces of the subsystems using the caches. So, for example, i18n::get_cache() could use the service SilverStripe\i18n\i18n\Cache.

In either case, it also begs the question of whether we need Cache::factory() or we should just get the services from the Injector directly. It seems like an unnecessary extra layer.

@sminnee
Copy link
Member

sminnee commented Nov 6, 2016

For reference, these are the caches that we use in core:

  • LeftAndMain_CMSVersion: Cache the CMS version derived from composer.lock
  • CMSMain_SiteTreeHints: Cache the calculation of CMSMain::SiteTreeHints
  • GDBackend_Manipulations: Retain the pass/fail state of manipulation calls between executions
  • SS_Configuration: Cache the ConfigManifest
  • i18n: Passed to Zend_Translate
  • cacheblock: Cache partial caches

@kinglozzer
Copy link
Member

Just to confirm: the new Cache and associated configuration API would be specifically designed for whichever PSR-6 library we choose to bundle with framework, right? Rather than attempting to be generic enough to be able to configure other PSR-6 libraries.

What's not decided yet is how we define the caches. We could provide injected services such as SilverStripe\Core\Cache.<backend-name>. Or we could provide a range of injected services that live within the namespaces of the subsystems using the caches. So, for example, i18n::get_cache() could use the service SilverStripe\i18n\i18n\Cache.

My preference is the latter - my understanding of the first option is that we couldn’t then (easily) set different configuration options for different uses of the same backend. Let’s say I want to override core caches to cache everything with Apc, and have different ttl values for i18n vs the config manifest - could I achieve that with the first option?

In either case, it also begs the question of whether we need Cache::factory() or we should just get the services from the Injector directly. It seems like an unnecessary extra layer.

I think it could still be useful as a convenience layer. We could use Injector to map short names for drivers to full classes:

$cache = SilverStripe\Cache\Cache::factory('Apc', ['ttl' => 600]);

// vs...

$cache = SilverStripe\Core\Injector\Injector::inst()->get('SilverStripe\Core\Cache.Apc');
$cache->getDriver()->setOptions(['ttl' => 600]);

There’s not a huge difference there, but it should be a pretty thin API layer to maintain for the added convenience.

@sminnee
Copy link
Member

sminnee commented Nov 10, 2016

Just to confirm: the new Cache and associated configuration API would be specifically designed for whichever PSR-6 library we choose to bundle with framework, right? Rather than attempting to be generic enough to be able to configure other PSR-6 libraries.

If we provided a configuration API then I would say so, but my preference would be that we just pass responsibility for configuration over to the 3rd part API and don't attempt to build any kind of configuration adapter at all — I don't think it would be a value-adding layer.

I think it could still be useful as a convenience layer. We could use Injector to map short names for drivers to full classes: $cache = SilverStripe\Cache\Cache::factory('Apc', ['ttl' => 600]);

I don't think the TTL argument here is appropriate. I think that the TTL setting should be part of the service definition for the APC cache.

@sminnee sminnee closed this as completed Nov 10, 2016
@sminnee sminnee reopened this Nov 10, 2016
@kinglozzer
Copy link
Member

This is the only SimpleCache implementation, apparently: http://www.scrapbook.cash/. It provides it as part of a caching library.

Looks like it’s a SimpleCache implementation specifically for scrapbook, but not a PSR-6 “wrapper” as such.

PSR-16/SimpleCache is under review at the moment, and these messages ([1] and [2]) seem to suggest that a PSR-6 to PSR-16 bridge will live in a simple-cache-utils package. If that’s the case, then we likely wouldn’t need to maintain our own implementation once (and assuming) PSR-16 is approved.

@chillu
Copy link
Member Author

chillu commented Dec 6, 2016

This might be a good time to come up with a solution for the cache cross-pollination issue raised by Nicolaas - we basically need a way for the system to influence the cache key, e.g. by adding a domain.

@sunnysideup
Copy link
Contributor

Having spent a bit of time working with caches in the last few days, but NOT having read all the stuff above or understanding it all ... Here are a few things I have thought of:

  • It would be great if you could change out SS_Cache class for something else. If SS_Cache were to extend Object then that is easy AFAIK (using a custom class directive).

  • In my work, I have not been too worried about cache expiry times. Basically, I would not let caches expire by themselves, but a flush would remove ALL of them. The idea that you would say: this cache expires in an hour and this one in two hours just seems rather complicated to fully understand in its implications and management. Rather than cache key expiry, you could perhaps simply overwrite cache values, clear it or point to new ones.

  • In my frustration working with memcache (BUG: cache constantly being cleared on Redis / Memcached / LibMemcached / WinCache / APC / XCache #6383), I started to write my own cache and came across the idea of using a Mysql table with set length fields (CHAR) stored using Engine=MEMORY (a MYSQL table that is stored in RAM in its entirety) together with an InnoDB table for longer strings. I like this idea as the default cache for Silverstripe because:
    -- (a) Mysql Memory table is exactly made for such a thing.
    -- (b) MYSQL is available and there is no need to set up anything additional (such as REDIS / Memcache)
    -- (c) it is super easy to inspect the values being stored (where reading values in, for example, memcache takes a lot more effort).
    -- (d) I imagine it to be faster and easier to work with than the current default of temp files.
    -- (e) as a database is always linked to one site, there is no question about cross-pollination that you may have with temp files and/or memcache
    This is only an idea right now, but . I would be super happy to research it if any of you thought that this idea might be a good one.

  • A caching system should be a storage place for keys with associated values (like a one dimensional associative array) that you are happy to lose at any time (flush, server reset) ... nothing more. Keeping it really simple will make it faster and easier to understand.

Sorry for the long rant. I hope it helps in getting a better caching system soon ;-)

@kinglozzer
Copy link
Member

@sunnysideup I appreciate you’ve had a few frustrating issues with caching recently, thanks for your work in tracking them and their causes down. We might not be able to fix them all, or fix them cleanly, in the 3.x releases but it’s helpful to know what problems we need to avoid in future.

I won’t touch on all of your points above, but we’ll definitely avoid writing our own caching system from scratch. Caching is deceptively hard to get right, and there are many mature, well-tested libraries out there that handle it very well (doctrine/cache, symfony/cache, tedivm/stash to name a few). If you’re thinking of writing your own for use in a live project, I would recommend instead investing the time in using a library like one of those listed above (of course, it could still be a fun side project to play around with!).

@sunnysideup
Copy link
Contributor

@kinglozzer - thank you for your reply. Totally agree with the maxim of using something existing and that being better than homebaked.

Having said that, you should consider this: because the current caching system is hard to understand and tries to allow for so many options, with different settings, etc... it basically meant that SS 3 did not have a proper caching system beyond file caching.

In other words, you may move the grunt work of the caching to a third-party system, but if that third-party system is complex and has many options then you may end up writing a larger amount of code to deal with the options then the amount of code required to write our own solution.

Thus, I would advocate for something really fast and simple with the ability to switch it out for something more complex.

@sminnee sminnee modified the milestones: 4.0.0, CMS 4.0.0-alpha4, CMS 4.0.0-alpha5 Jan 11, 2017
@chillu
Copy link
Member Author

chillu commented Feb 14, 2017

So it sounds like the main point of contention is retaining SS_Cache as an API (Option 1b and 2b, two votes), or replacing it with PSR-6/PSR-16 (Option 1a and 2a, three votes).

Looking at the SS_Cache API, we have the following methods:

  • add_backend($name, $type, $options = array()
  • pick_backend($name, $for, $priority = 1)
  • get_cache_lifetime($for)
  • set_cache_lifetime($for, $lifetime = 600, $priority = 1)
  • factory($for, $frontend = 'Output', $frontendOptions = null) (returns a non-PSR Zend_Cache object)

Just to clarify, is anybody suggesting to create a "PSR-6/PSR-16 to Zend_Cache" adapter, to avoid any existing cache users changing their code? Or is this just about retaining SS_Cache::factory() as an entry point and wrapper around a DI service creation based on the $for argument, while still returning a PSR-6/PSR-16 cache interface?

Sorry, the wording of the options ("internally") implied that we weren't requiring devs to change their own code use, and retain the Zend_Cache_Core signature: load($id, $doNotTestCacheValidity = false, $doNotUnserialize = false) and save($data, $id = null, $tags = array(), $specificLifetime = false, $priority = 8). That doesn't sound feasible, so even Option 1b and 2b would expose PSR-6/PSR-16 cache interfaces to SilverStripe devs.

@sminnee
Copy link
Member

sminnee commented Feb 14, 2017

Ingo I think that level of detail is best addressed at the PR stage. Might be best to start coding?

@dhensby
Copy link
Contributor

dhensby commented Feb 14, 2017

I'm becoming less attached to the idea of keeping SS_Cache. What I was concerned with was making sure we have a unified API where backends are easily swapped out. It sounds like that can be solved without keeping SS_Cache, right?

If so, I'm happy to go with the crowd on this one

@micmania1
Copy link
Contributor

The reason I'd be against keeping SS_Cache is that PSR's are there to agree upon an API and if we add our own layer, we're not really buying into PSR's. Also, it means devs coming to SS would need to learn our API instead of using what they already know.

@sminnee
Copy link
Member

sminnee commented Feb 14, 2017

I'm becoming less attached to the idea of keeping SS_Cache. What I was concerned with was making sure we have a unified API where backends are easily swapped out. It sounds like that can be solved without keeping SS_Cache, right?

I was imagining something like:

$partialCache = Injector::inst()->get('Psr\Cache\CacheItemPoolInterface.Partials');
$item = $partialCache->getItem('foo');
if($item->isHit()) {
  return $item->get();
} else {
  $value = doSomething();
  $item->set($value);
  return $value;
}

and your yaml

Injector:
  Psr\Cache\CacheItemPoolInterface.Partials
    class: bla
    arguments:
     - foo
     - bar
     - bz

Instead of the base service name Psr\Cache\CacheItemPoolInterface we could just use Cache.

@chillu
Copy link
Member Author

chillu commented Feb 21, 2017

Status update: I've started implementing this and hitting some conceptual road blocks. I've identified a few implied requirements from the 3.x-style API:

  1. Globally exchange cache driver for all cache implementations without knowledge of which named caches exist
  2. Set different cache driver for one particular named cache
  3. Set default cache lifetime without duplicating all configuration for the cache driver(s)
  4. Allow creating a new named cache without writing a bunch of boilerplate YAML config

In Symfony's PSR-16 cache drivers, this is achieved by $namespace and $defaultLifetime constructor args. These differ based on implementation (argument position).

FilesystemCache::__construct($namespace = '', $defaultLifetime = 0, $directory = null)
MemcachedCache::__construct(\Memcached $client, $namespace = '', $defaultLifetime = 0)

Every cache has it's own namespace in order to be clearable without affecting other caches (cacheblock, GDManipulation, LeftAndMain_Version, etc). In the filesystem driver, this translates to subfolders in TEMP_FOLDER. Because of the constructor arg use in Symfony (rather than setters), we need to define an entire YAML injector configuration for each of these variations. Here's a mock up of how this would look - using different drivers to illustrate the issue, but it would be the same amount of config when using the same driver for both (less with some YAML referencing magic):

---
Name: corecache
---
SilverStripe\Core\Injector\Injector:
  Cache.default:
    class: 'Symfony\Component\Cache\Simple\FilesystemCache'
    constructor:
      namespace: 'default'
      defaultLifetime: 300
      directory: `TEMP_FOLDER`
  Cache.GDBackend_Manipulations:
    class: 'Symfony\Component\Cache\Simple\MemcachedCache'
    constructor:
      client: '%$MemcachedCacheService
      namespace: 'GDBackend_Manipulations'
      defaultLifetime: 100

Namespace option 1: Create lightweight driver-specific factories return PSR-16 objects, e.g. SilverStripe\Core\Cache\FilesystemCacheFactory and SilverStripe\Core\Cache\MemcacheCacheFactory. These would pass through constructor args, but also take a $namespace argument separately in order to insert it in the correct place depending on the constructor method signature.

Namespace option 2: Use StashPHP + PSR-6, which means we can use Pool->setNamespace() and consistently rely on the SS Injector with a minimal Cache::factory() wrapper to create these services. Since StashPHP doesn't support PSR-16, this automatically means exposing PSR-6 to SilverStripe devs (unless we want to write our own adapters).

Namespace option 3: Drop the "Allow creating a new named cache without writing a bunch of boilerplate YAML config" requirement. This would mean any project would need to know about all cache implementations used throughout core, modules and project code, and duplicate the same YAML config block for each of them to consistently set a different driver (e.g. exchanging filesystem for memcache).

@sminnee
Copy link
Member

sminnee commented Feb 21, 2017

I think that option 1 is the best road to go down. I'd define a CacheFactory interface for these factory objects, and have all the default cache services use something like this in their service definition

CacheFactory.php

namespace SilverStripe\Core\Cache;

interface CacheFactory
{
  function create($namespace, $lifetime = null);
}

cache.yml

SilverStripe\Core\Cache\CacheFactory:
  class: 'SilverStripe\Core\Cache\FilesystemCacheFactory'
  properties:
    Directory: `TEMP_FOLDER`
SilverStripe\Core\Cache\Cache.partials:
  factory: SilverStripe\Core\Cache\CacheFactory
  constructor:
    - "partials"
    - 300

Encourage module developers to do the same.This will mean that the CacheFactory service can be redefined and all default caches will be replaced.

@sminnee
Copy link
Member

sminnee commented Feb 21, 2017

It's your call as to whether the cache factory interface needs a setLifetime as sell

@sminnee
Copy link
Member

sminnee commented Feb 21, 2017

@chillu I amended my example above after looking at how Injector actually works. ;-) Principle is the same

@sminnee
Copy link
Member

sminnee commented Feb 21, 2017

Oh the other thing you could do is have a generic cache factory that lets you define the argument order:

SilverStripe\Core\Cache\CacheFactory:
  class: 'SilverStripe\Core\Cache\DefaultCacheFactory'
  properties:
    Class: 'Symfony\Component\Cache\Simple\FilesystemCache'
    Constructor:
     - '$namespace'
     - '$lifetime'
     - `TEMP_FOLDER`

Or

SilverStripe\Core\Cache\CacheFactory:
  class: 'SilverStripe\Core\Cache\DefaultCacheFactory'
  properties:
    Class: 'Symfony\Component\Cache\Simple\ MemcachedCache'
    Constructor:
     - '%$MemcachedCacheService
     - '$namespace'
     - '$lifetime'

With code something like this:

class DefaultCacheFactory implements CacheFactory {
  public $Class, $Constructor;

  function create($namespace, $lifetime) {
    $mappings = [ '$namespace' => $namespace, '$lifetime' => $lifetime ];
    foreach($this->Constructor as $arg) {
      $mappedArgs[] = isset($mappings[$arg]) ? $mappings[$arg] : $ar;g
    }
    return Injector::inst()->create($this->Class, $mappedArgs);
  }

chillu added a commit to open-sausages/silverstripe-framework that referenced this issue Feb 23, 2017
Replaced with a PSR-16 implementation
chillu added a commit to open-sausages/silverstripe-framework that referenced this issue Feb 23, 2017
chillu added a commit to open-sausages/silverstripe-cms that referenced this issue Feb 23, 2017
@chillu
Copy link
Member Author

chillu commented Feb 23, 2017

Pull requests: #6648 and silverstripe/silverstripe-cms#1741

chillu added a commit to open-sausages/silverstripe-framework that referenced this issue Feb 24, 2017
Replaced with a PSR-16 implementation
chillu added a commit to open-sausages/silverstripe-framework that referenced this issue Feb 24, 2017
chillu added a commit to open-sausages/silverstripe-cms that referenced this issue Feb 24, 2017
chillu added a commit to open-sausages/silverstripe-cms that referenced this issue Feb 24, 2017
chillu added a commit to open-sausages/silverstripe-framework that referenced this issue Feb 25, 2017
Replaced with a PSR-16 implementation
chillu added a commit to open-sausages/silverstripe-framework that referenced this issue Feb 25, 2017
sminnee pushed a commit to silverstripe/silverstripe-cms that referenced this issue Feb 26, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants