Lines
100.00%
31 / 31
Methods
100.00%
6 / 6
Classes
100.00%
1 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| resolve | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| substituteInTree | 100.00% | 7 / 7 | 100.00% | 1 / 1 | 6 | ||
| findChildNode | 100.00% | 4 / 4 | 100.00% | 1 / 1 | 6 | ||
| resolveValue | 100.00% | 4 / 4 | 100.00% | 1 / 1 | 3 | ||
| substitute | 100.00% | 14 / 14 | 100.00% | 1 / 1 | 5 | ||
| 15 | final readonly class EnvironmentPlaceholders | |
| 16 | { | |
| 17 | private const string PLACEHOLDER = '/\$\$\{|\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/'; | |
| 18 | ||
| 19 | private const string WHOLE_VALUE_PLACEHOLDER = '/\A\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-[^}]*)?\}\z/'; | |
| 20 | ||
| 21 | public function __construct( | |
| 22 | private EnvironmentVariables $variables, | |
| 23 | private EnvironmentValueParser $environmentValueParser, | |
| 24 | ) {} | |
| 25 | ||
| 26 | /** | |
| 27 | * @param array<array-key, mixed> $file | |
| 28 | * @return array<array-key, mixed> | |
| 29 | * @throws ConfigurationException | |
| 30 | */ | |
| 31 | public function resolve(NodeInterface $tree, array $file): array | |
| 32 | { | |
| 33 | return $this->substituteInTree($file, $tree, ''); | |
| 34 | } | |
| 35 | ||
| 36 | /** | |
| 37 | * @param array<array-key, mixed> $file | |
| 38 | * @return array<array-key, mixed> | |
| 39 | * @throws ConfigurationException | |
| 40 | */ | |
| 41 | private function substituteInTree(array $file, ?NodeInterface $node, string $path): array | |
| 42 | { | |
| 43 | foreach ($file as $key => $value) { | |
| 44 | $setting = $path === '' ? (string) $key : $path . '.' . $key; | |
| 45 | $child = $this->findChildNode($node, (string) $key); | |
| 46 | ||
| 47 | $file[$key] = match (true) { | |
| 48 | \is_array($value) => $this->substituteInTree($value, $child, $setting), | |
| 49 | \is_string($value) => $this->resolveValue($value, $child, $setting), | |
| 50 | default => $value, | |
| 51 | }; | |
| 52 | } | |
| 53 | ||
| 54 | return $file; | |
| 55 | } | |
| 56 | ||
| 57 | private function findChildNode(?NodeInterface $node, string $key): ?NodeInterface | |
| 58 | { | |
| 59 | return match (true) { | |
| 60 | $node instanceof PrototypedArrayNode => $node->getPrototype(), | |
| 61 | $node instanceof ArrayNode => $node->getChildren()[$key] ?? $node->getChildren()[strtr($key, '-', '_')] ?? null, | |
| 62 | // ScalarNode extends VariableNode, so only a free-form node passes its type on to nested values. | |
| 63 | $node !== null && $node::class === VariableNode::class => $node, | |
| 64 | default => null, | |
| 65 | }; | |
| 66 | } | |
| 67 | ||
| 68 | /** @throws ConfigurationException */ | |
| 69 | private function resolveValue(string $value, ?NodeInterface $node, string $setting): mixed | |
| 70 | { | |
| 71 | $substituted = $this->substitute($value, $setting); | |
| 72 | ||
| 73 | if ($node === null || preg_match(self::WHOLE_VALUE_PLACEHOLDER, $value, $match) !== 1) { | |
| 74 | return $substituted; | |
| 75 | } | |
| 76 | ||
| 77 | return $this->environmentValueParser->parseForNode($node, \sprintf('%s (${%s})', $setting, $match[1]), $substituted); | |
| 78 | } | |
| 79 | ||
| 80 | /** @throws ConfigurationException */ | |
| 81 | private function substitute(string $value, string $setting): string | |
| 82 | { | |
| 83 | return preg_replace_callback( | |
| 84 | self::PLACEHOLDER, | |
| 85 | function (array $match) use ($setting): string { | |
| 86 | $name = $match[1] ?? null; | |
| 87 | ||
| 88 | if ($name === null) { | |
| 89 | return '${'; | |
| 90 | } | |
| 91 | ||
| 92 | $variable = $this->variables->get($name); | |
| 93 | $default = $match[2] ?? null; | |
| 94 | ||
| 95 | if ($default !== null && ($variable === null || $variable === '')) { | |
| 96 | return $default; | |
| 97 | } | |
| 98 | ||
| 99 | return $variable ?? throw ConfigurationException::createForReason(ConfigurationError::PlaceholderUnset, setting: $setting, variable: $name); | |
| 100 | }, | |
| 101 | $value, | |
| 102 | ) ?? $value; | |
| 103 | } | |
| 104 | } |