@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Begin modularizing config options in a more modern way

Summary:
Ref T12845. Config options are "modular", but the modularity is very old, half-implemented, and doesn't use modern patterns.

Half the types are hard-coded, while half the types are semi-modular but in a weird hacky way where you prefix the type with `custom:...`.

The actual API is also weird and requires types to return a lot of `array($stuff, $thing, $other_thing, $more_stuff)` sorts of tuples.

Instead:

- Add a new replacement layer which uses modern modularity patterns and overrides the older stuff if available, so we can migrate things one at a time.
- New layer uses a more modern API -- no `return array($thing, $other_thing, ...)`, and more modern building blocks (like AphrontHTTPParameterType).
- New layer allows custom types to be deleted, which will ultimately let us deal with T12845.

Then, convert the `'int'` type to use the new layer.

Test Plan:
- Set, edited, tried-to-change-in-an-invalid-way, and deleted an `'int'` option from the web UI.
- Same from the CLI.
- Edited `config.json` to have an invalid value, verified that the error was detected and config was repaired.

Reviewers: chad, amckinley

Reviewed By: amckinley

Maniphest Tasks: T12845

Differential Revision: https://secure.phabricator.com/D18155

+322 -84
+6
src/__phutil_library_map__.php
··· 2448 2448 'PhabricatorConfigTableSchema' => 'applications/config/schema/PhabricatorConfigTableSchema.php', 2449 2449 'PhabricatorConfigTransaction' => 'applications/config/storage/PhabricatorConfigTransaction.php', 2450 2450 'PhabricatorConfigTransactionQuery' => 'applications/config/query/PhabricatorConfigTransactionQuery.php', 2451 + 'PhabricatorConfigType' => 'applications/config/type/PhabricatorConfigType.php', 2451 2452 'PhabricatorConfigValidationException' => 'applications/config/exception/PhabricatorConfigValidationException.php', 2452 2453 'PhabricatorConfigVersionController' => 'applications/config/controller/PhabricatorConfigVersionController.php', 2453 2454 'PhabricatorConpherenceApplication' => 'applications/conpherence/application/PhabricatorConpherenceApplication.php', ··· 2997 2998 'PhabricatorInlineCommentPreviewController' => 'infrastructure/diff/PhabricatorInlineCommentPreviewController.php', 2998 2999 'PhabricatorInlineSummaryView' => 'infrastructure/diff/view/PhabricatorInlineSummaryView.php', 2999 3000 'PhabricatorInstructionsEditField' => 'applications/transactions/editfield/PhabricatorInstructionsEditField.php', 3001 + 'PhabricatorIntConfigType' => 'applications/config/type/PhabricatorIntConfigType.php', 3000 3002 'PhabricatorInternalSetting' => 'applications/settings/setting/PhabricatorInternalSetting.php', 3001 3003 'PhabricatorInternationalizationManagementExtractWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php', 3002 3004 'PhabricatorInternationalizationManagementWorkflow' => 'infrastructure/internationalization/management/PhabricatorInternationalizationManagementWorkflow.php', ··· 4127 4129 'PhabricatorTestStorageEngine' => 'applications/files/engine/PhabricatorTestStorageEngine.php', 4128 4130 'PhabricatorTestWorker' => 'infrastructure/daemon/workers/__tests__/PhabricatorTestWorker.php', 4129 4131 'PhabricatorTextAreaEditField' => 'applications/transactions/editfield/PhabricatorTextAreaEditField.php', 4132 + 'PhabricatorTextConfigType' => 'applications/config/type/PhabricatorTextConfigType.php', 4130 4133 'PhabricatorTextEditField' => 'applications/transactions/editfield/PhabricatorTextEditField.php', 4131 4134 'PhabricatorTime' => 'infrastructure/time/PhabricatorTime.php', 4132 4135 'PhabricatorTimeFormatSetting' => 'applications/settings/setting/PhabricatorTimeFormatSetting.php', ··· 7697 7700 'PhabricatorConfigTableSchema' => 'PhabricatorConfigStorageSchema', 7698 7701 'PhabricatorConfigTransaction' => 'PhabricatorApplicationTransaction', 7699 7702 'PhabricatorConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 7703 + 'PhabricatorConfigType' => 'Phobject', 7700 7704 'PhabricatorConfigValidationException' => 'Exception', 7701 7705 'PhabricatorConfigVersionController' => 'PhabricatorConfigController', 7702 7706 'PhabricatorConpherenceApplication' => 'PhabricatorApplication', ··· 8324 8328 'PhabricatorInlineCommentPreviewController' => 'PhabricatorController', 8325 8329 'PhabricatorInlineSummaryView' => 'AphrontView', 8326 8330 'PhabricatorInstructionsEditField' => 'PhabricatorEditField', 8331 + 'PhabricatorIntConfigType' => 'PhabricatorTextConfigType', 8327 8332 'PhabricatorInternalSetting' => 'PhabricatorSetting', 8328 8333 'PhabricatorInternationalizationManagementExtractWorkflow' => 'PhabricatorInternationalizationManagementWorkflow', 8329 8334 'PhabricatorInternationalizationManagementWorkflow' => 'PhabricatorManagementWorkflow', ··· 9659 9664 'PhabricatorTestStorageEngine' => 'PhabricatorFileStorageEngine', 9660 9665 'PhabricatorTestWorker' => 'PhabricatorWorker', 9661 9666 'PhabricatorTextAreaEditField' => 'PhabricatorEditField', 9667 + 'PhabricatorTextConfigType' => 'PhabricatorConfigType', 9662 9668 'PhabricatorTextEditField' => 'PhabricatorEditField', 9663 9669 'PhabricatorTime' => 'Phobject', 9664 9670 'PhabricatorTimeFormatSetting' => 'PhabricatorSelectSetting',
+59 -10
src/applications/config/controller/PhabricatorConfigEditController.php
··· 274 274 PhabricatorConfigOption $option, 275 275 AphrontRequest $request) { 276 276 277 + $type = $option->newOptionType(); 278 + if ($type) { 279 + $is_set = $type->isValuePresentInRequest($option, $request); 280 + if ($is_set) { 281 + $value = $type->readValueFromRequest($option, $request); 282 + 283 + $errors = array(); 284 + try { 285 + $canonical_value = $type->newValueFromRequestValue( 286 + $option, 287 + $value); 288 + $type->validateStoredValue($canonical_value); 289 + $xaction = $type->newTransaction($option, $canonical_value); 290 + } catch (PhabricatorConfigValidationException $ex) { 291 + $errors[] = $ex->getMessage(); 292 + $xaction = null; 293 + } 294 + 295 + return array( 296 + $errors ? pht('Invalid') : null, 297 + $errors, 298 + $value, 299 + $xaction, 300 + ); 301 + } else { 302 + $delete_xaction = id(new PhabricatorConfigTransaction()) 303 + ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) 304 + ->setNewValue( 305 + array( 306 + 'deleted' => true, 307 + 'value' => null, 308 + )); 309 + 310 + return array( 311 + null, 312 + array(), 313 + null, 314 + $delete_xaction, 315 + ); 316 + } 317 + } 318 + 319 + // TODO: If we missed on the new `PhabricatorConfigType` map, fall back 320 + // to the old semi-modular, semi-hacky way of doing things. 321 + 277 322 $xaction = new PhabricatorConfigTransaction(); 278 323 $xaction->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT); 279 324 280 325 $e_value = null; 281 326 $errors = array(); 327 + 282 328 283 329 if ($option->isCustomType()) { 284 330 $info = $option->getCustomObject()->readRequest($option, $request); ··· 301 347 $set_value = null; 302 348 303 349 switch ($type) { 304 - case 'int': 305 - if (preg_match('/^-?[0-9]+$/', trim($value))) { 306 - $set_value = (int)$value; 307 - } else { 308 - $e_value = pht('Invalid'); 309 - $errors[] = pht('Value must be an integer.'); 310 - } 311 - break; 312 350 case 'string': 313 351 case 'enum': 314 352 $set_value = (string)$value; ··· 390 428 PhabricatorConfigEntry $entry, 391 429 $value) { 392 430 431 + $type = $option->newOptionType(); 432 + if ($type) { 433 + return $type->newDisplayValue($option, $value); 434 + } 435 + 393 436 if ($option->isCustomType()) { 394 437 return $option->getCustomObject()->getDisplayValue( 395 438 $option, ··· 398 441 } else { 399 442 $type = $option->getType(); 400 443 switch ($type) { 401 - case 'int': 402 444 case 'string': 403 445 case 'enum': 404 446 case 'class': ··· 421 463 $display_value, 422 464 $e_value) { 423 465 466 + $type = $option->newOptionType(); 467 + if ($type) { 468 + return $type->newControls( 469 + $option, 470 + $display_value, 471 + $e_value); 472 + } 473 + 424 474 if ($option->isCustomType()) { 425 475 $controls = $option->getCustomObject()->renderControls( 426 476 $option, ··· 429 479 } else { 430 480 $type = $option->getType(); 431 481 switch ($type) { 432 - case 'int': 433 482 case 'string': 434 483 $control = id(new AphrontFormTextControl()); 435 484 break;
+65 -63
src/applications/config/management/PhabricatorConfigManagementSetWorkflow.php
··· 59 59 60 60 $option = $options[$key]; 61 61 62 - $type = $option->getType(); 63 - switch ($type) { 64 - case 'string': 65 - case 'class': 66 - case 'enum': 67 - $value = (string)$value; 68 - break; 69 - case 'int': 70 - if (!ctype_digit($value)) { 71 - throw new PhutilArgumentUsageException( 72 - pht( 73 - "Config key '%s' is of type '%s'. Specify an integer.", 74 - $key, 75 - $type)); 76 - } 77 - $value = (int)$value; 78 - break; 79 - case 'bool': 80 - if ($value == 'true') { 81 - $value = true; 82 - } else if ($value == 'false') { 83 - $value = false; 84 - } else { 85 - throw new PhutilArgumentUsageException( 86 - pht( 87 - "Config key '%s' is of type '%s'. Specify '%s' or '%s'.", 88 - $key, 89 - $type, 90 - 'true', 91 - 'false')); 92 - } 93 - break; 94 - default: 95 - $value = json_decode($value, true); 96 - if (!is_array($value)) { 97 - switch ($type) { 98 - case 'set': 99 - $command = csprintf( 100 - './bin/config set %R %s', 62 + $type = $option->newOptionType(); 63 + if ($type) { 64 + try { 65 + $value = $type->newValueFromCommandLineValue( 66 + $option, 67 + $value); 68 + } catch (PhabricatorConfigValidationException $ex) { 69 + throw new PhutilArgumentUsageException($ex->getMessage()); 70 + } 71 + } else { 72 + $type = $option->getType(); 73 + switch ($type) { 74 + case 'string': 75 + case 'class': 76 + case 'enum': 77 + $value = (string)$value; 78 + break; 79 + case 'bool': 80 + if ($value == 'true') { 81 + $value = true; 82 + } else if ($value == 'false') { 83 + $value = false; 84 + } else { 85 + throw new PhutilArgumentUsageException( 86 + pht( 87 + "Config key '%s' is of type '%s'. Specify '%s' or '%s'.", 101 88 $key, 102 - '{"value1": true, "value2": true}'); 103 - 104 - $message = sprintf( 105 - "%s\n\n %s\n", 106 - pht( 107 - 'Config key "%s" is of type "%s". Specify it in JSON. '. 108 - 'For example:', 109 - $key, 110 - $type), 111 - $command); 112 - break; 113 - default: 114 - if (preg_match('/^list</', $type)) { 89 + $type, 90 + 'true', 91 + 'false')); 92 + } 93 + break; 94 + default: 95 + $value = json_decode($value, true); 96 + if (!is_array($value)) { 97 + switch ($type) { 98 + case 'set': 115 99 $command = csprintf( 116 100 './bin/config set %R %s', 117 101 $key, 118 - '["a", "b", "c"]'); 102 + '{"value1": true, "value2": true}'); 119 103 120 104 $message = sprintf( 121 105 "%s\n\n %s\n", ··· 125 109 $key, 126 110 $type), 127 111 $command); 128 - } else { 129 - $message = pht( 130 - 'Config key "%s" is of type "%s". Specify it in JSON.', 131 - $key, 132 - $type); 133 - } 134 - break; 112 + break; 113 + default: 114 + if (preg_match('/^list</', $type)) { 115 + $command = csprintf( 116 + './bin/config set %R %s', 117 + $key, 118 + '["a", "b", "c"]'); 119 + 120 + $message = sprintf( 121 + "%s\n\n %s\n", 122 + pht( 123 + 'Config key "%s" is of type "%s". Specify it in JSON. '. 124 + 'For example:', 125 + $key, 126 + $type), 127 + $command); 128 + } else { 129 + $message = pht( 130 + 'Config key "%s" is of type "%s". Specify it in JSON.', 131 + $key, 132 + $type); 133 + } 134 + break; 135 + } 136 + throw new PhutilArgumentUsageException($message); 135 137 } 136 - throw new PhutilArgumentUsageException($message); 137 - } 138 - break; 138 + break; 139 + } 139 140 } 141 + 140 142 $use_database = $args->getArg('database'); 141 143 if ($option->getLocked() && $use_database) { 142 144 throw new PhutilArgumentUsageException(
+14 -11
src/applications/config/option/PhabricatorApplicationConfigOptions.php
··· 20 20 return; 21 21 } 22 22 23 + $type = $option->newOptionType(); 24 + if ($type) { 25 + try { 26 + $type->validateStoredValue($option, $value); 27 + } catch (PhabricatorConfigValidationException $ex) { 28 + throw $ex; 29 + } catch (Exception $ex) { 30 + // If custom validators threw exceptions other than validation 31 + // exceptions, convert them to validation exceptions so we repair the 32 + // configuration and raise an error. 33 + throw new PhabricatorConfigValidationException($ex->getMessage()); 34 + } 35 + } 36 + 23 37 if ($option->isCustomType()) { 24 38 try { 25 39 return $option->getCustomObject()->validateOption($option, $value); 26 40 } catch (Exception $ex) { 27 - // If custom validators threw exceptions, convert them to configuation 28 - // validation exceptions so we repair the configuration and raise 29 - // an error. 30 41 throw new PhabricatorConfigValidationException($ex->getMessage()); 31 42 } 32 43 } ··· 38 49 throw new PhabricatorConfigValidationException( 39 50 pht( 40 51 "Option '%s' is of type bool, but value is not true or false.", 41 - $option->getKey())); 42 - } 43 - break; 44 - case 'int': 45 - if (!is_int($value)) { 46 - throw new PhabricatorConfigValidationException( 47 - pht( 48 - "Option '%s' is of type int, but value is not an integer.", 49 52 $option->getKey())); 50 53 } 51 54 break;
+6
src/applications/config/option/PhabricatorConfigOption.php
··· 175 175 return $this->type; 176 176 } 177 177 178 + public function newOptionType() { 179 + $type_key = $this->getType(); 180 + $type_map = PhabricatorConfigType::getAllTypes(); 181 + return idx($type_map, $type_key); 182 + } 183 + 178 184 public function isCustomType() { 179 185 return !strncmp($this->getType(), 'custom:', 7); 180 186 }
+115
src/applications/config/type/PhabricatorConfigType.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorConfigType extends Phobject { 4 + 5 + final public function getTypeKey() { 6 + return $this->getPhobjectClassConstant('TYPEKEY'); 7 + } 8 + 9 + final public static function getAllTypes() { 10 + return id(new PhutilClassMapQuery()) 11 + ->setAncestorClass(__CLASS__) 12 + ->setUniqueMethod('getTypeKey') 13 + ->execute(); 14 + } 15 + 16 + public function isValuePresentInRequest( 17 + PhabricatorConfigOption $option, 18 + AphrontRequest $request) { 19 + $http_type = $this->newHTTPParameterType(); 20 + return $http_type->getExists($request, 'value'); 21 + } 22 + 23 + public function readValueFromRequest( 24 + PhabricatorConfigOption $option, 25 + AphrontRequest $request) { 26 + $http_type = $this->newHTTPParameterType(); 27 + return $http_type->getValue($request, 'value'); 28 + } 29 + 30 + abstract protected function newHTTPParameterType(); 31 + 32 + public function validateValue(PhabricatorConfigOption $option, $value) { 33 + return array(); 34 + } 35 + 36 + public function newTransaction( 37 + PhabricatorConfigOption $option, 38 + $value) { 39 + 40 + $xaction_value = $this->newTransactionValue($option, $value); 41 + 42 + return id(new PhabricatorConfigTransaction()) 43 + ->setTransactionType(PhabricatorConfigTransaction::TYPE_EDIT) 44 + ->setNewValue( 45 + array( 46 + 'deleted' => false, 47 + 'value' => $xaction_value, 48 + )); 49 + } 50 + 51 + protected function newTransactionValue( 52 + PhabricatorConfigOption $option, 53 + $value) { 54 + return $value; 55 + } 56 + 57 + public function newDisplayValue( 58 + PhabricatorConfigOption $option, 59 + $value) { 60 + return $value; 61 + } 62 + 63 + public function newControls( 64 + PhabricatorConfigOption $option, 65 + $value, 66 + $error) { 67 + 68 + $control = $this->newControl($option) 69 + ->setError($error) 70 + ->setLabel(pht('Database Value')) 71 + ->setName('value'); 72 + 73 + $value = $this->newControlValue($option, $value); 74 + $control->setValue($value); 75 + 76 + return array( 77 + $control, 78 + ); 79 + } 80 + 81 + abstract protected function newControl(PhabricatorConfigOption $option); 82 + 83 + protected function newControlValue( 84 + PhabricatorConfigOption $option, 85 + $value) { 86 + return $value; 87 + } 88 + 89 + protected function newException($message) { 90 + return new PhabricatorConfigValidationException($message); 91 + } 92 + 93 + public function newValueFromRequestValue( 94 + PhabricatorConfigOption $option, 95 + $value) { 96 + return $this->newCanonicalValue($option, $value); 97 + } 98 + 99 + public function newValueFromCommandLineValue( 100 + PhabricatorConfigOption $option, 101 + $value) { 102 + return $this->newCanonicalValue($option, $value); 103 + } 104 + 105 + protected function newCanonicalValue( 106 + PhabricatorConfigOption $option, 107 + $value) { 108 + return $value; 109 + } 110 + 111 + abstract public function validateStoredValue( 112 + PhabricatorConfigOption $option, 113 + $value); 114 + 115 + }
+36
src/applications/config/type/PhabricatorIntConfigType.php
··· 1 + <?php 2 + 3 + final class PhabricatorIntConfigType 4 + extends PhabricatorTextConfigType { 5 + 6 + const TYPEKEY = 'int'; 7 + 8 + protected function newCanonicalValue( 9 + PhabricatorConfigOption $option, 10 + $value) { 11 + 12 + if (!preg_match('/^-?[0-9]+\z/', $value)) { 13 + throw $this->newException( 14 + pht( 15 + 'Value for option "%s" must be an integer.', 16 + $option->getKey())); 17 + } 18 + 19 + return (int)$value; 20 + } 21 + 22 + public function validateStoredValue( 23 + PhabricatorConfigOption $option, 24 + $value) { 25 + 26 + if (!is_int($value)) { 27 + throw $this->newException( 28 + pht( 29 + 'Option "%s" is of type "%s", but the configured value is not '. 30 + 'an integer.', 31 + $option->getKey(), 32 + $this->getTypeKey())); 33 + } 34 + } 35 + 36 + }
+21
src/applications/config/type/PhabricatorTextConfigType.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorTextConfigType 4 + extends PhabricatorConfigType { 5 + 6 + public function isValuePresentInRequest( 7 + PhabricatorConfigOption $option, 8 + AphrontRequest $request) { 9 + $value = parent::readValueFromRequest($option, $request); 10 + return (bool)strlen($value); 11 + } 12 + 13 + protected function newHTTPParameterType() { 14 + return new AphrontStringHTTPParameterType(); 15 + } 16 + 17 + protected function newControl(PhabricatorConfigOption $option) { 18 + return new AphrontFormTextControl(); 19 + } 20 + 21 + }