Unverified Commit c961ea39 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #3537627 by phenaproxima, jurgenhaas, thejimbirch, alexpott: If a recipe...

Issue #3537627 by phenaproxima, jurgenhaas, thejimbirch, alexpott: If a recipe takes a default input value from config, it should be able to fall back to a static value if that config doesn't exist
parent b450d189
Loading
Loading
Loading
Loading
Loading
+21 −9
Changes for core/lib/Drupal/Core/Recipe/InputConfigurator.php: 21 added lines, 9 removed lines.
Original line number Diff line number Diff line
@@ -156,13 +156,13 @@ public function collectAll(InputCollectorInterface $collector, array &$processed
        $definition,
        $this->getDefaultValue($definition),
      );
      $data->setValue($value, FALSE);
      $data->setValue($value);

      $violations = $data->validate();
      if (count($violations) > 0) {
        throw new ValidationFailedException($data, $violations);
      }
      $this->values[$key] = $data->getCastedValue();
      $this->values[$key] = $data->getValue();
    }
    $processed[] = $this->prefix;
  }
@@ -175,11 +175,13 @@ public function collectAll(InputCollectorInterface $collector, array &$processed
   *   of `config`, `env`, or `value`:
   *   - If `source` is `config`, there must also be a `config` element, which
   *     is a two-element indexed array containing (in order) the name of an
   *     extant config object, and a property path within that object.
   *     extant config object, and a property path within that object. If the
   *     config doesn't exist and there is no fallback value set, an exception
   *     is thrown.
   *   - If `source` is `env`, there must also be an `env` element, which is
   *     the name of an environment variable to return. The value will always
   *     be returned as a string. If the environment variable is not set, an
   *     empty string will be returned.
   *     be returned as a string. If the environment variable is not set and
   *     there is no fallback value set, an exception is thrown.
   *   - If `source` is 'value', then there must be a `value` element, which
   *    will be returned as-is.
   *
@@ -193,7 +195,9 @@ private function getDefaultValue(DataDefinition $definition): mixed {
      [$name, $key] = $settings['config'];
      $config = \Drupal::config($name);
      if ($config->isNew()) {
        throw new \RuntimeException("The '$name' config object does not exist.");
        return array_key_exists('fallback', $settings)
          ? $settings['fallback']
          : throw new \RuntimeException("The '$name' config object does not exist.");
      }
      return $config->get($key);
    }
@@ -204,9 +208,17 @@ private function getDefaultValue(DataDefinition $definition): mixed {
      if (empty($settings['env'])) {
        throw new \RuntimeException("The name of the environment variable cannot be empty.");
      }
      // If the variable doesn't exist, getenv() returns FALSE; we can represent
      // that as an empty string.
      return (string) getenv($settings['env']);
      $name = $settings['env'];
      $value = getenv($name);
      // If the variable is defined, getenv() will return it as a string.
      // Otherwise, try to return a fallback value, and if that fails, throw an
      // exception (to be consistent with the `config` behavior).
      if (is_string($value)) {
        return $value;
      }
      return array_key_exists('fallback', $settings)
        ? $settings['fallback']
        : throw new \RuntimeException("The '$name' environment variable is not defined.");
    }
    return $settings['value'];
  }
