1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-22 13:30:55 +01:00

Formalize configuration sources and source stacks

Summary: Currently, we have a configuration stack for unit tests, but they're built in to `PhabricatorEnv`. Pull them out and formalize them, so we can add more configuration sources (e.g., database).

Test Plan: Ran unit tests, web requests, scripts. This code had fairly good existing test coverage.

Reviewers: btrahan, vrana

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T2223, T2221

Differential Revision: https://secure.phabricator.com/D4284
This commit is contained in:
epriestley 2012-12-25 06:44:29 -08:00
parent 3eb370a533
commit 19b2c3d3d0
9 changed files with 315 additions and 50 deletions

View file

@ -679,6 +679,11 @@ phutil_register_library_map(array(
'PhabricatorConduitLogController' => 'applications/conduit/controller/PhabricatorConduitLogController.php',
'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php',
'PhabricatorConduitTokenController' => 'applications/conduit/controller/PhabricatorConduitTokenController.php',
'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php',
'PhabricatorConfigFileSource' => 'infrastructure/env/PhabricatorConfigFileSource.php',
'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php',
'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php',
'PhabricatorConfigStackSource' => 'infrastructure/env/PhabricatorConfigStackSource.php',
'PhabricatorContentSource' => 'applications/metamta/contentsource/PhabricatorContentSource.php',
'PhabricatorContentSourceView' => 'applications/metamta/contentsource/PhabricatorContentSourceView.php',
'PhabricatorController' => 'applications/base/controller/PhabricatorController.php',
@ -726,8 +731,8 @@ phutil_register_library_map(array(
'PhabricatorEmailTokenController' => 'applications/auth/controller/PhabricatorEmailTokenController.php',
'PhabricatorEmailVerificationController' => 'applications/people/controller/PhabricatorEmailVerificationController.php',
'PhabricatorEnglishTranslation' => 'infrastructure/internationalization/PhabricatorEnglishTranslation.php',
'PhabricatorEnv' => 'infrastructure/PhabricatorEnv.php',
'PhabricatorEnvTestCase' => 'infrastructure/__tests__/PhabricatorEnvTestCase.php',
'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php',
'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php',
'PhabricatorErrorExample' => 'applications/uiexample/examples/PhabricatorErrorExample.php',
'PhabricatorEvent' => 'infrastructure/events/PhabricatorEvent.php',
'PhabricatorEventEngine' => 'infrastructure/events/PhabricatorEventEngine.php',
@ -1083,7 +1088,7 @@ phutil_register_library_map(array(
'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php',
'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php',
'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php',
'PhabricatorScopedEnv' => 'infrastructure/PhabricatorScopedEnv.php',
'PhabricatorScopedEnv' => 'infrastructure/env/PhabricatorScopedEnv.php',
'PhabricatorSearchAbstractDocument' => 'applications/search/index/PhabricatorSearchAbstractDocument.php',
'PhabricatorSearchAttachController' => 'applications/search/controller/PhabricatorSearchAttachController.php',
'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php',
@ -1981,6 +1986,10 @@ phutil_register_library_map(array(
'PhabricatorConduitLogController' => 'PhabricatorConduitController',
'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO',
'PhabricatorConduitTokenController' => 'PhabricatorConduitController',
'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource',
'PhabricatorConfigFileSource' => 'PhabricatorConfigProxySource',
'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
'PhabricatorConfigStackSource' => 'PhabricatorConfigSource',
'PhabricatorContentSourceView' => 'AphrontView',
'PhabricatorController' => 'AphrontController',
'PhabricatorCountdownController' => 'PhabricatorController',

View file

@ -0,0 +1,36 @@
<?php
final class PhabricatorConfigDictionarySource
extends PhabricatorConfigSource {
private $dictionary;
public function __construct(array $dictionary) {
$this->dictionary = $dictionary;
}
public function getAllKeys() {
return $this->dictionary;
}
public function getKeys(array $keys) {
return array_select_keys($this->dictionary, $keys);
}
public function canWrite() {
return true;
}
public function setKeys(array $keys) {
$this->dictionary = $keys + $this->dictionary;
return $this;
}
public function deleteKeys(array $keys) {
foreach ($keys as $key) {
unset($this->dictionary[$key]);
}
return $keys;
}
}

View file

@ -0,0 +1,23 @@
<?php
/**
* Configuration source which reads from a configuration file on disk (a
* PHP file in the conf/ directory). This source
*/
final class PhabricatorConfigFileSource
extends PhabricatorConfigProxySource {
/**
* @phutil-external-symbol function phabricator_read_config_file
*/
public function __construct($config) {
$root = dirname(phutil_get_library_root('phabricator'));
require_once $root.'/conf/__init_conf__.php';
$dictionary = phabricator_read_config_file($config);
$dictionary['phabricator.env'] = $config;
$this->setSource(new PhabricatorConfigDictionarySource($dictionary));
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* Configuration source which proxies some other configuration source.
*/
abstract class PhabricatorConfigProxySource
extends PhabricatorConfigSource {
private $source;
final protected function getSource() {
if (!$this->source) {
throw new Exception("No configuration source set!");
}
return $this->source;
}
final protected function setSource(PhabricatorConfigSource $source) {
$this->source = $source;
return $this;
}
public function getAllKeys() {
return $this->getSource()->getAllKeys();
}
public function getKeys(array $keys) {
return $this->getSource()->getKeys($keys);
}
public function canWrite() {
return $this->getSource->canWrite();
}
public function setKeys(array $keys) {
return $this->getSource->setKeys();
}
public function deleteKeys(array $keys) {
return $this->getSource->deleteKeys();
}
}

View file

@ -0,0 +1,20 @@
<?php
abstract class PhabricatorConfigSource {
abstract public function getKeys(array $keys);
abstract public function getAllKeys();
public function canWrite() {
return false;
}
public function setKeys(array $keys) {
throw new Exception("This configuration source does not support writes.");
}
public function deleteKeys(array $keys) {
throw new Exception("This configuration source does not support writes.");
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* Configuration source which reads from a stack of other configuration
* sources.
*
* This source is writable if any source in the stack is writable. Writes happen
* to the first writable source only.
*/
final class PhabricatorConfigStackSource
extends PhabricatorConfigSource {
private $stack = array();
public function pushSource(PhabricatorConfigSource $source) {
array_unshift($this->stack, $source);
return $this;
}
public function popSource() {
if (empty($this->stack)) {
throw new Exception("Popping an empty config stack!");
}
return array_shift($this->stack);
}
public function getKeys(array $keys) {
$result = array();
foreach ($this->stack as $source) {
$result = $result + $source->getKeys($keys);
}
return $result;
}
public function getAllKeys() {
$result = array();
foreach ($this->stack as $source) {
$result = $result + $source->getAllKeys();
}
return $result;
}
public function canWrite() {
foreach ($this->stack as $source) {
if ($source->canWrite()) {
return true;
}
}
return false;
}
public function setKeys(array $keys) {
foreach ($this->stack as $source) {
if ($source->canWrite()) {
$source->setKeys($keys);
return;
}
}
// We can't write; this will throw an appropriate exception.
parent::setKeys($keys);
}
public function deleteKeys(array $keys) {
foreach ($this->stack as $source) {
if ($source->canWrite()) {
$source->deleteKeys($keys);
return;
}
}
// We can't write; this will throw an appropriate exception.
parent::deleteKeys($keys);
}
}

View file

@ -50,8 +50,7 @@
*/
final class PhabricatorEnv {
private static $env;
private static $stack = array();
private static $sourceStack;
/**
* @phutil-external-symbol class PhabricatorStartup
@ -93,18 +92,12 @@ final class PhabricatorEnv {
AphrontWriteGuard::allowDangerousUnguardedWrites(true);
}
/**
* @phutil-external-symbol function phabricator_read_config_file
*/
private static function initializeCommonEnvironment() {
$env = self::getSelectedEnvironmentName();
$root = dirname(phutil_get_library_root('phabricator'));
require_once $root.'/conf/__init_conf__.php';
$conf = phabricator_read_config_file($env);
$conf['phabricator.env'] = $env;
PhabricatorEnv::setEnvConfig($conf);
self::$sourceStack = new PhabricatorConfigStackSource();
self::$sourceStack->pushSource(new PhabricatorConfigFileSource($env));
PhutilErrorHandler::initialize();
@ -159,18 +152,8 @@ final class PhabricatorEnv {
* @task read
*/
public static function getEnvConfig($key, $default = null) {
// If we have environment overrides via beginScopedEnv(), check them for
// the key first.
if (self::$stack) {
foreach (array_reverse(self::$stack) as $override) {
if (array_key_exists($key, $override)) {
return $override[$key];
}
}
}
return idx(self::$env, $key, $default);
$result = self::$sourceStack->getKeys(array($key));
return idx($result, $key, $default);
}
@ -256,27 +239,26 @@ final class PhabricatorEnv {
* @task test
*/
public static function beginScopedEnv() {
return new PhabricatorScopedEnv(self::pushEnvironment());
return new PhabricatorScopedEnv(self::pushTestEnvironment());
}
/**
* @task test
*/
private static function pushEnvironment() {
self::$stack[] = array();
return last_key(self::$stack);
private static function pushTestEnvironment() {
$source = new PhabricatorConfigDictionarySource(array());
self::$sourceStack->pushSource($source);
return spl_object_hash($source);
}
/**
* @task test
*/
public static function popEnvironment($key) {
$stack_key = last_key(self::$stack);
array_pop(self::$stack);
public static function popTestEnvironment($key) {
$source = self::$sourceStack->popSource();
$stack_key = spl_object_hash($source);
if ($stack_key !== $key) {
throw new Exception(
"Scoped environments were destroyed in a diffent order than they ".
@ -366,14 +348,6 @@ final class PhabricatorEnv {
/* -( Internals )---------------------------------------------------------- */
/**
* @task internal
*/
public static function setEnvConfig(array $config) {
self::$env = $config;
}
/**
* @task internal
*/
@ -405,7 +379,7 @@ final class PhabricatorEnv {
* @task internal
*/
public static function envConfigExists($key) {
return array_key_exists($key, self::$env);
return array_key_exists($key, self::$sourceStack->getKeys(array($key)));
}
@ -413,15 +387,30 @@ final class PhabricatorEnv {
* @task internal
*/
public static function getAllConfigKeys() {
return self::$env;
return self::$sourceStack->getAllKeys();
}
/**
* @task internal
*/
public static function overrideEnvConfig($stack_key, $key, $value) {
self::$stack[$stack_key][$key] = $value;
public static function overrideTestEnvConfig($stack_key, $key, $value) {
$tmp = array();
// If we don't have the right key, we'll throw when popping the last
// source off the stack.
do {
$source = self::$sourceStack->popSource();
array_unshift($tmp, $source);
if (spl_object_hash($source) == $stack_key) {
$source->setKeys(array($key => $value));
break;
}
} while (true);
foreach ($tmp as $source) {
self::$sourceStack->pushSource($source);
}
}
}

View file

@ -24,7 +24,7 @@ final class PhabricatorScopedEnv {
* @task override
*/
public function overrideEnvConfig($key, $value) {
PhabricatorEnv::overrideEnvConfig(
PhabricatorEnv::overrideTestEnvConfig(
$this->key,
$key,
$value);
@ -36,7 +36,6 @@ final class PhabricatorScopedEnv {
/**
*
* @task internal
*/
public function __construct($stack_key) {
@ -52,7 +51,7 @@ final class PhabricatorScopedEnv {
*/
public function __destruct() {
if (!$this->isPopped) {
PhabricatorEnv::popEnvironment($this->key);
PhabricatorEnv::popTestEnvironment($this->key);
$this->isPopped = true;
}
}

View file

@ -38,8 +38,78 @@ final class PhabricatorEnvTestCase extends PhabricatorTestCase {
}
}
public function testDictionarySource() {
$source = new PhabricatorConfigDictionarySource(array('x' => 1));
$this->assertEqual(
array(
'x' => 1,
),
$source->getKeys(array('x', 'z')));
$source->setKeys(array('z' => 2));
$this->assertEqual(
array(
'x' => 1,
'z' => 2,
),
$source->getKeys(array('x', 'z')));
$source->setKeys(array('x' => 3));
$this->assertEqual(
array(
'x' => 3,
'z' => 2,
),
$source->getKeys(array('x', 'z')));
$source->deleteKeys(array('x'));
$this->assertEqual(
array(
'z' => 2,
),
$source->getKeys(array('x', 'z')));
}
public function testStackSource() {
$s1 = new PhabricatorConfigDictionarySource(array('x' => 1));
$s2 = new PhabricatorConfigDictionarySource(array('x' => 2));
$stack = new PhabricatorConfigStackSource();
$this->assertEqual(array(), $stack->getKeys(array('x')));
$stack->pushSource($s1);
$this->assertEqual(array('x' => 1), $stack->getKeys(array('x')));
$stack->pushSource($s2);
$this->assertEqual(array('x' => 2), $stack->getKeys(array('x')));
$stack->setKeys(array('x' => 3));
$this->assertEqual(array('x' => 3), $stack->getKeys(array('x')));
$stack->popSource();
$this->assertEqual(array('x' => 1), $stack->getKeys(array('x')));
$stack->popSource();
$this->assertEqual(array(), $stack->getKeys(array('x')));
$caught = null;
try {
$stack->popSource();
} catch (Exception $ex) {
$caught = $ex;
}
$this->assertEqual(true, ($caught instanceof Exception));
}
public function testOverrides() {
$outer = PhabricatorEnv::beginScopedEnv();
$outer->overrideEnvConfig('test.value', 1);
$this->assertEqual(1, PhabricatorEnv::getEnvConfig('test.value'));