Skip to content

Instantly share code, notes, and snippets.

@jm42
Created October 18, 2014 01:39
Show Gist options
  • Save jm42/1b06e44fed29628e3127 to your computer and use it in GitHub Desktop.
Save jm42/1b06e44fed29628e3127 to your computer and use it in GitHub Desktop.
Two-Step Configuration Idea
<?php
class Configurator implements ArrayAccess {
const PHASE_DEFAULT = 0;
private $config = [];
private $defaults = [];
private $queue;
private $hooks;
private $order;
public function __construct() {
$this->rollback();
$this->hooks = [];
}
protected function register($key, callable $callable, $phase=0) {
$this->queue->insert([$key, $callable], [$phase, --$this->order]);
}
protected function resolve() {
$phases = array();
foreach ($this->queue as $action) {
$phase = $action['priority'][0];
$key = $action['data'][0];
$fn = $action['data'][1];
if (!isset($phases[$phase])) {
$phases[$phase] = [];
}
if (!isset($phases[$phase][$key])) {
$phases[$phase][$key] = [];
}
$phases[$phase][$key][] = $fn;
}
unset($fn);
unset($key);
unset($phase);
unset($action);
$conflicts = array();
$callbacks = array();
foreach ($phases as $phase => $actions) {
if (isset($this->hooks[$phase])) {
foreach ($this->hooks[$phase] as $hook) {
call_user_func($hook);
}
}
foreach ($actions as $key => $fn) {
if (count($fn) > 1) {
$conflicts[$key] = true;
} else {
$callbacks[] = array_pop($fn);
}
}
}
if (count($conflicts) > 0) {
throw new RuntimeException('Conflicts detected');
}
foreach ($callbacks as $callback) {
call_user_func($callback);
}
}
public function rollback() {
unset($this->queue);
$this->queue = new SplPriorityQueue;
$this->queue->setExtractFlags(SplPriorityQueue::EXTR_BOTH);
$this->order = PHP_INT_MAX;
}
public function commit() {
$this->resolve();
$this->rollback();
}
public function hook($phase, callable $callback) {
if (!isset($this->hooks[$phase])) {
$this->hooks[$phase] = [];
}
$this->hooks[$phase][] = $callback;
}
public function load(array $config) {
foreach ($config as $key => $value) {
$this->offsetSet($key, $value);
}
}
public function setdefault($key, $default=null) {
$this->defaults[$key] = $default;
}
public function offsetExists($key) {
return array_key_exists($key, $this->config) ||
array_key_exists($key, $this->defaults);
}
public function offsetGet($key) {
if (array_key_exists($key, $this->config)) {
return $this->config[$key];
}
if (array_key_exists($key, $this->defaults)) {
return $this->defaults[$key];
}
throw new RuntimeException("Key $key not found");
}
public function offsetSet($key, $value) {
$this->register($key, function() use ($key, $value) {
$this->config[$key] = $value;
});
}
public function offsetUnset($key) {
$this->register($key, function() use ($key) {
if (array_key_exists($key, $this->config)) {
unset($this->config[$key]);
}
});
}
}
<?php
require_once 'config.php';
class ConfiguratorTest extends \PHPUnit_Framework_TestCase {
public function testSet() {
$c = new Configurator;
$c['foo'] = 'bar';
$this->assertFalse(isset($c['foo']));
$c->commit();
$this->assertTrue(isset($c['foo']));
$this->assertEquals('bar', $c['foo']);
}
public function testUnset() {
$c = new Configurator;
$c['foo'] = 'hello';
$c->commit();
$this->assertTrue(isset($c['foo']));
unset($c['foo']);
$this->assertTrue(isset($c['foo']));
$c->commit();
$this->assertFalse(isset($c['foo']));
}
/**
* @expectedException RuntimeException
* @excectedExceptionMessage Key foo not found
*/
public function testGetKeyNotFound() {
$c = new Configurator;
$c['foo'];
}
public function testSetDefault() {
$c = new Configurator;
$c->setdefault('foo', 'bar');
$this->assertTrue(isset($c['foo']));
$this->assertEquals('bar', $c['foo']);
}
public function testHook() {
$c = new Configurator;
$c['hook'] = false;
$c->hook(Configurator::PHASE_DEFAULT, function() use ($c) {
$c['hook'] = true;
});
$c->commit();
$this->assertTrue(isset($c['hook']));
$this->assertFalse($c['hook']);
}
public function testLoad() {
$c = new Configurator;
$c->load(array('foo' => 'bar', 'bar' => 'baz'));
$c->commit();
$this->assertEquals('bar', $c['foo']);
$this->assertEquals('baz', $c['bar']);
}
/**
* @expectedException RuntimeException
* @excectedExceptionMessage Conflicts detected
*/
public function testConflictsDetected() {
$c = new Configurator;
$c->load(array('foo' => 'baz'));
$c['foo'] = 'bar';
$c->commit();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment