Created
October 18, 2014 01:39
-
-
Save jm42/1b06e44fed29628e3127 to your computer and use it in GitHub Desktop.
Two-Step Configuration Idea
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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]); | |
| } | |
| }); | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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