mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
950 lines
27 KiB
PHP
950 lines
27 KiB
PHP
<?php
|
|
|
|
/**
|
|
* The configuration system works like this:
|
|
*
|
|
* Each class has a set of named properties
|
|
*
|
|
* Each named property can contain either
|
|
*
|
|
* - An array
|
|
* - A non-array value
|
|
*
|
|
* If the value is an array, each value in the array may also be one of those
|
|
* three types.
|
|
*
|
|
* A property can have a value specified in multiple locations, each of which
|
|
* have a hard coded or explicit priority. We combine all these values together
|
|
* into a "composite" value using rules that depend on the priority order of
|
|
* the locations to give the final value, using these rules:
|
|
*
|
|
* - If the value is an array, each array is added to the _beginning_ of the
|
|
* composite array in ascending priority order. If a higher priority item has
|
|
* a non-integer key which is the same as a lower priority item, the value of
|
|
* those items is merged using these same rules, and the result of the merge
|
|
* is located in the same location the higher priority item would be if there
|
|
* was no key clash. Other than in this key-clash situation, within the
|
|
* particular array, order is preserved.
|
|
*
|
|
* - If the value is not an array, the highest priority value is used without
|
|
* any attempt to merge.
|
|
*
|
|
* It is an error to have mixed types of the same named property in different
|
|
* locations (but an error will not necessarily be raised due to optimizations
|
|
* in the lookup code).
|
|
*
|
|
* The exception to this is "false-ish" values - empty arrays, empty strings,
|
|
* etc. When merging a non-false-ish value with a false-ish value, the result
|
|
* will be the non-false-ish value regardless of priority. When merging two
|
|
* false-ish values the result will be the higher priority false-ish value.
|
|
*
|
|
* The locations that configuration values are taken from in highest -> lowest
|
|
* priority order.
|
|
*
|
|
* - Any values set via a call to Config#update.
|
|
*
|
|
* - The configuration values taken from the YAML files in _config directories
|
|
* (internally sorted in before / after order, where the item that is latest
|
|
* is highest priority).
|
|
*
|
|
* - Any static set on an "additional static source" class (such as an
|
|
* extension) named the same as the name of the property.
|
|
*
|
|
* - Any static set on the class named the same as the name of the property.
|
|
*
|
|
* - The composite configuration value of the parent class of this class.
|
|
*
|
|
* At some of these levels you can also set masks. These remove values from the
|
|
* composite value at their priority point rather than add. They are much
|
|
* simpler. They consist of a list of key / value pairs. When applied against
|
|
* the current composite value:
|
|
*
|
|
* - If the composite value is a sequential array, any member of that array
|
|
* that matches any value in the mask is removed.
|
|
*
|
|
* - If the composite value is an associative array, any member of that array
|
|
* that matches both the key and value of any pair in the mask is removed.
|
|
*
|
|
* - If the composite value is not an array, if that value matches any value
|
|
* in the mask it is removed.
|
|
*
|
|
* @package framework
|
|
* @subpackage core
|
|
*/
|
|
class Config {
|
|
|
|
/**
|
|
* A marker instance for the "anything" singleton value. Don't access
|
|
* directly, even in-class, always use self::anything()
|
|
*
|
|
* @var Object
|
|
*/
|
|
private static $_anything = null;
|
|
|
|
/**
|
|
* Get a marker class instance that is used to do a "remove anything with
|
|
* this key" by adding $key => Config::anything() to the suppress array
|
|
*
|
|
* @return Object
|
|
*/
|
|
public static function anything() {
|
|
if (self::$_anything === null) {
|
|
self::$_anything = new stdClass();
|
|
}
|
|
|
|
return self::$_anything;
|
|
}
|
|
|
|
// -- Source options bitmask --
|
|
|
|
/**
|
|
* source options bitmask value - merge all parent configuration in as
|
|
* lowest priority.
|
|
*
|
|
* @const
|
|
*/
|
|
const INHERITED = 0;
|
|
|
|
/**
|
|
* source options bitmask value - only get configuration set for this
|
|
* specific class, not any of it's parents.
|
|
*
|
|
* @const
|
|
*/
|
|
const UNINHERITED = 1;
|
|
|
|
/**
|
|
* source options bitmask value - inherit, but stop on the first class
|
|
* that actually provides a value (event an empty value).
|
|
*
|
|
* @const
|
|
*/
|
|
const FIRST_SET = 2;
|
|
|
|
/**
|
|
* @const source options bitmask value - do not use additional statics
|
|
* sources (such as extension)
|
|
*/
|
|
const EXCLUDE_EXTRA_SOURCES = 4;
|
|
|
|
// -- get_value_type response enum --
|
|
|
|
/**
|
|
* Return flag for get_value_type indicating value is a scalar (or really
|
|
* just not-an-array, at least ATM)
|
|
*
|
|
* @const
|
|
*/
|
|
const ISNT_ARRAY = 1;
|
|
|
|
/**
|
|
* Return flag for get_value_type indicating value is an array.
|
|
* @const
|
|
*/
|
|
const IS_ARRAY = 2;
|
|
|
|
/**
|
|
* Get whether the value is an array or not. Used to be more complicated,
|
|
* but still nice sugar to have an enum to compare and not just a true /
|
|
* false value.
|
|
*
|
|
* @param $val any - The value
|
|
*
|
|
* @return int - One of ISNT_ARRAY or IS_ARRAY
|
|
*/
|
|
protected static function get_value_type($val) {
|
|
if (is_array($val)) {
|
|
return self::IS_ARRAY;
|
|
}
|
|
|
|
return self::ISNT_ARRAY;
|
|
}
|
|
|
|
/**
|
|
* What to do if there's a type mismatch.
|
|
*
|
|
* @throws UnexpectedValueException
|
|
*/
|
|
protected static function type_mismatch() {
|
|
throw new UnexpectedValueException('Type mismatch in configuration. All values for a particular property must'
|
|
. ' contain the same type (or no value at all).');
|
|
}
|
|
|
|
/**
|
|
* @todo If we can, replace next static & static methods with DI once that's in
|
|
*/
|
|
protected static $instance;
|
|
|
|
/**
|
|
* Get the current active Config instance.
|
|
*
|
|
* Configs should not normally be manually created.
|
|
*
|
|
* In general use you will use this method to obtain the current Config
|
|
* instance.
|
|
*
|
|
* @return Config
|
|
*/
|
|
public static function inst() {
|
|
if (!self::$instance) {
|
|
self::$instance = new Config();
|
|
}
|
|
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Set the current active {@link Config} instance.
|
|
*
|
|
* {@link Config} objects should not normally be manually created.
|
|
*
|
|
* A use case for replacing the active configuration set would be for
|
|
* creating an isolated environment for unit tests.
|
|
*
|
|
* @param Config $instance New instance of Config to assign
|
|
* @return Config Reference to new active Config instance
|
|
*/
|
|
public static function set_instance($instance) {
|
|
self::$instance = $instance;
|
|
|
|
global $_SINGLETONS;
|
|
$_SINGLETONS['Config'] = $instance;
|
|
return $instance;
|
|
}
|
|
|
|
/**
|
|
* Make the newly active {@link Config} be a copy of the current active
|
|
* {@link Config} instance.
|
|
*
|
|
* You can then make changes to the configuration by calling update and
|
|
* remove on the new value returned by {@link Config::inst()}, and then discard
|
|
* those changes later by calling unnest.
|
|
*
|
|
* @return Config Reference to new active Config instance
|
|
*/
|
|
public static function nest() {
|
|
$current = self::inst();
|
|
|
|
$new = clone $current;
|
|
$new->nestedFrom = $current;
|
|
return self::set_instance($new);
|
|
}
|
|
|
|
/**
|
|
* Change the active Config back to the Config instance the current active
|
|
* Config object was copied from.
|
|
*
|
|
* @return Config Reference to new active Config instance
|
|
*/
|
|
public static function unnest() {
|
|
if (self::inst()->nestedFrom) {
|
|
self::set_instance(self::inst()->nestedFrom);
|
|
}
|
|
else {
|
|
user_error(
|
|
"Unable to unnest root Config, please make sure you don't have mis-matched nest/unnest",
|
|
E_USER_WARNING
|
|
);
|
|
}
|
|
return self::inst();
|
|
}
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $cache;
|
|
|
|
/**
|
|
* Each copy of the Config object need's it's own cache, so changes don't
|
|
* leak through to other instances.
|
|
*/
|
|
public function __construct() {
|
|
$this->cache = new Config_MemCache();
|
|
}
|
|
|
|
public function __clone() {
|
|
$this->cache = clone $this->cache;
|
|
}
|
|
|
|
/**
|
|
* @var Config - The config instance this one was copied from when
|
|
* Config::nest() was called.
|
|
*/
|
|
protected $nestedFrom = null;
|
|
|
|
/**
|
|
* @var array - Array of arrays. Each member is an nested array keyed as
|
|
* $class => $name => $value, where value is a config value to treat as
|
|
* the highest priority item.
|
|
*/
|
|
protected $overrides = array();
|
|
|
|
/**
|
|
* @var array $suppresses Array of arrays. Each member is an nested array
|
|
* keyed as $class => $name => $value, where value is a config value suppress
|
|
* from any lower priority item.
|
|
*/
|
|
protected $suppresses = array();
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $staticManifests = array();
|
|
|
|
/**
|
|
* @param SS_ConfigStaticManifest
|
|
*/
|
|
public function pushConfigStaticManifest(SS_ConfigStaticManifest $manifest) {
|
|
array_unshift($this->staticManifests, $manifest);
|
|
|
|
$this->cache->clean();
|
|
}
|
|
|
|
/** @var [array] - The list of settings pulled from config files to search through */
|
|
protected $manifests = array();
|
|
|
|
/**
|
|
* Add another manifest to the list of config manifests to search through.
|
|
*
|
|
* WARNING: Config manifests to not merge entries, and do not solve before/after rules inter-manifest -
|
|
* instead, the last manifest to be added always wins
|
|
*/
|
|
public function pushConfigYamlManifest(SS_ConfigManifest $manifest) {
|
|
array_unshift($this->manifests, $manifest);
|
|
|
|
// Now that we've got another yaml config manifest we need to clean the cache
|
|
$this->cache->clean();
|
|
// We also need to clean the cache if the manifest's calculated config values change
|
|
$manifest->registerChangeCallback(array($this->cache, 'clean'));
|
|
|
|
// @todo: Do anything with these. They're for caching after config.php has executed
|
|
$this->collectConfigPHPSettings = true;
|
|
$this->configPHPIsSafe = false;
|
|
|
|
$manifest->activateConfig();
|
|
|
|
$this->collectConfigPHPSettings = false;
|
|
}
|
|
|
|
/** @var [Config_ForClass] - The list of Config_ForClass instances, keyed off class */
|
|
static protected $for_class_instances = array();
|
|
|
|
/**
|
|
* Get an accessor that returns results by class by default.
|
|
*
|
|
* Shouldn't be overridden, since there might be many Config_ForClass instances already held in the wild. Each
|
|
* Config_ForClass instance asks the current_instance of Config for the actual result, so override that instead
|
|
*
|
|
* @param $class
|
|
* @return Config_ForClass
|
|
*/
|
|
public function forClass($class) {
|
|
if (isset(self::$for_class_instances[$class])) {
|
|
return self::$for_class_instances[$class];
|
|
}
|
|
else {
|
|
return self::$for_class_instances[$class] = new Config_ForClass($class);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Merge a lower priority associative array into an existing higher priority associative array, as per the class
|
|
* docblock rules
|
|
*
|
|
* It is assumed you've already checked that you've got two associative arrays, not scalars or sequential arrays
|
|
*
|
|
* @param $dest array - The existing high priority associative array
|
|
* @param $src array - The low priority associative array to merge in
|
|
*/
|
|
public static function merge_array_low_into_high(&$dest, $src) {
|
|
foreach ($src as $k => $v) {
|
|
if (!$v) {
|
|
continue;
|
|
}
|
|
else if (is_int($k)) {
|
|
$dest[] = $v;
|
|
}
|
|
else if (isset($dest[$k])) {
|
|
$newType = self::get_value_type($v);
|
|
$currentType = self::get_value_type($dest[$k]);
|
|
|
|
// Throw error if types don't match
|
|
if ($currentType !== $newType) self::type_mismatch();
|
|
|
|
if ($currentType == self::IS_ARRAY) self::merge_array_low_into_high($dest[$k], $v);
|
|
else continue;
|
|
}
|
|
else {
|
|
$dest[$k] = $v;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Merge a higher priority assocative array into an existing lower priority associative array, as per the class
|
|
* docblock rules.
|
|
*
|
|
* Much more expensive that the other way around, as there's no way to insert an associative k/v pair into an
|
|
* array at the top of the array
|
|
*
|
|
* @static
|
|
* @param $dest array - The existing low priority associative array
|
|
* @param $src array - The high priority array to merge in
|
|
*/
|
|
public static function merge_array_high_into_low(&$dest, $src) {
|
|
$res = $src;
|
|
self::merge_array_low_into_high($res, $dest);
|
|
$dest = $res;
|
|
}
|
|
|
|
public static function merge_high_into_low(&$result, $value) {
|
|
$newType = self::get_value_type($value);
|
|
|
|
if (!$result) {
|
|
$result = $value;
|
|
}
|
|
else {
|
|
$currentType = self::get_value_type($result);
|
|
if ($currentType !== $newType) self::type_mismatch();
|
|
|
|
if ($currentType == self::ISNT_ARRAY) $result = $value;
|
|
else self::merge_array_high_into_low($result, $value);
|
|
}
|
|
}
|
|
|
|
public static function merge_low_into_high(&$result, $value, $suppress) {
|
|
$newType = self::get_value_type($value);
|
|
|
|
if ($suppress) {
|
|
if ($newType == self::IS_ARRAY) {
|
|
$value = self::filter_array_by_suppress_array($value, $suppress);
|
|
if (!$value) return;
|
|
}
|
|
else {
|
|
if (self::check_value_contained_in_suppress_array($value, $suppress)) return;
|
|
}
|
|
}
|
|
|
|
if (!$result) {
|
|
$result = $value;
|
|
}
|
|
else {
|
|
$currentType = self::get_value_type($result);
|
|
if ($currentType !== $newType) self::type_mismatch();
|
|
|
|
if ($currentType == self::ISNT_ARRAY) return; // PASS
|
|
else self::merge_array_low_into_high($result, $value);
|
|
}
|
|
}
|
|
|
|
public static function check_value_contained_in_suppress_array($v, $suppresses) {
|
|
foreach ($suppresses as $suppress) {
|
|
list($sk, $sv) = $suppress;
|
|
if ($sv === self::anything() || $v == $sv) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static protected function check_key_or_value_contained_in_suppress_array($k, $v, $suppresses) {
|
|
foreach ($suppresses as $suppress) {
|
|
list($sk, $sv) = $suppress;
|
|
if (($sk === self::anything() || $k == $sk) && ($sv === self::anything() || $v == $sv)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static protected function filter_array_by_suppress_array($array, $suppress) {
|
|
$res = array();
|
|
|
|
foreach ($array as $k => $v) {
|
|
$suppressed = self::check_key_or_value_contained_in_suppress_array($k, $v, $suppress);
|
|
|
|
if (!$suppressed) {
|
|
if (is_numeric($k)) $res[] = $v;
|
|
else $res[$k] = $v;
|
|
}
|
|
}
|
|
|
|
return $res;
|
|
}
|
|
|
|
protected $extraConfigSources = array();
|
|
|
|
public function extraConfigSourcesChanged($class) {
|
|
unset($this->extraConfigSources[$class]);
|
|
$this->cache->clean("__{$class}");
|
|
}
|
|
|
|
protected function getUncached($class, $name, $sourceOptions, &$result, $suppress, &$tags) {
|
|
$tags[] = "__{$class}";
|
|
$tags[] = "__{$class}__{$name}";
|
|
|
|
// If result is already not something to merge into, just return it
|
|
if ($result !== null && !is_array($result)) return $result;
|
|
|
|
// First, look through the override values
|
|
foreach($this->overrides as $k => $overrides) {
|
|
if (isset($overrides[$class][$name])) {
|
|
$value = $overrides[$class][$name];
|
|
|
|
self::merge_low_into_high($result, $value, $suppress);
|
|
if ($result !== null && !is_array($result)) return $result;
|
|
}
|
|
|
|
if (isset($this->suppresses[$k][$class][$name])) {
|
|
$suppress = $suppress
|
|
? array_merge($suppress, $this->suppresses[$k][$class][$name])
|
|
: $this->suppresses[$k][$class][$name];
|
|
}
|
|
}
|
|
|
|
$nothing = null;
|
|
|
|
// Then the manifest values
|
|
foreach($this->manifests as $manifest) {
|
|
$value = $manifest->get($class, $name, $nothing);
|
|
if ($value !== $nothing) {
|
|
self::merge_low_into_high($result, $value, $suppress);
|
|
if ($result !== null && !is_array($result)) return $result;
|
|
}
|
|
}
|
|
|
|
$sources = array($class);
|
|
|
|
// Include extensions only if not flagged not to, and some have been set
|
|
if (($sourceOptions & self::EXCLUDE_EXTRA_SOURCES) != self::EXCLUDE_EXTRA_SOURCES) {
|
|
// If we don't have a fresh list of extra sources, get it from the class itself
|
|
if (!array_key_exists($class, $this->extraConfigSources)) {
|
|
$this->extraConfigSources[$class] = Object::get_extra_config_sources($class);
|
|
}
|
|
|
|
// Update $sources with any extra sources
|
|
$extraSources = $this->extraConfigSources[$class];
|
|
if ($extraSources) $sources = array_merge($sources, $extraSources);
|
|
}
|
|
|
|
$value = $nothing = null;
|
|
|
|
foreach ($sources as $staticSource) {
|
|
if (is_array($staticSource)) {
|
|
$value = isset($staticSource[$name]) ? $staticSource[$name] : $nothing;
|
|
}
|
|
else {
|
|
foreach ($this->staticManifests as $i => $statics) {
|
|
$value = $statics->get($staticSource, $name, $nothing);
|
|
if ($value !== $nothing) break;
|
|
}
|
|
}
|
|
|
|
if ($value !== $nothing) {
|
|
self::merge_low_into_high($result, $value, $suppress);
|
|
if ($result !== null && !is_array($result)) return $result;
|
|
}
|
|
}
|
|
|
|
// Finally, merge in the values from the parent class
|
|
if (
|
|
($sourceOptions & self::UNINHERITED) != self::UNINHERITED &&
|
|
(($sourceOptions & self::FIRST_SET) != self::FIRST_SET || $result === null)
|
|
) {
|
|
$parent = get_parent_class($class);
|
|
if ($parent) $this->getUncached($parent, $name, $sourceOptions, $result, $suppress, $tags);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get the config value associated for a given class and property
|
|
*
|
|
* This merges all current sources and overrides together to give final value
|
|
* todo: Currently this is done every time. This function is an inner loop function, so we really need to be
|
|
* caching heavily here.
|
|
*
|
|
* @param $class string - The name of the class to get the value for
|
|
* @param $name string - The property to get the value for
|
|
* @param int $sourceOptions Bitmask which can be set to some combintain of Config::UNINHERITED,
|
|
* Config::FIRST_SET, and Config::EXCLUDE_EXTENSIONS.
|
|
*
|
|
* Config::UNINHERITED does not include parent classes when merging configuration fragments
|
|
* Config::FIRST_SET stops inheriting once the first class that sets a value (even an empty value) is encoutered
|
|
* Config::EXCLUDE_EXTRA_SOURCES does not include any additional static sources (such as extensions)
|
|
*
|
|
* Config::INHERITED is a utility constant that can be used to mean "none of the above", equvilient to 0
|
|
* Setting both Config::UNINHERITED and Config::FIRST_SET behaves the same as just Config::UNINHERITED
|
|
*
|
|
* should the parent classes value be merged in as the lowest priority source?
|
|
* @param $result array|scalar Reference to a variable to put the result in. Also returned, so this can be left
|
|
* as null safely. If you do pass a value, it will be treated as the highest priority
|
|
* value in the result chain
|
|
* @param $suppress array Internal use when called by child classes. Array of mask pairs to filter value by
|
|
* @return array|scalar The value of the config item, or null if no value set. Could be an associative array,
|
|
* sequential array or scalar depending on value (see class docblock)
|
|
*/
|
|
public function get($class, $name, $sourceOptions = 0, &$result = null, $suppress = null) {
|
|
// Have we got a cached value? Use it if so
|
|
$key = $class.$name.$sourceOptions;
|
|
|
|
list($cacheHit, $result) = $this->cache->checkAndGet($key);
|
|
if (!$cacheHit) {
|
|
$tags = array();
|
|
$result = null;
|
|
$this->getUncached($class, $name, $sourceOptions, $result, $suppress, $tags);
|
|
$this->cache->set($key, $result, $tags);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Update a configuration value
|
|
*
|
|
* Configuration is modify only. The value passed is merged into the existing configuration. If you want to
|
|
* replace the current array value, you'll need to call remove first.
|
|
*
|
|
* @param string $class The class to update a configuration value for
|
|
* @param string $name The configuration property name to update
|
|
* @param mixed $value The value to update with
|
|
*
|
|
* Arrays are recursively merged into current configuration as "latest" - for associative arrays the passed value
|
|
* replaces any item with the same key, for sequential arrays the items are placed at the end of the array, for
|
|
* non-array values, this value replaces any existing value
|
|
*
|
|
* You will get an error if you try and override array values with non-array values or vice-versa
|
|
*/
|
|
public function update($class, $name, $val) {
|
|
if(is_null($val)) {
|
|
$this->remove($class, $name);
|
|
} else {
|
|
if (!isset($this->overrides[0][$class])) $this->overrides[0][$class] = array();
|
|
|
|
if (!array_key_exists($name, $this->overrides[0][$class])) {
|
|
$this->overrides[0][$class][$name] = $val;
|
|
} else {
|
|
self::merge_high_into_low($this->overrides[0][$class][$name], $val);
|
|
}
|
|
}
|
|
|
|
$this->cache->clean("__{$class}__{$name}");
|
|
}
|
|
|
|
/**
|
|
* Remove a configuration value
|
|
*
|
|
* You can specify a key, a key and a value, or neither. Either argument can be Config::anything(), which is
|
|
* what is defaulted to if you don't specify something
|
|
*
|
|
* This removes any current configuration value that matches the key and/or value specified
|
|
*
|
|
* Works like this:
|
|
* - Check the current override array, and remove any values that match the arguments provided
|
|
* - Keeps track of the arguments passed to this method, and in get filters everything _except_ the current
|
|
* override array to exclude any match
|
|
*
|
|
* This way we can re-set anything removed by a call to this function by calling set. Because the current override
|
|
* array is only filtered immediately on calling this remove method, that value will then be exposed. However,
|
|
* every other source is filtered on request, so no amount of changes to parent's configuration etc can override a
|
|
* remove call.
|
|
*
|
|
* @param string $class The class to remove a configuration value from
|
|
* @param string $name The configuration name
|
|
*
|
|
* Matching is always by "==", not by "==="
|
|
*/
|
|
public function remove($class, $name /*,$key = null*/ /*,$value = null*/) {
|
|
$argc = func_num_args();
|
|
$key = $argc > 2 ? func_get_arg(2) : self::anything();
|
|
$value = $argc > 3 ? func_get_arg(3) : self::anything();
|
|
|
|
$suppress = array($key, $value);
|
|
|
|
if (isset($this->overrides[0][$class][$name])) {
|
|
$value = $this->overrides[0][$class][$name];
|
|
|
|
if (is_array($value)) {
|
|
$this->overrides[0][$class][$name] = self::filter_array_by_suppress_array($value, array($suppress));
|
|
}
|
|
else {
|
|
if (self::check_value_contained_in_suppress_array($value, array($suppress))) {
|
|
unset($this->overrides[0][$class][$name]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isset($this->suppresses[0][$class])) $this->suppresses[0][$class] = array();
|
|
if (!isset($this->suppresses[0][$class][$name])) $this->suppresses[0][$class][$name] = array();
|
|
|
|
$this->suppresses[0][$class][$name][] = $suppress;
|
|
|
|
$this->cache->clean("__{$class}__{$name}");
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @package framework
|
|
* @subpackage core
|
|
* @deprecated 4.0
|
|
*/
|
|
class Config_LRU {
|
|
const SIZE = 1000;
|
|
|
|
protected $cache;
|
|
protected $indexing;
|
|
|
|
protected $i = 0;
|
|
protected $c = 0;
|
|
|
|
public function __construct() {
|
|
Deprecation::notice('4.0', 'Please use Config_MemCache instead', Deprecation::SCOPE_CLASS);
|
|
if (version_compare(PHP_VERSION, '5.3.7', '<') || version_compare(PHP_VERSION, '6.99.99', '>')) {
|
|
// SplFixedArray causes seg faults before PHP 5.3.7, and also in 7.0.0-RC1
|
|
$this->cache = array();
|
|
}
|
|
else {
|
|
$this->cache = new SplFixedArray(self::SIZE);
|
|
}
|
|
|
|
// Pre-fill with stdClass instances. By reusing we avoid object-thrashing
|
|
for ($i = 0; $i < self::SIZE; $i++) {
|
|
$this->cache[$i] = new stdClass();
|
|
$this->cache[$i]->key = null;
|
|
}
|
|
|
|
$this->indexing = array();
|
|
}
|
|
|
|
public function __clone() {
|
|
if (version_compare(PHP_VERSION, '5.3.7', '<')) {
|
|
// SplFixedArray causes seg faults before PHP 5.3.7
|
|
$cloned = array();
|
|
}
|
|
else {
|
|
$cloned = new SplFixedArray(self::SIZE);
|
|
}
|
|
for ($i = 0; $i < self::SIZE; $i++) {
|
|
$cloned[$i] = clone $this->cache[$i];
|
|
}
|
|
$this->cache = $cloned;
|
|
}
|
|
|
|
public function set($key, $val, $tags = array()) {
|
|
// Find an index to set at
|
|
$replacing = null;
|
|
|
|
// Target count - not always the lowest, but guaranteed to exist (or hit an empty item)
|
|
$target = $this->c - self::SIZE + 1;
|
|
$i = $stop = $this->i;
|
|
|
|
do {
|
|
if (!($i--)) $i = self::SIZE-1;
|
|
$item = $this->cache[$i];
|
|
|
|
if ($item->key === null) { $replacing = null; break; }
|
|
else if ($item->c <= $target) { $replacing = $item; break; }
|
|
}
|
|
while ($i != $stop);
|
|
|
|
if ($replacing) unset($this->indexing[$replacing->key]);
|
|
|
|
$this->indexing[$key] = $this->i = $i;
|
|
|
|
$obj = $this->cache[$i];
|
|
$obj->key = $key;
|
|
$obj->value = $val;
|
|
$obj->tags = $tags;
|
|
$obj->c = ++$this->c;
|
|
}
|
|
|
|
private $hit = 0;
|
|
private $miss = 0;
|
|
|
|
public function stats() {
|
|
return $this->miss ? ($this->hit / $this->miss) : 0;
|
|
}
|
|
|
|
/**
|
|
* Return a cached value in the case of a hit, false otherwise.
|
|
* For a more robust cache checking, use {@link checkAndGet()}
|
|
*
|
|
* @param string $key The cache key
|
|
* @return variant Cached value, if hit. False otherwise
|
|
*/
|
|
public function get($key) {
|
|
list($hit, $result) = $this->checkAndGet($key);
|
|
return $hit ? $result : false;
|
|
}
|
|
|
|
/**
|
|
* Checks for a cache hit and looks up the value by returning multiple values.
|
|
* Distinguishes a cached 'false' value from a cache miss.
|
|
*
|
|
* @param string $key The cache key
|
|
* @return array First element boolean, isHit. Second element the actual result.
|
|
*/
|
|
public function checkAndGet($key) {
|
|
if (array_key_exists($key, $this->indexing)) {
|
|
$this->hit++;
|
|
|
|
$res = $this->cache[$this->indexing[$key]];
|
|
$res->c = ++$this->c;
|
|
return array(true, $res->value);
|
|
|
|
} else {
|
|
$this->miss++;
|
|
return array(false, null);
|
|
}
|
|
}
|
|
|
|
public function clean($tag = null) {
|
|
if ($tag) {
|
|
foreach ($this->cache as $i => $v) {
|
|
if ($v->key !== null && in_array($tag, $v->tags)) {
|
|
unset($this->indexing[$v->key]);
|
|
$this->cache[$i]->key = null;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
for ($i = 0; $i < self::SIZE; $i++) $this->cache[$i]->key = null;
|
|
$this->indexing = array();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @package framework
|
|
* @subpackage core
|
|
*/
|
|
class Config_MemCache {
|
|
protected $cache;
|
|
|
|
protected $i = 0;
|
|
protected $c = 0;
|
|
protected $tags = array();
|
|
|
|
public function __construct() {
|
|
$this->cache = array();
|
|
}
|
|
|
|
public function set($key, $val, $tags = array()) {
|
|
foreach($tags as $t) {
|
|
if(!isset($this->tags[$t])) {
|
|
$this->tags[$t] = array();
|
|
}
|
|
$this->tags[$t][$key] = true;
|
|
}
|
|
|
|
$this->cache[$key] = array($val, $tags);
|
|
}
|
|
|
|
private $hit = 0;
|
|
private $miss = 0;
|
|
|
|
public function stats() {
|
|
return $this->miss ? ($this->hit / $this->miss) : 0;
|
|
}
|
|
|
|
public function get($key) {
|
|
list($hit, $result) = $this->checkAndGet($key);
|
|
return $hit ? $result : false;
|
|
}
|
|
|
|
/**
|
|
* Checks for a cache hit and returns the value as a multi-value return
|
|
* @return array First element boolean, isHit. Second element the actual result.
|
|
*/
|
|
public function checkAndGet($key) {
|
|
if(array_key_exists($key, $this->cache)) {
|
|
++$this->hit;
|
|
return array(true, $this->cache[$key][0]);
|
|
} else {
|
|
++$this->miss;
|
|
return array(false, null);
|
|
}
|
|
}
|
|
|
|
public function clean($tag = null) {
|
|
if($tag) {
|
|
if(isset($this->tags[$tag])) {
|
|
foreach($this->tags[$tag] as $k => $dud) {
|
|
// Remove the key from everywhere else it is tagged
|
|
$ts = $this->cache[$k][1];
|
|
foreach($ts as $t) {
|
|
unset($this->tags[$t][$k]);
|
|
}
|
|
unset($this->cache[$k]);
|
|
}
|
|
unset($this->tags[$tag]);
|
|
}
|
|
} else {
|
|
$this->cache = array();
|
|
$this->tags = array();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @package framework
|
|
* @subpackage core
|
|
*/
|
|
class Config_ForClass {
|
|
|
|
/**
|
|
* @var string $class
|
|
*/
|
|
protected $class;
|
|
|
|
/**
|
|
* @param string $class
|
|
*/
|
|
public function __construct($class) {
|
|
$this->class = $class;
|
|
}
|
|
|
|
/**
|
|
* @param string $name
|
|
* @return mixed
|
|
*/
|
|
public function __get($name) {
|
|
return Config::inst()->get($this->class, $name);
|
|
}
|
|
|
|
/**
|
|
* @param string $name
|
|
* @param mixed $val
|
|
*/
|
|
public function __set($name, $val) {
|
|
return Config::inst()->update($this->class, $name, $val);
|
|
}
|
|
|
|
/**
|
|
* @param string $name
|
|
* @return bool
|
|
*/
|
|
public function __isset($name)
|
|
{
|
|
$val = $this->__get($name);
|
|
return isset($val);
|
|
}
|
|
|
|
/**
|
|
* @param string $name
|
|
* @param int $sourceOptions
|
|
*
|
|
* @return array|scalar
|
|
*/
|
|
public function get($name, $sourceOptions = 0) {
|
|
return Config::inst()->get($this->class, $name, $sourceOptions);
|
|
}
|
|
|
|
/**
|
|
* @param string
|
|
*
|
|
* @return Config_ForClass
|
|
*/
|
|
public function forClass($class) {
|
|
return Config::inst()->forClass($class);
|
|
}
|
|
}
|