+3 −0
Changes for core/lib/Drupal/Core/Recipe/Recipe.php: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -250,6 +250,9 @@ private static function parse(string $file): array {
                      ]),
                    ]),
                  ]),
                  // An optional fallback value if trying to get a default value
                  // from a non-existent config object.
                  'fallback' => new Optional(),
                  'env' => new Optional([
                    new Type('string'),
                    new NotBlank(),
+82 −24
Changes for core/tests/Drupal/KernelTests/Core/Recipe/InputTest.php: 82 added lines, 24 removed lines.
Original line number Diff line number Diff line
@@ -163,25 +163,48 @@ public function testMissingArgumentsThrowsException(): void {
  }

  /**
   * Tests getting the default value from non-existing configuration.
   * Tests getting the fallback default value from non-existing configuration.
   *
   * @covers \Drupal\Core\Recipe\InputConfigurator::getDefaultValue
   */
  public function testDefaultValueFromNonExistentConfig(): void {
    $recipe = $this->createRecipe(<<<YAML
name: 'Default value from non-existent config'
input:
  capital:
    data_type: string
    description: This will be erroneous.
    default:
      source: config
      config: ['foo.baz', 'bar']
YAML
    );
  public function testDefaultValueFromNonExistentConfigWithFallback(): void {
    $recipe_data = [
      'name' => 'Default value from non-existent config',
      'input' => [
        'capital' => [
          'data_type' => 'string',
          'description' => 'This will use the fallback value.',
          'default' => [
            'source' => 'config',
            'config' => ['foo.baz', 'bar'],
            'fallback' => 'fallback',
          ],
        ],
      ],
    ];
    $recipe = $this->createRecipe($recipe_data);
    // Mock an input collector that will return the default value.
    $collector = $this->createMock(InputCollectorInterface::class);
    $collector->expects($this->atLeastOnce())
      ->method('collectValue')
      ->withAnyParameters()
      ->willReturnArgument(2);

    $recipe->input->collectAll($collector);
    $this->assertSame(['capital' => 'fallback'], $recipe->input->getValues());

    // NULL is an allowable fallback value.
    $recipe_data['input']['capital']['default']['fallback'] = NULL;
    $recipe = $this->createRecipe($recipe_data);
    $recipe->input->collectAll($collector);
    $this->assertSame(['capital' => NULL], $recipe->input->getValues());

    // If there's no fallback value at all, we should get an exception.
    unset($recipe_data['input']['capital']['default']['fallback']);
    $recipe = $this->createRecipe($recipe_data);
    $this->expectException(\RuntimeException::class);
    $this->expectExceptionMessage("The 'foo.baz' config object does not exist.");
    $recipe->input->collectAll($this->createMock(InputCollectorInterface::class));
    $recipe->input->collectAll($collector);
  }

  /**
@@ -337,18 +360,11 @@ public function testDefaultInputFromEnvironmentVariables(): void {
    default:
      source: env
      env: SITE_NAME
  slogan:
    data_type: string
    description: The site slogan.
    default:
      source: env
      env: SITE_SLOGAN
config:
  actions:
    system.site:
      simpleConfigUpdate:
        name: \${name}
        slogan: \${slogan}
YAML
    );
    putenv('SITE_NAME=Input Test');
@@ -364,9 +380,51 @@ public function testDefaultInputFromEnvironmentVariables(): void {
    RecipeRunner::processRecipe($recipe);
    $config = $this->config('system.site');
    $this->assertSame('Input Test', $config->get('name'));
    // There was no SITE_SLOGAN environment variable, so it should have been
    // set to an empty string.
    $this->assertSame('', $config->get('slogan'));
  }

  /**
   * Tests getting a fallback value for an undefined environment variable.
   *
   * @covers \Drupal\Core\Recipe\InputConfigurator::getDefaultValue
   */
  public function testFallbackValueForUndefinedEnvironmentVariable(): void {
    $recipe_data = [
      'name' => 'Default value from undefined environment variable',
      'input' => [
        'capital' => [
          'data_type' => 'string',
          'description' => 'This will use the fallback value.',
          'default' => [
            'source' => 'env',
            'env' => 'NO_SUCH_THING',
            'fallback' => 'fallback',
          ],
        ],
      ],
    ];
    // Mock an input collector that will return the default value.
    $collector = $this->createMock(InputCollectorInterface::class);
    $collector->expects($this->atLeastOnce())
      ->method('collectValue')
      ->withAnyParameters()
      ->willReturnArgument(2);

    $recipe = $this->createRecipe($recipe_data);
    $recipe->input->collectAll($collector);
    $this->assertSame(['capital' => 'fallback'], $recipe->input->getValues());

    // NULL is an allowable fallback value.
    $recipe_data['input']['capital']['default']['fallback'] = NULL;
    $recipe = $this->createRecipe($recipe_data);
    $recipe->input->collectAll($collector);
    $this->assertSame(['capital' => NULL], $recipe->input->getValues());

    // If there's no fallback value at all, we should get an exception.
    unset($recipe_data['input']['capital']['default']['fallback']);
    $recipe = $this->createRecipe($recipe_data);
    $this->expectException(\RuntimeException::class);
    $this->expectExceptionMessage("The 'NO_SUCH_THING' environment variable is not defined.");
    $recipe->input->collectAll($collector);
  }

}