diff --git a/core/lib/Drupal/Core/Config/Schema/Mapping.php b/core/lib/Drupal/Core/Config/Schema/Mapping.php index a0b305ee2649275aa99ad3aa88d94a0c3444e5cf..99f48104f6a7e5a30d9679804f6499f31966a696 100644 --- a/core/lib/Drupal/Core/Config/Schema/Mapping.php +++ b/core/lib/Drupal/Core/Config/Schema/Mapping.php @@ -119,8 +119,8 @@ protected function getDefinedKeys(): array { * - the corresponding value an array of the additional mapping keys that * are supported for this resolved type * - * @see \Drupal\Core\Config\TypedConfigManager::replaceName() - * @see \Drupal\Core\Config\TypedConfigManager::replaceVariable() + * @see \Drupal\Core\Config\TypedConfigManager::resolveDynamicTypeName() + * @see \Drupal\Core\Config\TypedConfigManager::resolveExpression() * @see https://www.drupal.org/files/ConfigSchemaCheatSheet2.0.pdf */ public function getDynamicallyValidKeys(): array { diff --git a/core/lib/Drupal/Core/Config/TypedConfigManager.php b/core/lib/Drupal/Core/Config/TypedConfigManager.php index 054247d06a3bd7c88e381618358647536e51270a..7d1981c76d40cd18a21c9b408fc7e82f8afc688c 100644 --- a/core/lib/Drupal/Core/Config/TypedConfigManager.php +++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php @@ -104,7 +104,7 @@ public function buildDataDefinition(array $definition, $value, $name = NULL, $pa if (isset($name)) { $replace['%key'] = $name; } - $type = $this->replaceName($type, $replace); + $type = $this->resolveDynamicTypeName($type, $replace); // Remove the type from the definition so that it is replaced with the // concrete type from schema definitions. unset($definition['type']); @@ -150,7 +150,7 @@ protected function determineType($base_plugin_id, array $definitions) { } /** - * Gets a schema definition with replacements for dynamic names. + * Gets a schema definition with replacements for dynamic type names. * * @param string $base_plugin_id * A plugin ID. @@ -177,7 +177,7 @@ protected function getDefinitionWithReplacements($base_plugin_id, array $replace // Replace dynamic portions of the definition type. if (!empty($replacements) && strpos($definition['type'], ']')) { - $sub_type = $this->determineType($this->replaceName($definition['type'], $replacements), $definitions); + $sub_type = $this->determineType($this->resolveDynamicTypeName($definition['type'], $replacements), $definitions); $sub_definition = $definitions[$sub_type]; if (isset($definitions[$sub_type]['type'])) { $sub_merge = $this->getDefinition($definitions[$sub_type]['type'], $exception_on_invalid); @@ -282,39 +282,69 @@ protected function getFallbackName($name) { } /** - * Replaces variables in configuration name. + * Replaces dynamic type expressions in configuration type. * - * The configuration name may contain one or more variables to be replaced, - * enclosed in square brackets like '[name]' and will follow the replacement - * rules defined by the replaceVariable() method. + * The configuration type name may contain one or more expressions to be + * replaced, enclosed in square brackets like '[name]' or '[%parent.id]' and + * will follow the replacement rules defined by the resolveExpression() + * method. * - * @param string $name - * Configuration name with variables in square brackets. - * @param mixed $data + * @param string $type + * Configuration type, potentially with expressions in square brackets. + * @param array $data * Configuration data for the element. * * @return string - * Configuration name with variables replaced. + * Configuration type name with all expressions resolved. */ - protected function replaceName($name, $data) { - if (preg_match_all("/\[(.*)\]/U", $name, $matches)) { + protected function resolveDynamicTypeName(string $type, array $data): string { + // Parse the expressions in the dynamic type, if any. + if (preg_match_all("/\[(.*)\]/U", $type, $matches)) { // Build our list of '[value]' => replacement. $replace = []; foreach (array_combine($matches[0], $matches[1]) as $key => $value) { - $replace[$key] = $this->replaceVariable($value, $data); + $replace[$key] = $this->resolveExpression($value, $data); } - return strtr($name, $replace); + return strtr($type, $replace); } else { - return $name; + // No expressions: nothing to resolve. + return $type; } } /** - * Replaces variable values in included names with configuration data. + * Replaces dynamic type expressions in configuration type. + * + * The configuration type name may contain one or more expressions to be + * replaced, enclosed in square brackets like '[name]' or '[%parent.id]' and + * will follow the replacement rules defined by the resolveExpression() + * method. + * + * @param string $name + * Configuration type, potentially with expressions in square brackets. + * @param array $data + * Configuration data for the element. + * + * @return string + * Configuration type name with all expressions resolved. + * + * @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use + * ::resolveDynamicTypeName() instead. + * + * @see https://www.drupal.org/node/3408266 + */ + protected function replaceName($name, $data) { + @trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use ::resolveDynamicTypeName() instead. See https://www.drupal.org/node/3408266', E_USER_DEPRECATED); + return $this->resolveDynamicTypeName($name, $data); + } + + /** + * Resolves a dynamic type expression using configuration data. * - * Variable values are nested configuration keys that will be replaced by - * their value or some of these special strings: + * Dynamic type names are nested configuration keys containing expressions to + * be replaced by the value at the property path that the expression is + * pointing at. The expression may contain the following special strings: * - '%key', will be replaced by the element's key. * - '%parent', to reference the parent element. * - '%type', to reference the schema definition type. Can only be used in @@ -324,7 +354,7 @@ protected function replaceName($name, $data) { * patterns like '%parent.name' which references the 'name' value of the * parent element. * - * Example patterns: + * Example expressions: * - 'name.subkey', indicates a nested value of the current element. * - '%parent.name', will be replaced by the 'name' value of the parent. * - '%parent.%key', will be replaced by the parent element's key. @@ -332,29 +362,33 @@ protected function replaceName($name, $data) { * - '%parent.%parent.%type', will be replaced by the schema type of the * parent's parent. * - * @param string $value - * Variable value to be replaced. - * @param mixed $data + * @param string $expression + * Expression to be resolved. + * @param array $data * Configuration data for the element. * * @return string - * The replaced value if a replacement found or the original value if not. + * The value the expression resolves to, or the given expression if it + * cannot be resolved. + * + * @todo Validate the expression in https://www.drupal.org/project/drupal/issues/3392903 */ - protected function replaceVariable($value, $data) { - $parts = explode('.', $value); + protected function resolveExpression(string $expression, array $data): string { + assert(!str_contains($expression, '[') && !str_contains($expression, ']')); + $parts = explode('.', $expression); // Process each value part, one at a time. while ($name = array_shift($parts)) { if (!is_array($data) || !isset($data[$name])) { // Key not found, return original value - return $value; + return $expression; } elseif (!$parts) { - $value = $data[$name]; - if (is_bool($value)) { - $value = (int) $value; + $expression = $data[$name]; + if (is_bool($expression)) { + $expression = (int) $expression; } // If no more parts left, this is the final property. - return (string) $value; + return (string) $expression; } else { // Get nested value and continue processing. @@ -375,6 +409,51 @@ protected function replaceVariable($value, $data) { } } } + + // Satisfy PHPStan, which cannot interpret the loop. + return $expression; + } + + /** + * Resolves a dynamic type expression using configuration data. + * + * Dynamic type names are nested configuration keys containing expressions to + * be replaced by the value at the property path that the expression is + * pointing at. The expression may contain the following special strings: + * - '%key', will be replaced by the element's key. + * - '%parent', to reference the parent element. + * - '%type', to reference the schema definition type. Can only be used in + * combination with %parent. + * + * There may be nested configuration keys separated by dots or more complex + * patterns like '%parent.name' which references the 'name' value of the + * parent element. + * + * Example expressions: + * - 'name.subkey', indicates a nested value of the current element. + * - '%parent.name', will be replaced by the 'name' value of the parent. + * - '%parent.%key', will be replaced by the parent element's key. + * - '%parent.%type', will be replaced by the schema type of the parent. + * - '%parent.%parent.%type', will be replaced by the schema type of the + * parent's parent. + * + * @param string $value + * Expression to be resolved. + * @param array $data + * Configuration data for the element. + * + * @return string + * The value the expression resolves to, or the given expression if it + * cannot be resolved. + * + * @deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use + * ::resolveExpression() instead. + * + * @see https://www.drupal.org/node/3408266 + */ + protected function replaceVariable($value, $data) { + @trigger_error(__METHOD__ . '() is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. Use ::resolveExpression() instead. See https://www.drupal.org/node/3408266', E_USER_DEPRECATED); + return $this->resolveExpression($value, $data); } /** diff --git a/core/phpstan-baseline.neon b/core/phpstan-baseline.neon index ba653f410f80dbb3b1a49596149eb8f9e9db114a..a37bb883d88123ea30b6b183104d84cc328c5f27 100644 --- a/core/phpstan-baseline.neon +++ b/core/phpstan-baseline.neon @@ -344,11 +344,6 @@ parameters: count: 1 path: lib/Drupal/Core/Config/ExtensionInstallStorage.php - - - message: "#^Method Drupal\\\\Core\\\\Config\\\\TypedConfigManager\\:\\:replaceVariable\\(\\) should return string but return statement is missing\\.$#" - count: 1 - path: lib/Drupal/Core/Config/TypedConfigManager.php - - message: """ #^Fetching deprecated class constant RETURN_AFFECTED of class Drupal\\\\Core\\\\Database\\\\Database\\: