Permalink
Browse files

FIX changing environment in config.php changes matched yaml rules

  • Loading branch information...
1 parent e74c002 commit 5484283a25d666fa1fb658da1a2bcfe6db3b8730 Hamish Friedlander committed Jul 1, 2013
View
@@ -295,8 +295,12 @@ public function pushConfigStaticManifest(SS_ConfigStaticManifest $manifest) {
* instead, the last manifest to be added always wins
*/
public function pushConfigYamlManifest(SS_ConfigManifest $manifest) {
- array_unshift($this->manifests, $manifest->yamlConfig);
+ 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;
@@ -479,10 +483,13 @@ protected function getUncached($class, $name, $sourceOptions, &$result, $suppres
}
}
+ $value = $nothing = null;
+
// Then the manifest values
foreach($this->manifests as $manifest) {
- if (isset($manifest[$class][$name])) {
- self::merge_low_into_high($result, $manifest[$class][$name], $suppress);
+ $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;
}
}
@@ -9,7 +9,16 @@
*/
class SS_ConfigManifest {
- /**
+ /** @var string - The base path used when building the manifest */
+ protected $base;
+
+ /** @var string - A string to prepend to all cache keys to ensure all keys are unique to just this $base */
+ protected $key;
+
+ /** @var bool - Whether `test` directories should be searched when searching for configuration */
+ protected $includeTests;
+
+ /**
* All the values needed to be collected to determine the correct combination of fragements for
* the current environment.
* @var array
@@ -34,6 +43,17 @@ class SS_ConfigManifest {
*/
public $yamlConfig = array();
+ /**
+ * The variant key state as when yamlConfig was loaded
+ * @var string
+ */
+ protected $yamlConfigVariantKey = null;
+
+ /**
+ * @var [callback] A list of callbacks to be called whenever the content of yamlConfig changes
+ */
+ protected $configChangeCallbacks = array();
+
/**
* A side-effect of collecting the _config fragments is the calculation of all module directories, since
* the definition of a module is "a directory that contains either a _config.php file or a _config directory
@@ -65,6 +85,7 @@ public function moduleExists($module) {
public function __construct($base, $includeTests = false, $forceRegen = false ) {
$this->base = $base;
$this->key = sha1($base).'_';
+ $this->includeTests = $includeTests;
// Get the Zend Cache to load/store cache into
$this->cache = SS_Cache::factory('SS_Configuration', 'Core', array(
@@ -78,21 +99,28 @@ public function __construct($base, $includeTests = false, $forceRegen = false )
$this->phpConfigSources = $this->cache->load($this->key.'php_config_sources');
// Get the variant key spec
$this->variantKeySpec = $this->cache->load($this->key.'variant_key_spec');
- // Try getting the pre-filtered & merged config for this variant
- if (!($this->yamlConfig = $this->cache->load($this->key.'yaml_config_'.$this->variantKey()))) {
- // Otherwise, if we do have the yaml config fragments (and we should since we have a variant key spec)
- // work out the config for this variant
- if ($this->yamlConfigFragments = $this->cache->load($this->key.'yaml_config_fragments')) {
- $this->buildYamlConfigVariant();
- }
- }
}
- // If we don't have a config yet, we need to do a full regen to get it
- if (!$this->yamlConfig) {
+ // If we don't have a variantKeySpec (because we're forcing regen, or it just wasn't in the cache), generate it
+ if (!$this->variantKeySpec) {
$this->regenerate($includeTests);
- $this->buildYamlConfigVariant();
}
+
+ // At this point $this->variantKeySpec will always contain something valid, so we can build the variant
+ $this->buildYamlConfigVariant();
+ }
+
+ /**
+ * Register a callback to be called whenever the calculated merged config changes
+ *
+ * In some situations the merged config can change - for instance, code in _config.php can cause which Only
+ * and Except fragments match. Registering a callback with this function allows code to be called when
+ * this happens.
+ *
+ * @param callback $callback
+ */
+ public function registerChangeCallback($callback) {
+ $this->configChangeCallbacks[] = $callback;
}
/**
@@ -103,6 +131,22 @@ public function activateConfig() {
foreach ($this->phpConfigSources as $config) {
require_once $config;
}
+
+ if ($this->variantKey() != $this->yamlConfigVariantKey) $this->buildYamlConfigVariant();
+ }
+
+ /**
+ * Gets the (merged) config value for the given class and config property name
+ *
+ * @param string $class - The class to get the config property value for
+ * @param string $name - The config property to get the value for
+ * @param any $default - What to return if no value was contained in any YAML file for the passed $class and $name
+ * @return any - The merged set of all values contained in all the YAML configuration files for the passed
+ * $class and $name, or $default if there are none
+ */
+ public function get($class, $name, $default=null) {
+ if (isset($this->yamlConfig[$class][$name])) return $this->yamlConfig[$class][$name];
+ else return $default;
}
/**
@@ -493,9 +537,32 @@ public function addVariantKeySpecRules($rules) {
/**
* Calculates which yaml config fragments are applicable in this variant, and merge those all together into
* the $this->yamlConfig propperty
+ *
+ * Checks cache and takes care of loading yamlConfigFragments if they aren't already present, but expects
+ * $variantKeySpec to already be set
*/
public function buildYamlConfigVariant($cache = true) {
+ // Only try loading from cache if we don't have the fragments already loaded, as there's no way to know if a
+ // given variant is stale compared to the complete set of fragments
+ if (!$this->yamlConfigFragments) {
+ // First try and just load the exact variant
+ if ($this->yamlConfig = $this->cache->load($this->key.'yaml_config_'.$this->variantKey())) {
+ $this->yamlConfigVariantKey = $this->variantKey();
+ return;
+ }
+ // Otherwise try and load the fragments so we can build the variant
+ else {
+ $this->yamlConfigFragments = $this->cache->load($this->key.'yaml_config_fragments');
+ }
+ }
+
+ // If we still don't have any fragments we have to build them
+ if (!$this->yamlConfigFragments) {
+ $this->regenerate($this->includeTests, $cache);
+ }
+
$this->yamlConfig = array();
+ $this->yamlConfigVariantKey = $this->variantKey();
foreach ($this->yamlConfigFragments as $i => $fragment) {
$matches = true;
@@ -514,6 +581,9 @@ public function buildYamlConfigVariant($cache = true) {
if ($cache) {
$this->cache->save($this->yamlConfig, $this->key.'yaml_config_'.$this->variantKey());
}
+
+ // Since yamlConfig has changed, call any callbacks that are interested
+ foreach ($this->configChangeCallbacks as $callback) call_user_func($callback);
}
/**
@@ -8,100 +8,139 @@ public function relativeOrder($a, $b) {
class ConfigManifestTest extends SapphireTest {
- protected function getConfigFixture() {
+ protected function getConfigFixtureValue($name) {
$manifest = new SS_ConfigManifest(dirname(__FILE__).'/fixtures/configmanifest', true, true);
- return $manifest->yamlConfig;
+ return $manifest->get('ConfigManifestTest', $name);
}
public function testClassRules() {
- $config = $this->getConfigFixture();
+ $config = $this->getConfigFixtureValue('Class');
$this->assertEquals(
- 'Yes', @$config['ConfigManifestTest']['Class']['DirectorExists'],
+ 'Yes', @$config['DirectorExists'],
'Only rule correctly detects existing class'
);
$this->assertEquals(
- 'No', @$config['ConfigManifestTest']['Class']['NoSuchClassExists'],
+ 'No', @$config['NoSuchClassExists'],
'Except rule correctly detects missing class'
);
}
public function testModuleRules() {
- $config = $this->getConfigFixture();
+ $config = $this->getConfigFixtureValue('Module');
$this->assertEquals(
- 'Yes', @$config['ConfigManifestTest']['Module']['MysiteExists'],
+ 'Yes', @$config['MysiteExists'],
'Only rule correctly detects existing module'
);
$this->assertEquals(
- 'No', @$config['ConfigManifestTest']['Module']['NoSuchModuleExists'],
+ 'No', @$config['NoSuchModuleExists'],
'Except rule correctly detects missing module'
);
}
public function testEnvVarSetRules() {
$_ENV['EnvVarSet_Foo'] = 1;
- $config = $this->getConfigFixture();
+ $config = $this->getConfigFixtureValue('EnvVarSet');
$this->assertEquals(
- 'Yes', @$config['ConfigManifestTest']['EnvVarSet']['FooSet'],
+ 'Yes', @$config['FooSet'],
'Only rule correctly detects set environment variable'
);
$this->assertEquals(
- 'No', @$config['ConfigManifestTest']['EnvVarSet']['BarSet'],
+ 'No', @$config['BarSet'],
'Except rule correctly detects unset environment variable'
);
}
public function testConstantDefinedRules() {
define('ConstantDefined_Foo', 1);
- $config = $this->getConfigFixture();
+ $config = $this->getConfigFixtureValue('ConstantDefined');
$this->assertEquals(
- 'Yes', @$config['ConfigManifestTest']['ConstantDefined']['FooDefined'],
+ 'Yes', @$config['FooDefined'],
'Only rule correctly detects defined constant'
);
$this->assertEquals(
- 'No', @$config['ConfigManifestTest']['ConstantDefined']['BarDefined'],
+ 'No', @$config['BarDefined'],
'Except rule correctly detects undefined constant'
);
}
public function testEnvOrConstantMatchesValueRules() {
$_ENV['EnvOrConstantMatchesValue_Foo'] = 'Foo';
define('EnvOrConstantMatchesValue_Bar', 'Bar');
- $config = $this->getConfigFixture();
+ $config = $this->getConfigFixtureValue('EnvOrConstantMatchesValue');
$this->assertEquals(
- 'Yes', @$config['ConfigManifestTest']['EnvOrConstantMatchesValue']['FooIsFoo'],
+ 'Yes', @$config['FooIsFoo'],
'Only rule correctly detects environment variable matches specified value'
);
$this->assertEquals(
- 'Yes', @$config['ConfigManifestTest']['EnvOrConstantMatchesValue']['BarIsBar'],
+ 'Yes', @$config['BarIsBar'],
'Only rule correctly detects constant matches specified value'
);
$this->assertEquals(
- 'No', @$config['ConfigManifestTest']['EnvOrConstantMatchesValue']['FooIsQux'],
+ 'No', @$config['FooIsQux'],
'Except rule correctly detects environment variable that doesn\'t match specified value'
);
$this->assertEquals(
- 'No', @$config['ConfigManifestTest']['EnvOrConstantMatchesValue']['BarIsQux'],
+ 'No', @$config['BarIsQux'],
'Except rule correctly detects environment variable that doesn\'t match specified value'
);
$this->assertEquals(
- 'No', @$config['ConfigManifestTest']['EnvOrConstantMatchesValue']['BazIsBaz'],
+ 'No', @$config['BazIsBaz'],
'Except rule correctly detects undefined variable'
);
}
+ public function testEnvironmentRules() {
+ foreach (array('dev', 'test', 'live') as $env) {
+ Config::inst()->nest();
+
+ Config::inst()->update('Director', 'environment_type', $env);
+ $config = $this->getConfigFixtureValue('Environment');
+
+ foreach (array('dev', 'test', 'live') as $check) {
+ $this->assertEquals(
+ $env == $check ? $check : 'not'.$check, @$config[ucfirst($check).'Environment'],
+ 'Only & except rules correctly detect environment'
+ );
+ }
+
+ Config::inst()->unnest();
+ }
+ }
+
+ public function testDynamicEnvironmentRules() {
+ Config::inst()->nest();
+
+ // First, make sure environment_type is live
+ Config::inst()->update('Director', 'environment_type', 'live');
+ $this->assertEquals('live', Config::inst()->get('Director', 'environment_type'));
+
+ // Then, load in a new manifest, which includes a _config.php that sets environment_type to dev
+ $manifest = new SS_ConfigManifest(dirname(__FILE__).'/fixtures/configmanifest_dynamicenv', true, true);
+ Config::inst()->pushConfigYamlManifest($manifest);
+
+ // Make sure that stuck
+ $this->assertEquals('dev', Config::inst()->get('Director', 'environment_type'));
+
+ // And that the dynamic rule was calculated correctly
+ $this->assertEquals('dev', Config::inst()->get('ConfigManifestTest', 'DynamicEnvironment'));
+
+ Config::inst()->unnest();
+ }
+
+
public function testRelativeOrder() {
$accessor = new ConfigManifestTest_ConfigManifestAccess(BASE_PATH, true, false);
@@ -0,0 +1,42 @@
+---
+Only:
+ Environment: live
+---
+ConfigManifestTest:
+ Environment:
+ LiveEnvironment: live
+---
+Only:
+ Environment: dev
+---
+ConfigManifestTest:
+ Environment:
+ DevEnvironment: dev
+---
+Only:
+ Environment: test
+---
+ConfigManifestTest:
+ Environment:
+ TestEnvironment: test
+---
+Except:
+ Environment: live
+---
+ConfigManifestTest:
+ Environment:
+ LiveEnvironment: notlive
+---
+Except:
+ Environment: dev
+---
+ConfigManifestTest:
+ Environment:
+ DevEnvironment: notdev
+---
+Except:
+ Environment: test
+---
+ConfigManifestTest:
+ Environment:
+ TestEnvironment: nottest
@@ -0,0 +1,4 @@
+<?php
+
+// Dynamically change environment
+Config::inst()->update('Director', 'environment_type', 'dev');
Oops, something went wrong.

0 comments on commit 5484283

Please sign in to comment.