mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Substitute core config system with new silverstripe/config module
This commit is contained in:
parent
72ddac2a56
commit
a6e9a7111b
@ -25,8 +25,10 @@
|
||||
"swiftmailer/swiftmailer": "~5.4",
|
||||
"symfony/cache": "^3.3@dev",
|
||||
"symfony/config": "^2.8",
|
||||
"symfony/cache": "^3.1",
|
||||
"symfony/translation": "^2.8",
|
||||
"vlucas/phpdotenv": "^2.4"
|
||||
"vlucas/phpdotenv": "^2.4",
|
||||
"silverstripe/config": "^1@dev"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/PHPUnit": "~4.8",
|
||||
|
@ -997,6 +997,11 @@ specific functions.
|
||||
* Added a server requirement for the php-intl extension (shipped by default with most PHP distributions)
|
||||
* Replaced Zend_Date and Zend_Locale with the php-intl extension.
|
||||
* Consistently use CLDR date formats (rather than a mix of CLDR and date() formats)
|
||||
* Moved config into a new module: [silverstripe/config](https://github.com/silverstripe/silverstripe-config/).
|
||||
See upgrading notes below.
|
||||
* Falsey config values (null, 0, false, etc) can now replace non-falsey values.
|
||||
* Introduced new ModuleLoader manifest, which allows modules to be found via composer name.
|
||||
E.g. `$cms = ModuleLoader::instance()->getManifest()->getModule('silverstripe/cms')`
|
||||
|
||||
#### <a name="overview-general-removed"></a>General and Core Removed API
|
||||
|
||||
@ -1033,6 +1038,7 @@ specific functions.
|
||||
* Removed TextParser and BBCodeParser. These are available in an archived module,
|
||||
[silverstripe-archive/bbcodeparser](https://github.com/silverstripe-archive/silverstripe-bbcodeparser)
|
||||
* Removed `ViewableData::ThemeDir`. Use `ThemeResourceLoader::findThemedResource` in conjunction with `SSViewer::get_themes` instead.
|
||||
* Removed `Config::FIRST_SET` and `Config::INHERITED`
|
||||
|
||||
#### <a name="overview-general-deprecated"></a>General and Core Deprecated API
|
||||
|
||||
@ -1040,6 +1046,8 @@ A very small number of methods were chosen for deprecation, and will be removed
|
||||
|
||||
* `ClassInfo::baseDataClass` - Use `DataObject::getSchema()->baseDataClass()` instead.
|
||||
* `ClassInfo::table_for_object_field` - Use `DataObject::getSchema()->tableForField()` instead
|
||||
* `Config::inst()->update()` is deprecated. Use `Config::modify()->set()` or `Config::modify()->merge()`
|
||||
instead.
|
||||
|
||||
### <a name="overview-orm"></a>ORM API
|
||||
|
||||
@ -1437,6 +1445,11 @@ New `TimeField` methods replace `getConfig()` / `setConfig()`
|
||||
* Removed `DatetimeField`, `DateField` and `TimeField` methods `getConfig` and `setConfig`. Individual
|
||||
getters and setters for individual options are provided instead. See above for list of new methods.
|
||||
* Removed `NumericField_Readonly`. Use `setReadonly(true)` instead.
|
||||
* `SSViewer` deprecated methods removed:
|
||||
* `set_source_file_comments()`
|
||||
* `get_source_file_comments()`
|
||||
* `getOption`
|
||||
* `setOption`
|
||||
|
||||
### <a name="overview-i18n"></a>i18n API
|
||||
|
||||
|
3
main.php
3
main.php
@ -163,7 +163,8 @@ $chain
|
||||
|
||||
// Next, check if we're in dev mode, or the database doesn't have any security data, or we are admin
|
||||
if (Director::isDev() || !Security::database_is_ready() || Permission::check('ADMIN')) {
|
||||
return $reloadToken->reloadWithToken();
|
||||
$reloadToken->reloadWithToken();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fail and redirect the user to the login page
|
||||
|
@ -2,126 +2,15 @@
|
||||
|
||||
namespace SilverStripe\Core\Config;
|
||||
|
||||
use SilverStripe\Core\Object;
|
||||
use SilverStripe\Core\Manifest\ConfigStaticManifest;
|
||||
use SilverStripe\Core\Manifest\ConfigManifest;
|
||||
use UnexpectedValueException;
|
||||
use stdClass;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Config\Collections\ConfigCollectionInterface;
|
||||
use SilverStripe\Config\Collections\MutableConfigCollectionInterface;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
class Config
|
||||
abstract 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;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $collectConfigPHPSettings;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $configPHPIsSafe;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -130,104 +19,44 @@ class Config
|
||||
*/
|
||||
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 mixed $val 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.
|
||||
* instance. It assumes the config instance has already been set.
|
||||
*
|
||||
* @return Config
|
||||
* @return ConfigCollectionInterface
|
||||
*/
|
||||
public static function inst()
|
||||
{
|
||||
if (!self::$instance) {
|
||||
self::$instance = new Config();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
return ConfigLoader::instance()->getManifest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current active {@link Config} instance.
|
||||
* Make this config available to be modified
|
||||
*
|
||||
* {@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
|
||||
* @return MutableConfigCollectionInterface
|
||||
*/
|
||||
public static function set_instance($instance)
|
||||
public static function modify()
|
||||
{
|
||||
self::$instance = $instance;
|
||||
return $instance;
|
||||
$instance = static::inst();
|
||||
if ($instance instanceof MutableConfigCollectionInterface) {
|
||||
return $instance;
|
||||
}
|
||||
|
||||
// By default nested configs should become mutable
|
||||
$instance = static::nest();
|
||||
if ($instance instanceof MutableConfigCollectionInterface) {
|
||||
return $instance;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Nested config could not be made mutable");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,527 +67,48 @@ class Config
|
||||
* 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
|
||||
* @return ConfigCollectionInterface Active config
|
||||
*/
|
||||
public static function nest()
|
||||
{
|
||||
$current = self::$instance;
|
||||
|
||||
$new = clone $current;
|
||||
$new->nestedFrom = $current;
|
||||
return self::set_instance($new);
|
||||
// Clone current config and nest
|
||||
$new = self::inst()->nest();
|
||||
ConfigLoader::instance()->pushManifest($new);
|
||||
return $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
|
||||
* @return ConfigCollectionInterface
|
||||
*/
|
||||
public static function unnest()
|
||||
{
|
||||
if (self::inst()->nestedFrom) {
|
||||
self::set_instance(self::inst()->nestedFrom);
|
||||
} else {
|
||||
// Unnest unless we would be left at 0 manifests
|
||||
$loader = ConfigLoader::instance();
|
||||
if ($loader->countManifests() < 2) {
|
||||
user_error(
|
||||
"Unable to unnest root Config, please make sure you don't have mis-matched nest/unnest",
|
||||
E_USER_WARNING
|
||||
);
|
||||
} else {
|
||||
$loader->popManifest();
|
||||
}
|
||||
return self::inst();
|
||||
return static::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 ConfigStaticManifest $manifest
|
||||
*/
|
||||
public function pushConfigStaticManifest(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
|
||||
* @param ConfigManifest $manifest
|
||||
*/
|
||||
public function pushConfigYamlManifest(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
|
||||
* @param string $class
|
||||
* @return Config_ForClass
|
||||
*/
|
||||
public function forClass($class)
|
||||
public static 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;
|
||||
} elseif (is_int($k)) {
|
||||
$dest[] = $v;
|
||||
} elseif (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;
|
||||
}
|
||||
|
||||
protected static 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;
|
||||
}
|
||||
|
||||
protected static 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 mixed 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 mixed 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 $val 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
|
||||
* @param mixed $key An optional key to filter against.
|
||||
* If referenced config value is an array, only members of that array that match this key will be removed
|
||||
* Must also match value if provided to be removed
|
||||
* @param mixed $value And optional value to filter against.
|
||||
* If referenced config value is an array, only members of that array that match this value will be removed
|
||||
* If referenced config value is not an array, value will be removed only if it matches this argument
|
||||
* Must also match key if provided and referenced config value is an array to be removed
|
||||
*
|
||||
* Matching is always by "==", not by "==="
|
||||
*/
|
||||
public function remove($class, $name, $key = null, $value = null)
|
||||
{
|
||||
if (func_num_args() < 3) {
|
||||
$key = self::anything();
|
||||
}
|
||||
if (func_num_args() < 4) {
|
||||
$value = 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}");
|
||||
return new Config_ForClass($class);
|
||||
}
|
||||
}
|
||||
|
90
src/Core/Config/ConfigLoader.php
Normal file
90
src/Core/Config/ConfigLoader.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Config;
|
||||
|
||||
use SilverStripe\Config\Collections\ConfigCollectionInterface;
|
||||
|
||||
/**
|
||||
* Registers config sources via ConfigCollectionInterface
|
||||
*/
|
||||
class ConfigLoader
|
||||
{
|
||||
/**
|
||||
* @var self
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* @var ConfigCollectionInterface[] map of config collections
|
||||
*/
|
||||
protected $manifests = array();
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public static function instance()
|
||||
{
|
||||
return self::$instance ? self::$instance : self::$instance = new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently active class manifest instance that is used for
|
||||
* loading classes.
|
||||
*
|
||||
* @return ConfigCollectionInterface
|
||||
*/
|
||||
public function getManifest()
|
||||
{
|
||||
return $this->manifests[count($this->manifests) - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this class loader has a manifest.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasManifest()
|
||||
{
|
||||
return (bool)$this->manifests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a class manifest instance onto the top of the stack.
|
||||
*
|
||||
* @param ConfigCollectionInterface $manifest
|
||||
*/
|
||||
public function pushManifest(ConfigCollectionInterface $manifest)
|
||||
{
|
||||
$this->manifests[] = $manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ConfigCollectionInterface
|
||||
*/
|
||||
public function popManifest()
|
||||
{
|
||||
return array_pop($this->manifests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check number of manifests
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countManifests()
|
||||
{
|
||||
return count($this->manifests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nest the current manifest
|
||||
*
|
||||
* @return ConfigCollectionInterface
|
||||
*/
|
||||
public function nest()
|
||||
{
|
||||
$manifest = $this->getManifest()->nest();
|
||||
$this->pushManifest($manifest);
|
||||
return $manifest;
|
||||
}
|
||||
}
|
@ -2,20 +2,21 @@
|
||||
|
||||
namespace SilverStripe\Core\Config;
|
||||
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
class Config_ForClass
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string $class
|
||||
*/
|
||||
protected $class;
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string|object $class
|
||||
*/
|
||||
public function __construct($class)
|
||||
{
|
||||
$this->class = $class;
|
||||
$this->class = is_object($class) ? get_class($class) : $class;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,19 +34,45 @@ class Config_ForClass
|
||||
*/
|
||||
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, $val)
|
||||
public function update($name, $value)
|
||||
{
|
||||
Config::inst()->update($this->class, $name, $val);
|
||||
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 set($name, $value)
|
||||
{
|
||||
Config::modify()->set($this->class, $name, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -61,12 +88,12 @@ class Config_ForClass
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param int $sourceOptions
|
||||
* @param mixed $options
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($name, $sourceOptions = 0)
|
||||
public function get($name, $options = 0)
|
||||
{
|
||||
return Config::inst()->get($this->class, $name, $sourceOptions);
|
||||
return Config::inst()->get($this->class, $name, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,7 +104,7 @@ class Config_ForClass
|
||||
*/
|
||||
public function remove($name)
|
||||
{
|
||||
Config::inst()->remove($this->class, $name);
|
||||
Config::modify()->remove($this->class, $name);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -88,6 +115,17 @@ class Config_ForClass
|
||||
*/
|
||||
public function forClass($class)
|
||||
{
|
||||
return Config::inst()->forClass($class);
|
||||
return Config::forClass($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get uninherited config
|
||||
*
|
||||
* @param string $name Name of config
|
||||
* @return mixed
|
||||
*/
|
||||
public function uninherited($name)
|
||||
{
|
||||
return $this->get($name, Config::UNINHERITED);
|
||||
}
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Config;
|
||||
|
||||
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
|
||||
*
|
||||
* @param string $key
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace SilverStripe\Core\Config;
|
||||
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
/**
|
||||
* Provides extensions to this object to integrate it with standard config API methods.
|
||||
*
|
||||
@ -17,29 +19,19 @@ trait Configurable
|
||||
*/
|
||||
public static function config()
|
||||
{
|
||||
return Config::inst()->forClass(get_called_class());
|
||||
return Config::forClass(get_called_class());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first set value for the given config option
|
||||
* Get inherited config value
|
||||
*
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function stat($name)
|
||||
{
|
||||
return Config::inst()->get(get_class($this), $name, Config::FIRST_SET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the config value for a given property
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function set_stat($name, $value)
|
||||
{
|
||||
Config::inst()->update(get_class($this), $name, $value);
|
||||
Deprecation::notice('5.0', 'Use ->get');
|
||||
return $this->config()->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,6 +42,20 @@ trait Configurable
|
||||
*/
|
||||
public function uninherited($name)
|
||||
{
|
||||
return Config::inst()->get(get_class($this), $name, Config::UNINHERITED);
|
||||
return $this->config()->uninherited($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the config value for a given property
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
*/
|
||||
public function set_stat($name, $value)
|
||||
{
|
||||
Deprecation::notice('5.0', 'Use ->config()->set()');
|
||||
$this->config()->set($name, $value);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
171
src/Core/Config/CoreConfigCreator.php
Normal file
171
src/Core/Config/CoreConfigCreator.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Config;
|
||||
|
||||
use SilverStripe\Config\Collections\CachedConfigCollection;
|
||||
use SilverStripe\Config\Collections\MemoryConfigCollection;
|
||||
use SilverStripe\Config\Transformer\PrivateStaticTransformer;
|
||||
use SilverStripe\Config\Transformer\YamlTransformer;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Config\Middleware\ExtensionMiddleware;
|
||||
use SilverStripe\Core\Config\Middleware\InheritanceMiddleware;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
/**
|
||||
* Factory for silverstripe configs
|
||||
*/
|
||||
class CoreConfigCreator
|
||||
{
|
||||
/**
|
||||
* @var static
|
||||
*/
|
||||
protected static $inst = null;
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public static function inst()
|
||||
{
|
||||
if (!self::$inst) {
|
||||
self::$inst = new static();
|
||||
}
|
||||
return self::$inst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create root application config.
|
||||
* This will be an immutable cached config,
|
||||
* which conditionally generates a nested "core" config.
|
||||
*
|
||||
* @param bool $flush
|
||||
* @return CachedConfigCollection
|
||||
*/
|
||||
public function createRoot($flush)
|
||||
{
|
||||
$instance = new CachedConfigCollection();
|
||||
|
||||
// Set root cache
|
||||
$instance->setPool(new FilesystemAdapter('configcache', 0, getTempFolder()));
|
||||
$instance->setFlush($flush);
|
||||
|
||||
// Set collection creator
|
||||
$instance->setCollectionCreator(function () {
|
||||
return $this->createCore();
|
||||
});
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild new uncached config, which is mutable
|
||||
*
|
||||
* @return MemoryConfigCollection
|
||||
*/
|
||||
public function createCore()
|
||||
{
|
||||
$config = new MemoryConfigCollection();
|
||||
|
||||
// Set default middleware
|
||||
$config->setMiddlewares([
|
||||
new InheritanceMiddleware(Config::UNINHERITED),
|
||||
new ExtensionMiddleware(Config::EXCLUDE_EXTRA_SOURCES),
|
||||
]);
|
||||
|
||||
// Transform
|
||||
$config->transform([
|
||||
$this->buildStaticTransformer(),
|
||||
$this->buildYamlTransformer()
|
||||
]);
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return YamlTransformer
|
||||
*/
|
||||
protected function buildYamlTransformer()
|
||||
{
|
||||
// Get all module dirs
|
||||
$modules = ModuleLoader::instance()->getManifest()->getModules();
|
||||
$dirs = [];
|
||||
foreach ($modules as $module) {
|
||||
// Load from _config dirs
|
||||
$path = $module->getPath() . '/_config';
|
||||
if (is_dir($path)) {
|
||||
$dirs[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->buildYamlTransformerForPath($dirs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PrivateStaticTransformer
|
||||
*/
|
||||
public function buildStaticTransformer()
|
||||
{
|
||||
return new PrivateStaticTransformer(function () {
|
||||
$classes = ClassLoader::instance()->getManifest()->getClasses();
|
||||
return array_keys($classes);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $dirs Base dir to load from
|
||||
* @return YamlTransformer
|
||||
*/
|
||||
public function buildYamlTransformerForPath($dirs)
|
||||
{
|
||||
// Construct
|
||||
$transformer = YamlTransformer::create(
|
||||
BASE_PATH,
|
||||
Finder::create()
|
||||
->in($dirs)
|
||||
->files()
|
||||
->name('/\.(yml|yaml)$/')
|
||||
);
|
||||
|
||||
// Add default rules
|
||||
$envvarset = function ($var, $value = null) {
|
||||
if (getenv($var) === false) {
|
||||
return false;
|
||||
}
|
||||
if ($value) {
|
||||
return getenv($var) === $value;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
$constantdefined = function ($const, $value = null) {
|
||||
if (!defined($const)) {
|
||||
return false;
|
||||
}
|
||||
if ($value) {
|
||||
return constant($const) === $value;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return $transformer
|
||||
->addRule('classexists', function ($class) {
|
||||
return class_exists($class);
|
||||
})
|
||||
->addRule('envvarset', $envvarset)
|
||||
->addRule('constantdefined', $constantdefined)
|
||||
->addRule(
|
||||
'envorconstant',
|
||||
// Composite rule
|
||||
function ($name, $value = null) use ($envvarset, $constantdefined) {
|
||||
return $envvarset($name, $value) || $constantdefined($name, $value);
|
||||
}
|
||||
)
|
||||
->addRule('environment', function ($env) {
|
||||
$current = Director::get_environment_type();
|
||||
return strtolower($current) === strtolower($env);
|
||||
})
|
||||
->addRule('moduleexists', function ($module) {
|
||||
return ModuleLoader::instance()->getManifest()->moduleExists($module);
|
||||
});
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Config;
|
||||
|
||||
use Exception;
|
||||
use IteratorAggregate;
|
||||
|
||||
/**
|
||||
* A Directed Acyclic Graph - used for doing topological sorts on dependencies, such as the before/after conditions
|
||||
* in config yaml fragments
|
||||
*/
|
||||
class DAG implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* The nodes/vertices in the graph. Should be a numeric sequence of items (no string keys, no gaps).
|
||||
* @var array|null
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* The edges in the graph, in $to_idx => [$from_idx1, $from_idx2, ...] format
|
||||
* @var array
|
||||
*/
|
||||
protected $dag;
|
||||
|
||||
public function __construct($data = null)
|
||||
{
|
||||
$data = $data ? array_values($data) : array();
|
||||
|
||||
$this->data = $data;
|
||||
$this->dag = array_fill_keys(array_keys($data), array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add another node/vertex
|
||||
* @param mixed $item The item to add to the graph
|
||||
*/
|
||||
public function additem($item)
|
||||
{
|
||||
$this->data[] = $item;
|
||||
$this->dag[] = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an edge from one vertex to another.
|
||||
*
|
||||
* When passing actual nodes (as opposed to indexes), uses array_search with strict = true to find
|
||||
*
|
||||
* @param int $from The index in $data of the node/vertex, or the node/vertex
|
||||
* itself, that the edge goes from
|
||||
* @param int $to The index in $data of the node/vertex, or the node/vertex
|
||||
* itself, that the edge goes to
|
||||
* @throws Exception
|
||||
*/
|
||||
public function addedge($from, $to)
|
||||
{
|
||||
$i = is_numeric($from) ? $from : array_search($from, $this->data, true);
|
||||
$j = is_numeric($to) ? $to : array_search($to, $this->data, true);
|
||||
|
||||
if ($i === false) {
|
||||
throw new Exception("Couldnt find 'from' item in data when adding edge to DAG");
|
||||
}
|
||||
if ($j === false) {
|
||||
throw new Exception("Couldnt find 'to' item in data when adding edge to DAG");
|
||||
}
|
||||
|
||||
if (!isset($this->dag[$j])) {
|
||||
$this->dag[$j] = array();
|
||||
}
|
||||
$this->dag[$j][] = $i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort graph so that each node (a) comes before any nodes (b) where an edge exists from a to b
|
||||
* @return array - The nodes
|
||||
* @throws Exception - If the graph is cyclic (and so can't be sorted)
|
||||
*/
|
||||
public function sort()
|
||||
{
|
||||
$data = $this->data;
|
||||
$dag = $this->dag;
|
||||
$sorted = array();
|
||||
|
||||
while (true) {
|
||||
$withedges = array_filter($dag, 'count');
|
||||
$starts = array_diff_key($dag, $withedges);
|
||||
|
||||
if (!count($starts)) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($starts as $i => $foo) {
|
||||
$sorted[] = $data[$i];
|
||||
}
|
||||
|
||||
foreach ($withedges as $j => $deps) {
|
||||
$withedges[$j] = array_diff($withedges[$j], array_keys($starts));
|
||||
}
|
||||
|
||||
$dag = $withedges;
|
||||
}
|
||||
|
||||
if ($dag) {
|
||||
$remainder = new DAG($data);
|
||||
$remainder->dag = $dag;
|
||||
throw new DAG_CyclicException("DAG has cyclic requirements", $remainder);
|
||||
}
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
public function getIterator()
|
||||
{
|
||||
return new DAG_Iterator($this->data, $this->dag);
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Config;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when the {@link SilverStripe\Core\Config\DAG} class is unable to resolve sorting the DAG due
|
||||
* to cyclic dependencies.
|
||||
*/
|
||||
class DAG_CyclicException extends Exception
|
||||
{
|
||||
|
||||
public $dag;
|
||||
|
||||
/**
|
||||
* @param string $message The Exception message
|
||||
* @param DAG $dag The remainder of the Directed Acyclic Graph (DAG) after the last successful sort
|
||||
*/
|
||||
public function __construct($message, $dag)
|
||||
{
|
||||
$this->dag = $dag;
|
||||
parent::__construct($message);
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Config;
|
||||
|
||||
use Iterator;
|
||||
|
||||
class DAG_Iterator implements Iterator
|
||||
{
|
||||
|
||||
protected $data;
|
||||
protected $dag;
|
||||
|
||||
protected $dagkeys;
|
||||
protected $i;
|
||||
|
||||
public function __construct($data, $dag)
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->dag = $dag;
|
||||
$this->rewind();
|
||||
}
|
||||
|
||||
public function key()
|
||||
{
|
||||
return $this->i;
|
||||
}
|
||||
|
||||
public function current()
|
||||
{
|
||||
$res = array();
|
||||
|
||||
$res['from'] = $this->data[$this->i];
|
||||
|
||||
$res['to'] = array();
|
||||
foreach ($this->dag[$this->i] as $to) {
|
||||
$res['to'][] = $this->data[$to];
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function next()
|
||||
{
|
||||
$this->i = array_shift($this->dagkeys);
|
||||
}
|
||||
|
||||
public function rewind()
|
||||
{
|
||||
$this->dagkeys = array_keys($this->dag);
|
||||
$this->next();
|
||||
}
|
||||
|
||||
public function valid()
|
||||
{
|
||||
return $this->i !== null;
|
||||
}
|
||||
}
|
84
src/Core/Config/Middleware/ExtensionMiddleware.php
Normal file
84
src/Core/Config/Middleware/ExtensionMiddleware.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Config\Middleware;
|
||||
|
||||
use Generator;
|
||||
use InvalidArgumentException;
|
||||
use SilverStripe\Config\MergeStrategy\Priority;
|
||||
use SilverStripe\Config\Middleware\Middleware;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Object;
|
||||
|
||||
class ExtensionMiddleware implements \SilverStripe\Config\Middleware\Middleware
|
||||
{
|
||||
use MiddlewareCommon;
|
||||
|
||||
/**
|
||||
* Get config for a class
|
||||
*
|
||||
* @param string $class
|
||||
* @param mixed $options
|
||||
* @param callable $next
|
||||
* @return string
|
||||
*/
|
||||
public function getClassConfig($class, $options, $next)
|
||||
{
|
||||
// Get base config
|
||||
$config = $next($class, $options);
|
||||
|
||||
if (!$this->enabled($options)) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
foreach ($this->getExtraConfig($class, $config) as $extra) {
|
||||
$config = Priority::mergeArray($extra, $config);
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applied config to a class from its extensions
|
||||
*
|
||||
* @param string $class
|
||||
* @param array $classConfig
|
||||
* @return Generator
|
||||
*/
|
||||
protected function getExtraConfig($class, $classConfig)
|
||||
{
|
||||
if (empty($classConfig['extensions'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$extensions = $classConfig['extensions'];
|
||||
foreach ($extensions as $extension) {
|
||||
list($extensionClass, $extensionArgs) = Object::parse_class_spec($extension);
|
||||
if (!class_exists($extensionClass)) {
|
||||
throw new InvalidArgumentException("$class references nonexistent $extensionClass in 'extensions'");
|
||||
}
|
||||
|
||||
// Init extension
|
||||
call_user_func(array($extensionClass, 'add_to_class'), $class, $extensionClass, $extensionArgs);
|
||||
|
||||
// Check class hierarchy from root up
|
||||
foreach (ClassInfo::ancestry($extensionClass) as $extensionClassParent) {
|
||||
// Merge config from extension
|
||||
$extensionConfig = Config::inst()->get($extensionClassParent, null, true);
|
||||
if ($extensionConfig) {
|
||||
yield $extensionConfig;
|
||||
}
|
||||
if (ClassInfo::has_method_from($extensionClassParent, 'get_extra_config', $extensionClassParent)) {
|
||||
$extensionConfig = call_user_func(
|
||||
[ $extensionClassParent, 'get_extra_config' ],
|
||||
$class,
|
||||
$extensionClass,
|
||||
$extensionArgs
|
||||
);
|
||||
if ($extensionConfig) {
|
||||
yield $extensionConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
src/Core/Config/Middleware/InheritanceMiddleware.php
Normal file
36
src/Core/Config/Middleware/InheritanceMiddleware.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Config\Middleware;
|
||||
|
||||
use SilverStripe\Config\MergeStrategy\Priority;
|
||||
use SilverStripe\Config\Middleware\Middleware;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
|
||||
class InheritanceMiddleware implements Middleware
|
||||
{
|
||||
use MiddlewareCommon;
|
||||
|
||||
/**
|
||||
* Get config for a class
|
||||
*
|
||||
* @param string $class
|
||||
* @param mixed $options
|
||||
* @param callable $next
|
||||
* @return string
|
||||
*/
|
||||
public function getClassConfig($class, $options, $next)
|
||||
{
|
||||
// Check if enabled
|
||||
if (!$this->enabled($options)) {
|
||||
return $next($class, $options);
|
||||
}
|
||||
|
||||
// Merge hierarchy
|
||||
$config = [];
|
||||
foreach (ClassInfo::ancestry($class) as $nextClass) {
|
||||
$nextConfig = $next($nextClass, $options);
|
||||
$config = Priority::mergeArray($nextConfig, $config);
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
}
|
49
src/Core/Config/Middleware/MiddlewareCommon.php
Normal file
49
src/Core/Config/Middleware/MiddlewareCommon.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Config\Middleware;
|
||||
|
||||
/**
|
||||
* Abstract flag-aware middleware
|
||||
*/
|
||||
trait MiddlewareCommon
|
||||
{
|
||||
/**
|
||||
* Disable flag
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $disableFlag = 0;
|
||||
|
||||
public function __construct($disableFlag = 0)
|
||||
{
|
||||
$this->disableFlag = $disableFlag;
|
||||
}
|
||||
|
||||
protected function enabled($options)
|
||||
{
|
||||
if ($options === true) {
|
||||
return false;
|
||||
}
|
||||
if (!$this->disableFlag) {
|
||||
return true;
|
||||
}
|
||||
if (is_array($options)) {
|
||||
if (!isset($options['disableFlag'])) {
|
||||
return true;
|
||||
}
|
||||
$options = $options['disableFlag'];
|
||||
}
|
||||
|
||||
return ($options & $this->disableFlag) !== $this->disableFlag;
|
||||
}
|
||||
|
||||
public function serialize()
|
||||
{
|
||||
return json_encode([$this->disableFlag]);
|
||||
}
|
||||
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
list($this->disableFlag) = json_decode($serialized, true);
|
||||
}
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Config\CoreConfigCreator;
|
||||
use SilverStripe\Core\Config\ConfigLoader;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Core\Injector\SilverStripeServiceConfigurationLocator;
|
||||
use SilverStripe\Core\Manifest\ClassManifest;
|
||||
use SilverStripe\Core\Manifest\ClassLoader;
|
||||
use SilverStripe\Core\Manifest\ConfigStaticManifest;
|
||||
use SilverStripe\Core\Manifest\ConfigManifest;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||
use SilverStripe\i18n\i18n;
|
||||
|
||||
/**
|
||||
@ -56,7 +58,7 @@ gc_enable();
|
||||
|
||||
// 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'));
|
||||
$injector = new Injector(array('locator' => SilverStripeServiceConfigurationLocator::class));
|
||||
Injector::set_inst($injector);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -76,13 +78,16 @@ $loader = ClassLoader::instance();
|
||||
$loader->registerAutoloader();
|
||||
$loader->pushManifest($manifest);
|
||||
|
||||
// Now that the class manifest is up, load the static configuration
|
||||
$configManifest = new ConfigStaticManifest();
|
||||
Config::inst()->pushConfigStaticManifest($configManifest);
|
||||
// Init module manifest
|
||||
$moduleManifest = new ModuleManifest(BASE_PATH, false, $flush);
|
||||
ModuleLoader::instance()->pushManifest($moduleManifest);
|
||||
|
||||
// And then the yaml configuration
|
||||
$configManifest = new ConfigManifest(BASE_PATH, false, $flush);
|
||||
Config::inst()->pushConfigYamlManifest($configManifest);
|
||||
// Build config manifest
|
||||
$configManifest = CoreConfigCreator::inst()->createRoot($flush);
|
||||
ConfigLoader::instance()->pushManifest($configManifest);
|
||||
|
||||
// After loading config, boot _config.php files
|
||||
ModuleLoader::instance()->getManifest()->activateConfig();
|
||||
|
||||
// Load template manifest
|
||||
SilverStripe\View\ThemeResourceLoader::instance()->addSet('$default', new SilverStripe\View\ThemeManifest(
|
||||
@ -103,7 +108,6 @@ if (Director::isLive()) {
|
||||
/**
|
||||
* Load error handlers
|
||||
*/
|
||||
|
||||
$errorHandler = Injector::inst()->get('ErrorHandler');
|
||||
$errorHandler->start();
|
||||
|
||||
|
@ -122,11 +122,7 @@ trait Extensible
|
||||
if (in_array($class, self::$unextendable_classes)) {
|
||||
continue;
|
||||
}
|
||||
$extensions = Config::inst()->get(
|
||||
$class,
|
||||
'extensions',
|
||||
Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES
|
||||
);
|
||||
$extensions = Config::inst()->get($class, 'extensions', true);
|
||||
|
||||
if ($extensions) {
|
||||
foreach ($extensions as $extension) {
|
||||
@ -217,8 +213,10 @@ trait Extensible
|
||||
}
|
||||
}
|
||||
|
||||
Config::inst()->update($class, 'extensions', array($extension));
|
||||
Config::inst()->extraConfigSourcesChanged($class);
|
||||
Config::modify()
|
||||
->merge($class, 'extensions', array(
|
||||
$extension
|
||||
));
|
||||
|
||||
Injector::inst()->unregisterNamedObject($class);
|
||||
|
||||
@ -234,6 +232,8 @@ trait Extensible
|
||||
|
||||
/**
|
||||
* Remove an extension from a class.
|
||||
* Note: This will not remove extensions from parent classes, and must be called
|
||||
* directly on the class assigned the extension.
|
||||
*
|
||||
* Keep in mind that this won't revert any datamodel additions
|
||||
* of the extension at runtime, unless its used before the
|
||||
@ -252,22 +252,23 @@ trait Extensible
|
||||
{
|
||||
$class = get_called_class();
|
||||
|
||||
Config::inst()->remove($class, 'extensions', Config::anything(), $extension);
|
||||
|
||||
// remove any instances of the extension with parameters
|
||||
$config = Config::inst()->get($class, 'extensions');
|
||||
|
||||
if ($config) {
|
||||
foreach ($config as $k => $v) {
|
||||
// extensions with parameters will be stored in config as
|
||||
// ExtensionName("Param").
|
||||
if (preg_match(sprintf("/^(%s)\(/", preg_quote($extension, '/')), $v)) {
|
||||
Config::inst()->remove($class, 'extensions', Config::anything(), $v);
|
||||
}
|
||||
// Build filtered extension list
|
||||
$found = false;
|
||||
$config = Config::inst()->get($class, 'extensions', true) ?: [];
|
||||
foreach ($config as $key => $candidate) {
|
||||
// extensions with parameters will be stored in config as ExtensionName("Param").
|
||||
if (strcasecmp($candidate, $extension) === 0 ||
|
||||
stripos($candidate, $extension.'(') === 0
|
||||
) {
|
||||
$found = true;
|
||||
unset($config[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
Config::inst()->extraConfigSourcesChanged($class);
|
||||
// Don't dirty cache if no changes
|
||||
if (!$found) {
|
||||
return;
|
||||
}
|
||||
Config::modify()->set($class, 'extensions', $config);
|
||||
|
||||
// unset singletons to avoid side-effects
|
||||
Injector::inst()->unregisterAllObjects();
|
||||
@ -292,7 +293,7 @@ trait Extensible
|
||||
*/
|
||||
public static function get_extensions($class, $includeArgumentString = false)
|
||||
{
|
||||
$extensions = Config::inst()->get($class, 'extensions');
|
||||
$extensions = Config::forClass($class)->get('extensions', Config::EXCLUDE_EXTRA_SOURCES);
|
||||
if (empty($extensions)) {
|
||||
return array();
|
||||
}
|
||||
@ -329,7 +330,7 @@ trait Extensible
|
||||
$sources = null;
|
||||
|
||||
// Get a list of extensions
|
||||
$extensions = Config::inst()->get($class, 'extensions', Config::UNINHERITED | Config::EXCLUDE_EXTRA_SOURCES);
|
||||
$extensions = Config::inst()->get($class, 'extensions', true);
|
||||
|
||||
if (!$extensions) {
|
||||
return null;
|
||||
|
@ -21,7 +21,7 @@ abstract class Extension
|
||||
* This is used by extensions designed to be applied to controllers.
|
||||
* It works the same way as {@link Controller::$allowed_actions}.
|
||||
*/
|
||||
private static $allowed_actions = null;
|
||||
private static $allowed_actions = [];
|
||||
|
||||
/**
|
||||
* The object this extension is applied to.
|
||||
|
@ -386,8 +386,8 @@ class ClassManifest
|
||||
*/
|
||||
protected function setDefaults()
|
||||
{
|
||||
$this->classes['sstemplateparser'] = FRAMEWORK_PATH.'/View/SSTemplateParser.php';
|
||||
$this->classes['sstemplateparseexception'] = FRAMEWORK_PATH.'/View/SSTemplateParseException.php';
|
||||
$this->classes['sstemplateparser'] = FRAMEWORK_PATH.'/src/View/SSTemplateParser.php';
|
||||
$this->classes['sstemplateparseexception'] = FRAMEWORK_PATH.'/src/View/SSTemplateParseException.php';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Core\Manifest;
|
||||
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Allows access to config values set on classes using private statics.
|
||||
*/
|
||||
class ConfigStaticManifest
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($class, $name)
|
||||
{
|
||||
if (class_exists($class)) {
|
||||
// The config system is case-sensitive so we need to check the exact value
|
||||
$reflection = new ReflectionClass($class);
|
||||
if (strcmp($reflection->name, $class) === 0) {
|
||||
if ($reflection->hasProperty($name)) {
|
||||
$property = $reflection->getProperty($name);
|
||||
if ($property->isStatic() && $property->isPrivate()) {
|
||||
$property->setAccessible(true);
|
||||
return $property->getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user