Skip to content

Commit

Permalink
Add a ConfigOverlay class.
Browse files Browse the repository at this point in the history
  • Loading branch information
greg-1-anderson committed Jul 31, 2017
1 parent 60795cb commit 3a7b22c
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 8 deletions.
8 changes: 4 additions & 4 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ public function has($key)
/**
* {@inheritdoc}
*/
public function get($key, $defaultOverride = null)
public function get($key, $defaultFallback = null)
{
if ($this->has($key)) {
return $this->config->get($key);
}
return $this->getDefault($key, $defaultOverride);
return $this->getDefault($key, $defaultFallback);
}

/**
Expand Down Expand Up @@ -84,9 +84,9 @@ public function hasDefault($key)
/**
* {@inheritdoc}
*/
public function getDefault($key, $defaultOverride = null)
public function getDefault($key, $defaultFallback = null)
{
return $this->hasDefault($key) ? $this->defaults[$key] : $defaultOverride;
return $this->hasDefault($key) ? $this->defaults[$key] : $defaultFallback;
}

/**
Expand Down
11 changes: 7 additions & 4 deletions src/ConfigInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ public function has($key);
* Fetch a configuration value
*
* @param string $key Which config item to look up
* @param string|null $defaultOverride Override usual default value with a different default. Deprecated; provide defaults to the config processor instead.
* @param string|null $defaultFallback Fallback default value to use when
* configuration object has neither a value nor a default. Use is
* discouraged; use default context in ConfigOverlay, or provide defaults
* using a config processor.
*
* @return mixed
*/
public function get($key, $defaultOverride = null);
public function get($key, $defaultFallback = null);

/**
* Set a config value
Expand Down Expand Up @@ -55,11 +58,11 @@ public function hasDefault($key);
* Return the default value for a given configuration item.
*
* @param string $key
* @param mixed $defaultOverride
* @param mixed $defaultFallback
*
* @return mixed
*/
public function getDefault($key, $defaultOverride = null);
public function getDefault($key, $defaultFallback = null);

/**
* Set the default value for a configuration setting. This allows us to
Expand Down
134 changes: 134 additions & 0 deletions src/ConfigOverlay.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php
namespace Consolidation\Config;

/**
* Overlay different configuration objects that implement ConfigInterface
* to make a priority-based, merged configuration object.
*
* Note that using a ConfigOverlay hides the defaults stored in each
* individual configuration context. When using overlays, always call
* getDefault / setDefault on the ConfigOverlay object itself.
*/
class ConfigOverlay implements ConfigInterface
{
protected $contexts = [];

public function __construct()
{
$this->contexts['default'] = new Config();
$this->contexts['process'] = new Config();
}

/**
* Add a named configuration object to the configuration overlay.
* Configuration objects added LAST have HIGHEST priority, with the
* exception of the fact that the process context always has the
* highest priority.
*/
public function addContext($name, ConfigInterface $config)
{
$process = $this->contexts['process'];
unset($this->contexts['process']);
unset($this->contexts[$name]);
$this->contexts[$name] = $config;
$this->contexts['process'] = $process;
}

public function hasContext($name)
{
return isset($this->contexts[$name]);
}

public function getContext($name)
{
if ($this->hasContext($name)) {
return $this->contexts[$name];
}
return new Config();
}

/**
* Determine if a non-default config value exists.
*/
public function findContext($key)
{
foreach (array_reverse($this->contexts) as $name => $config) {
if ($config->has($key)) {
return $config;
}
}
return false;
}

/**
* @inheritdoc
*/
public function has($key)
{
return $this->findContext($key) != false;
}

/**
* @inheritdoc
*/
public function get($key, $default = null)
{
$context = $this->findContext($key);
if ($context) {
return $context->get($key, $default);
}
return $default;
}

/**
* @inheritdoc
*/
public function set($key, $value)
{
return $this->contexts['process']->set($key, $value);
}

/**
* @inheritdoc
*/
public function import($data)
{
throw new \Exception('The method "import" is not supported for the ConfigOverlay class.');
}

/**
* @inheritdoc
*/
public function export()
{
$export = [];
foreach ($this->contexts as $name => $config) {
$export = array_merge_recursive($export, $config->export());
}
return $export;
}

/**
* @inheritdoc
*/
public function hasDefault($key)
{
return $this->contexts['default']->has($key);
}

/**
* @inheritdoc
*/
public function getDefault($key, $default = null)
{
return $this->contexts['default']->get($key, $default);
}

/**
* @inheritdoc
*/
public function setDefault($key, $value)
{
return $this->contexts['default']->set($key, $value);
}
}
114 changes: 114 additions & 0 deletions tests/ConfigOverlayTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php
namespace Consolidation\Config;

