@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

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

+315 -50
+12 -3
src/__phutil_library_map__.php
··· 679 679 'PhabricatorConduitLogController' => 'applications/conduit/controller/PhabricatorConduitLogController.php', 680 680 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php', 681 681 'PhabricatorConduitTokenController' => 'applications/conduit/controller/PhabricatorConduitTokenController.php', 682 + 'PhabricatorConfigDictionarySource' => 'infrastructure/env/PhabricatorConfigDictionarySource.php', 683 + 'PhabricatorConfigFileSource' => 'infrastructure/env/PhabricatorConfigFileSource.php', 684 + 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 685 + 'PhabricatorConfigSource' => 'infrastructure/env/PhabricatorConfigSource.php', 686 + 'PhabricatorConfigStackSource' => 'infrastructure/env/PhabricatorConfigStackSource.php', 682 687 'PhabricatorContentSource' => 'applications/metamta/contentsource/PhabricatorContentSource.php', 683 688 'PhabricatorContentSourceView' => 'applications/metamta/contentsource/PhabricatorContentSourceView.php', 684 689 'PhabricatorController' => 'applications/base/controller/PhabricatorController.php', ··· 726 731 'PhabricatorEmailTokenController' => 'applications/auth/controller/PhabricatorEmailTokenController.php', 727 732 'PhabricatorEmailVerificationController' => 'applications/people/controller/PhabricatorEmailVerificationController.php', 728 733 'PhabricatorEnglishTranslation' => 'infrastructure/internationalization/PhabricatorEnglishTranslation.php', 729 - 'PhabricatorEnv' => 'infrastructure/PhabricatorEnv.php', 730 - 'PhabricatorEnvTestCase' => 'infrastructure/__tests__/PhabricatorEnvTestCase.php', 734 + 'PhabricatorEnv' => 'infrastructure/env/PhabricatorEnv.php', 735 + 'PhabricatorEnvTestCase' => 'infrastructure/env/__tests__/PhabricatorEnvTestCase.php', 731 736 'PhabricatorErrorExample' => 'applications/uiexample/examples/PhabricatorErrorExample.php', 732 737 'PhabricatorEvent' => 'infrastructure/events/PhabricatorEvent.php', 733 738 'PhabricatorEventEngine' => 'infrastructure/events/PhabricatorEventEngine.php', ··· 1083 1088 'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php', 1084 1089 'PhabricatorSQLPatchList' => 'infrastructure/storage/patch/PhabricatorSQLPatchList.php', 1085 1090 'PhabricatorSSHWorkflow' => 'infrastructure/ssh/PhabricatorSSHWorkflow.php', 1086 - 'PhabricatorScopedEnv' => 'infrastructure/PhabricatorScopedEnv.php', 1091 + 'PhabricatorScopedEnv' => 'infrastructure/env/PhabricatorScopedEnv.php', 1087 1092 'PhabricatorSearchAbstractDocument' => 'applications/search/index/PhabricatorSearchAbstractDocument.php', 1088 1093 'PhabricatorSearchAttachController' => 'applications/search/controller/PhabricatorSearchAttachController.php', 1089 1094 'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php', ··· 1981 1986 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 1982 1987 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 1983 1988 'PhabricatorConduitTokenController' => 'PhabricatorConduitController', 1989 + 'PhabricatorConfigDictionarySource' => 'PhabricatorConfigSource', 1990 + 'PhabricatorConfigFileSource' => 'PhabricatorConfigProxySource', 1991 + 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 1992 + 'PhabricatorConfigStackSource' => 'PhabricatorConfigSource', 1984 1993 'PhabricatorContentSourceView' => 'AphrontView', 1985 1994 'PhabricatorController' => 'AphrontController', 1986 1995 'PhabricatorCountdownController' => 'PhabricatorController',
+33 -44
src/infrastructure/PhabricatorEnv.php src/infrastructure/env/PhabricatorEnv.php
··· 50 50 */ 51 51 final class PhabricatorEnv { 52 52 53 - private static $env; 54 - private static $stack = array(); 53 + private static $sourceStack; 55 54 56 55 /** 57 56 * @phutil-external-symbol class PhabricatorStartup ··· 93 92 AphrontWriteGuard::allowDangerousUnguardedWrites(true); 94 93 } 95 94 96 - /** 97 - * @phutil-external-symbol function phabricator_read_config_file 98 - */ 95 + 99 96 private static function initializeCommonEnvironment() { 100 97 $env = self::getSelectedEnvironmentName(); 101 98 102 - $root = dirname(phutil_get_library_root('phabricator')); 103 - require_once $root.'/conf/__init_conf__.php'; 104 - $conf = phabricator_read_config_file($env); 105 - $conf['phabricator.env'] = $env; 106 - 107 - PhabricatorEnv::setEnvConfig($conf); 99 + self::$sourceStack = new PhabricatorConfigStackSource(); 100 + self::$sourceStack->pushSource(new PhabricatorConfigFileSource($env)); 108 101 109 102 PhutilErrorHandler::initialize(); 110 103 ··· 159 152 * @task read 160 153 */ 161 154 public static function getEnvConfig($key, $default = null) { 162 - 163 - // If we have environment overrides via beginScopedEnv(), check them for 164 - // the key first. 165 - if (self::$stack) { 166 - foreach (array_reverse(self::$stack) as $override) { 167 - if (array_key_exists($key, $override)) { 168 - return $override[$key]; 169 - } 170 - } 171 - } 172 - 173 - return idx(self::$env, $key, $default); 155 + $result = self::$sourceStack->getKeys(array($key)); 156 + return idx($result, $key, $default); 174 157 } 175 158 176 159 ··· 256 239 * @task test 257 240 */ 258 241 public static function beginScopedEnv() { 259 - return new PhabricatorScopedEnv(self::pushEnvironment()); 242 + return new PhabricatorScopedEnv(self::pushTestEnvironment()); 260 243 } 261 244 262 245 263 246 /** 264 247 * @task test 265 248 */ 266 - private static function pushEnvironment() { 267 - self::$stack[] = array(); 268 - return last_key(self::$stack); 249 + private static function pushTestEnvironment() { 250 + $source = new PhabricatorConfigDictionarySource(array()); 251 + self::$sourceStack->pushSource($source); 252 + return spl_object_hash($source); 269 253 } 270 254 271 255 272 256 /** 273 257 * @task test 274 258 */ 275 - public static function popEnvironment($key) { 276 - $stack_key = last_key(self::$stack); 277 - 278 - array_pop(self::$stack); 279 - 259 + public static function popTestEnvironment($key) { 260 + $source = self::$sourceStack->popSource(); 261 + $stack_key = spl_object_hash($source); 280 262 if ($stack_key !== $key) { 281 263 throw new Exception( 282 264 "Scoped environments were destroyed in a diffent order than they ". ··· 369 351 /** 370 352 * @task internal 371 353 */ 372 - public static function setEnvConfig(array $config) { 373 - self::$env = $config; 374 - } 375 - 376 - 377 - /** 378 - * @task internal 379 - */ 380 354 public static function getRequiredClasses() { 381 355 return array( 382 356 'translation.provider' => 'PhabricatorTranslation', ··· 405 379 * @task internal 406 380 */ 407 381 public static function envConfigExists($key) { 408 - return array_key_exists($key, self::$env); 382 + return array_key_exists($key, self::$sourceStack->getKeys(array($key))); 409 383 } 410 384 411 385 ··· 413 387 * @task internal 414 388 */ 415 389 public static function getAllConfigKeys() { 416 - return self::$env; 390 + return self::$sourceStack->getAllKeys(); 417 391 } 418 392 419 393 420 394 /** 421 395 * @task internal 422 396 */ 423 - public static function overrideEnvConfig($stack_key, $key, $value) { 424 - self::$stack[$stack_key][$key] = $value; 397 + public static function overrideTestEnvConfig($stack_key, $key, $value) { 398 + $tmp = array(); 399 + 400 + // If we don't have the right key, we'll throw when popping the last 401 + // source off the stack. 402 + do { 403 + $source = self::$sourceStack->popSource(); 404 + array_unshift($tmp, $source); 405 + if (spl_object_hash($source) == $stack_key) { 406 + $source->setKeys(array($key => $value)); 407 + break; 408 + } 409 + } while (true); 410 + 411 + foreach ($tmp as $source) { 412 + self::$sourceStack->pushSource($source); 413 + } 425 414 } 426 415 427 416 }
+2 -3
src/infrastructure/PhabricatorScopedEnv.php src/infrastructure/env/PhabricatorScopedEnv.php
··· 24 24 * @task override 25 25 */ 26 26 public function overrideEnvConfig($key, $value) { 27 - PhabricatorEnv::overrideEnvConfig( 27 + PhabricatorEnv::overrideTestEnvConfig( 28 28 $this->key, 29 29 $key, 30 30 $value); ··· 36 36 37 37 38 38 /** 39 - * 40 39 * @task internal 41 40 */ 42 41 public function __construct($stack_key) { ··· 52 51 */ 53 52 public function __destruct() { 54 53 if (!$this->isPopped) { 55 - PhabricatorEnv::popEnvironment($this->key); 54 + PhabricatorEnv::popTestEnvironment($this->key); 56 55 $this->isPopped = true; 57 56 } 58 57 }
+70
src/infrastructure/__tests__/PhabricatorEnvTestCase.php src/infrastructure/env/__tests__/PhabricatorEnvTestCase.php
··· 38 38 } 39 39 } 40 40 41 + public function testDictionarySource() { 42 + $source = new PhabricatorConfigDictionarySource(array('x' => 1)); 43 + 44 + $this->assertEqual( 45 + array( 46 + 'x' => 1, 47 + ), 48 + $source->getKeys(array('x', 'z'))); 49 + 50 + $source->setKeys(array('z' => 2)); 51 + 52 + $this->assertEqual( 53 + array( 54 + 'x' => 1, 55 + 'z' => 2, 56 + ), 57 + $source->getKeys(array('x', 'z'))); 58 + 59 + $source->setKeys(array('x' => 3)); 60 + 61 + $this->assertEqual( 62 + array( 63 + 'x' => 3, 64 + 'z' => 2, 65 + ), 66 + $source->getKeys(array('x', 'z'))); 67 + 68 + $source->deleteKeys(array('x')); 69 + 70 + $this->assertEqual( 71 + array( 72 + 'z' => 2, 73 + ), 74 + $source->getKeys(array('x', 'z'))); 75 + } 76 + 77 + public function testStackSource() { 78 + $s1 = new PhabricatorConfigDictionarySource(array('x' => 1)); 79 + $s2 = new PhabricatorConfigDictionarySource(array('x' => 2)); 80 + 81 + $stack = new PhabricatorConfigStackSource(); 82 + 83 + $this->assertEqual(array(), $stack->getKeys(array('x'))); 84 + 85 + $stack->pushSource($s1); 86 + $this->assertEqual(array('x' => 1), $stack->getKeys(array('x'))); 87 + 88 + $stack->pushSource($s2); 89 + $this->assertEqual(array('x' => 2), $stack->getKeys(array('x'))); 90 + 91 + $stack->setKeys(array('x' => 3)); 92 + $this->assertEqual(array('x' => 3), $stack->getKeys(array('x'))); 93 + 94 + $stack->popSource(); 95 + $this->assertEqual(array('x' => 1), $stack->getKeys(array('x'))); 96 + 97 + $stack->popSource(); 98 + $this->assertEqual(array(), $stack->getKeys(array('x'))); 99 + 100 + $caught = null; 101 + try { 102 + $stack->popSource(); 103 + } catch (Exception $ex) { 104 + $caught = $ex; 105 + } 106 + 107 + $this->assertEqual(true, ($caught instanceof Exception)); 108 + } 109 + 41 110 public function testOverrides() { 42 111 $outer = PhabricatorEnv::beginScopedEnv(); 112 + 43 113 $outer->overrideEnvConfig('test.value', 1); 44 114 $this->assertEqual(1, PhabricatorEnv::getEnvConfig('test.value')); 45 115
+36
src/infrastructure/env/PhabricatorConfigDictionarySource.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigDictionarySource 4 + extends PhabricatorConfigSource { 5 + 6 + private $dictionary; 7 + 8 + public function __construct(array $dictionary) { 9 + $this->dictionary = $dictionary; 10 + } 11 + 12 + public function getAllKeys() { 13 + return $this->dictionary; 14 + } 15 + 16 + public function getKeys(array $keys) { 17 + return array_select_keys($this->dictionary, $keys); 18 + } 19 + 20 + public function canWrite() { 21 + return true; 22 + } 23 + 24 + public function setKeys(array $keys) { 25 + $this->dictionary = $keys + $this->dictionary; 26 + return $this; 27 + } 28 + 29 + public function deleteKeys(array $keys) { 30 + foreach ($keys as $key) { 31 + unset($this->dictionary[$key]); 32 + } 33 + return $keys; 34 + } 35 + 36 + }
+23
src/infrastructure/env/PhabricatorConfigFileSource.php
··· 1 + <?php 2 + 3 + /** 4 + * Configuration source which reads from a configuration file on disk (a 5 + * PHP file in the conf/ directory). This source 6 + */ 7 + final class PhabricatorConfigFileSource 8 + extends PhabricatorConfigProxySource { 9 + 10 + /** 11 + * @phutil-external-symbol function phabricator_read_config_file 12 + */ 13 + public function __construct($config) { 14 + $root = dirname(phutil_get_library_root('phabricator')); 15 + require_once $root.'/conf/__init_conf__.php'; 16 + 17 + $dictionary = phabricator_read_config_file($config); 18 + $dictionary['phabricator.env'] = $config; 19 + 20 + $this->setSource(new PhabricatorConfigDictionarySource($dictionary)); 21 + } 22 + 23 + }
+43
src/infrastructure/env/PhabricatorConfigProxySource.php
··· 1 + <?php 2 + 3 + /** 4 + * Configuration source which proxies some other configuration source. 5 + */ 6 + abstract class PhabricatorConfigProxySource 7 + extends PhabricatorConfigSource { 8 + 9 + private $source; 10 + 11 + final protected function getSource() { 12 + if (!$this->source) { 13 + throw new Exception("No configuration source set!"); 14 + } 15 + return $this->source; 16 + } 17 + 18 + final protected function setSource(PhabricatorConfigSource $source) { 19 + $this->source = $source; 20 + return $this; 21 + } 22 + 23 + public function getAllKeys() { 24 + return $this->getSource()->getAllKeys(); 25 + } 26 + 27 + public function getKeys(array $keys) { 28 + return $this->getSource()->getKeys($keys); 29 + } 30 + 31 + public function canWrite() { 32 + return $this->getSource->canWrite(); 33 + } 34 + 35 + public function setKeys(array $keys) { 36 + return $this->getSource->setKeys(); 37 + } 38 + 39 + public function deleteKeys(array $keys) { 40 + return $this->getSource->deleteKeys(); 41 + } 42 + 43 + }
+20
src/infrastructure/env/PhabricatorConfigSource.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorConfigSource { 4 + 5 + abstract public function getKeys(array $keys); 6 + abstract public function getAllKeys(); 7 + 8 + public function canWrite() { 9 + return false; 10 + } 11 + 12 + public function setKeys(array $keys) { 13 + throw new Exception("This configuration source does not support writes."); 14 + } 15 + 16 + public function deleteKeys(array $keys) { 17 + throw new Exception("This configuration source does not support writes."); 18 + } 19 + 20 + }
+76
src/infrastructure/env/PhabricatorConfigStackSource.php
··· 1 + <?php 2 + 3 + /** 4 + * Configuration source which reads from a stack of other configuration 5 + * sources. 6 + * 7 + * This source is writable if any source in the stack is writable. Writes happen 8 + * to the first writable source only. 9 + */ 10 + final class PhabricatorConfigStackSource 11 + extends PhabricatorConfigSource { 12 + 13 + private $stack = array(); 14 + 15 + public function pushSource(PhabricatorConfigSource $source) { 16 + array_unshift($this->stack, $source); 17 + return $this; 18 + } 19 + 20 + public function popSource() { 21 + if (empty($this->stack)) { 22 + throw new Exception("Popping an empty config stack!"); 23 + } 24 + return array_shift($this->stack); 25 + } 26 + 27 + public function getKeys(array $keys) { 28 + $result = array(); 29 + foreach ($this->stack as $source) { 30 + $result = $result + $source->getKeys($keys); 31 + } 32 + return $result; 33 + } 34 + 35 + public function getAllKeys() { 36 + $result = array(); 37 + foreach ($this->stack as $source) { 38 + $result = $result + $source->getAllKeys(); 39 + } 40 + return $result; 41 + } 42 + 43 + public function canWrite() { 44 + foreach ($this->stack as $source) { 45 + if ($source->canWrite()) { 46 + return true; 47 + } 48 + } 49 + return false; 50 + } 51 + 52 + public function setKeys(array $keys) { 53 + foreach ($this->stack as $source) { 54 + if ($source->canWrite()) { 55 + $source->setKeys($keys); 56 + return; 57 + } 58 + } 59 + 60 + // We can't write; this will throw an appropriate exception. 61 + parent::setKeys($keys); 62 + } 63 + 64 + public function deleteKeys(array $keys) { 65 + foreach ($this->stack as $source) { 66 + if ($source->canWrite()) { 67 + $source->deleteKeys($keys); 68 + return; 69 + } 70 + } 71 + 72 + // We can't write; this will throw an appropriate exception. 73 + parent::deleteKeys($keys); 74 + } 75 + 76 + }