Skip to content

Commit

Permalink
API Split Config into ImmutableConfig and MutableConfig
Browse files Browse the repository at this point in the history
API Remove Config::INHERITED
  • Loading branch information
Damian Mooyman committed Feb 13, 2017
1 parent e6aa894 commit 0a2b550
Show file tree
Hide file tree
Showing 8 changed files with 804 additions and 343 deletions.
490 changes: 156 additions & 334 deletions src/Core/Config/Config.php

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions src/Core/Config/ConfigLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,16 @@ public function popManifest()
{
return array_pop($this->manifests);
}

/**
* Nest the current manifest
*
* @return ConfigCollectionInterface
*/
public function nest()
{
$manifest = $this->getManifest()->nest();
$this->pushManifest($manifest);
return $manifest;
}
}
39 changes: 33 additions & 6 deletions src/Core/Config/Config_ForClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

namespace SilverStripe\Core\Config;

use SilverStripe\Dev\Deprecation;

class Config_ForClass
{

/**
* @var string $class
*/
Expand Down Expand Up @@ -33,19 +34,45 @@ public function __get($name)
*/
public function __set($name, $val)
{
$this->update($name, $val);
$this->set($name, $val);
}

/**
* Explicit pass-through to Config::update()
*
* @param string $name
* @param mixed $val
* @param mixed $value
* @return $this
*/
public function update($name, $value)
{
Deprecation::notice('5.0', 'Use merge() instead');
return $this->merge($name, $value);
}

/**
* Merge a given config
*
* @param string $name
* @param mixed $value
* @return $this
*/
public function merge($name, $value)
{
Config::modify()->merge($this->class, $name, $value);
return $this;
}

/**
* Replace config value
*
* @param string $name
* @param mixed $value
* @return $this
*/
public function update($name, $val)
public function set($name, $value)
{
Config::inst()->update($this->class, $name, $val);
Config::modify()->set($this->class, $name, $value);
return $this;
}

Expand Down Expand Up @@ -77,7 +104,7 @@ public function get($name, $sourceOptions = 0)
*/
public function remove($name)
{
Config::inst()->remove($this->class, $name);
Config::modify()->remove($this->class, $name);
return $this;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Core/Config/Configurable.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function stat($name)
*/
public function set_stat($name, $value)
{
Config::inst()->update(get_class($this), $name, $value);
Config::modify()->merge(get_class($this), $name, $value);
}

/**
Expand Down
238 changes: 238 additions & 0 deletions src/Core/Config/ImmutableConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
<?php

namespace SilverStripe\Core\Config;

use micmania1\config\MergeStrategy\Priority;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use SilverStripe\Core\Extension;
use SilverStripe\Core\Object;

class ImmutableConfig extends Config
{
/**
* In memory cache is used per-request to prevent unnecessary calls to cache
* which can have latency.
*
* Keyed first by class and then lookup mask
*
* @var array
*/
protected $memoryCache = [];

/**
* Cache applied to api-level calls (respective of merge options)
* Applies on top of $memoryCache
*
* Note: This cache does NOT include local modifications applied via
* remove(), replace() and merge()
*
* @var CacheItemPoolInterface
*/
protected $persistentCache = null;

/**
* @param CacheItemPoolInterface $persistentCache
*/
public function __construct(
CacheItemPoolInterface $persistentCache
) {
$this->persistentCache = $persistentCache;
}

/**
* Get the value of a config property class.name
*
* @param string $class
* @param string $name
* @var int $sourceOptions
* @return mixed
*/
public function get($class, $name = null, $sourceOptions = 0)
{
$this->validateSourceOption($sourceOptions);

// Load config from cache
$classConfig = $this->getCachedClassConfig($class, $sourceOptions, $cacheItem);
if (!isset($classConfig)) {
$classConfig = $this->loadClassConfig($class, $sourceOptions, $cacheItem);
if (!isset($classConfig)) {
return null;
}
}

// Return either name, or whole-class config
if ($name) {
return isset($classConfig[$name]) ? $classConfig[$name] : null;
}
return $classConfig;
}

/**
* Load class config from manifest and apply local modifications.
* Applies these modifications to the cache as well as necessary.
*
* @param string $class
* @param int $sourceOptions
* @param CacheItemInterface $cacheItem
* @return array
*/
protected function loadClassConfig($class, $sourceOptions, $cacheItem)
{
// Load raw cache
$classConfig = $this->getClassConfig($class, $sourceOptions);

// Save raw config to persistant cache
$cacheItem->set($classConfig);
$this->persistentCache->saveDeferred($cacheItem);

// Save in local cache
if (!isset($this->memoryCache[$class])) {
$this->memoryCache[$class] = [];
}
$this->memoryCache[$class][$sourceOptions] = $classConfig;
return $classConfig;
}

/**
* Load config for a given class from cache.
* This config result will include local modifications().
* Additionally this method will hydrate local cache from persistent
* cache as-needed.
*
* @param string $class
* @param int $sourceOptions
* @param CacheItemInterface $cacheItem Cache item which should be used
* to write back to persistent store
* @return array|null Cached config (with local modifications) or null if not cached
*/
protected function getCachedClassConfig($class, $sourceOptions, CacheItemInterface &$cacheItem)
{
$cacheItem = null;

// First hit from in-memory cache
if (isset($this->memoryCache[$class][$sourceOptions])) {
return $this->memoryCache[$class][$sourceOptions];
}

// Get from persistent cache
$key = $this->getClassConfigCacheKey($class, $sourceOptions);
$cacheItem = $this->persistentCache->getItem($key);
if (!$cacheItem->isHit()) {
return null;
}

// Load from persistent -> local cache
$classConfig = $cacheItem->get();
if (!isset($this->memoryCache[$class])) {
$this->memoryCache[$class] = [];
}
$this->memoryCache[$class][$sourceOptions] = $classConfig;
return $classConfig;
}

/**
* Get the class config for the given class with the given source options
*
* @param string $class
* @param int $sourceOptions
* @return array
*/
protected function getClassConfig($class, $sourceOptions = 0)
{
$classConfig = ConfigLoader::instance()->getManifest()->get($class);

if ($this->shouldApplyExtraConfig($class, $sourceOptions)) {
$this->applyExtraConfig($class, $sourceOptions, $classConfig);
}

return $classConfig;
}

/**
* Applied config to a class from its extensions
*
* @param string $class
* @param int $sourceOptions
* @param mixed $classConfig
*/
protected function applyExtraConfig($class, $sourceOptions, &$classConfig)
{
$extraSources = Object::get_extra_config_sources($class);
if (empty($extraSources)) {
return;
}

$priority = new Priority;
foreach ($extraSources as $source) {
if (is_string($source)) {
$source = $this->getClassConfig(
$source,
self::UNINHERITED | self::EXCLUDE_EXTRA_SOURCES
);
}

if (is_array($source)) {
if (is_null($classConfig) || !is_array($classConfig)) {
$classConfig = $source;
continue;
}

$classConfig = $priority->mergeArray($classConfig, $source);
} elseif (!is_null($source)) {
$classConfig = $source;
}
}
}

/**
* A check to test if we should include extra config (data extensions)
*
* @param string $class
* @param int $sourceOptions
* @return bool
*/
protected function shouldApplyExtraConfig($class, $sourceOptions)
{
if (is_a($class, Extension::class, true)) {
return false;
}

return ($sourceOptions & self::EXCLUDE_EXTRA_SOURCES) != self::EXCLUDE_EXTRA_SOURCES;
}

/**
* Get cache key
*
* @param string $class
* @param int $sourceOptions
* @return string
*/
protected function getClassConfigCacheKey($class, $sourceOptions)
{
$parts = [strtolower($class), $sourceOptions];
return md5(implode('-', $parts));
}

/**
* Build nested config
*
* @return MutableConfig
*/
public function cloneNest()
{
// Nested child of immutable config is mutable config
return new MutableConfig($this);
}

/**
* Get parent config
*
* @return Config
*/
public function getNestedFrom()
{
// Immutable config has no parent
return null;
}
}
Loading

0 comments on commit 0a2b550

Please sign in to comment.