use Consolidation\Config\Loader\ConfigProcessor;
use Consolidation\Config\Loader\YamlConfigLoader;

class ConfigOverlayTest extends \PHPUnit_Framework_TestCase
{
protected $overlay;

protected function setUp()
{
$aliasContext = new Config();
$aliasContext->import([
'hidden-by-a' => 'alias hidden-by-a',
'hidden-by-process' => 'alias hidden-by-process',
'options' =>[
'a-a' => 'alias-a',
],
'command' => [
'foo' => [
'bar' => [
'command' => [
'options' => [
'a-b' => 'alias-b',
],
],
],
],
],
]);

$configFileContext = new Config();
$configFileContext->import([
'hidden-by-cf' => 'config-file hidden-by-cf',
'hidden-by-a' => 'config-file hidden-by-a',
'hidden-by-process' => 'config-file hidden-by-process',
'options' =>[
'cf-a' => 'config-file-a',
],
'command' => [
'foo' => [
'bar' => [
'command' => [
'options' => [
'cf-b' => 'config-file-b',
],
],
],
],
],
]);

$this->overlay = new ConfigOverlay();
$this->overlay->set('hidden-by-process', 'process-h');
$this->overlay->addContext('cf', $configFileContext);
$this->overlay->addContext('a', $aliasContext);
$this->overlay->setDefault('df-a', 'default');
$this->overlay->setDefault('hidden-by-a', 'default hidden-by-a');
$this->overlay->setDefault('hidden-by-cf', 'default hidden-by-cf');
$this->overlay->setDefault('hidden-by-process', 'default hidden-by-process');
}

public function testGetPriority()
{
$this->assertEquals('process-h', $this->overlay->get('hidden-by-process'));
$this->assertEquals('config-file hidden-by-cf', $this->overlay->get('hidden-by-cf'));
$this->assertEquals('alias hidden-by-a', $this->overlay->get('hidden-by-a'));
}

public function testDefault()
{
$this->assertEquals('alias-a', $this->overlay->get('options.a-a'));
$this->assertEquals('alias-a', $this->overlay->get('options.a-a', 'ignored'));
$this->assertEquals('default', $this->overlay->getDefault('df-a', 'ignored'));
$this->assertEquals('nsv', $this->overlay->getDefault('a-a', 'nsv'));

$this->overlay->setDefault('df-a', 'new value');
$this->assertEquals('new value', $this->overlay->getDefault('df-a', 'ignored'));
}

public function testExport()
{
$data = $this->overlay->export();

$this->assertEquals('config-file-a', $data['options']['cf-a']);
$this->assertEquals('alias-a', $data['options']['a-a']);
}

/**
* @expectedException Exception
*/
public function testImport()
{
$data = $this->overlay->import(['a' => 'value']);
}

public function testChangePriority()
{
// Get and re-add the 'cf' context. Now, it should have a higher
// priority than the 'alias' context, but should still have a lower
// priority than the 'process' context.
$configFileContext = $this->overlay->getContext('cf');
$this->overlay->addContext('cf', $configFileContext);

// These asserts are the same as in testGetPriority
$this->assertEquals('process-h', $this->overlay->get('hidden-by-process'));
$this->assertEquals('config-file hidden-by-cf', $this->overlay->get('hidden-by-cf'));

// This one has changed: the config-file value is now found instead
// of the alias value.
$this->assertEquals('config-file hidden-by-a', $this->overlay->get('hidden-by-a'));
}
}

0 comments on commit 3a7b22c

Please sign in to comment.