Passed
Push — master ( 750155...96e4ec )
by Thorsten
08:12
created

ConfigProvider::resolvePath()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 26
c 0
b 0
f 0
ccs 19
cts 19
cp 1
rs 8.439
cc 5
eloc 19
nc 5
nop 3
crap 5
1
<?php
2
/**
3
 * This file is part of the daikon-cqrs/config project.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
declare(strict_types=1);
10
11
namespace Daikon\Config;
12
13
use Assert\Assertion;
14
15
final class ConfigProvider implements ConfigProviderInterface
16
{
17
    private const INTERPOLATION_PATTERN = '/(\$\{(.*?)\})/';
18
19
    /** @var mixed[] */
20
    private $config;
21
22
    /** @var ConfigProviderParamsInterface */
23
    private $params;
24
25
    /** @var mixed[] */
26
    private $paramInterpolations;
27
28 3
    public function __construct(ConfigProviderParamsInterface $params)
29
    {
30 3
        $this->params = $params;
31 3
        $this->config = [];
32 3
        $this->paramInterpolations = [];
33 3
    }
34
35
    /**
36
     * @param string $path
37
     * @param mixed|null $default
38
     * @return mixed|null
39
     */
40 7
    public function get(string $path, $default = null)
41
    {
42 7
        $path = ConfigPath::fromString($path);
43 7
        $scope = $path->getScope();
44 7
        Assertion::keyNotExists(
45 7
            $this->paramInterpolations,
46 7
            $scope,
47
            'Recursive interpolations are not allowed when interpolating "locations" or "sources". '.
48 7
            sprintf('Trying to recurse into scope: "%s"', $scope)
49
        );
50 7
        if (!isset($this->config[$scope]) && $this->params->hasScope($scope)) {
51 7
            $this->config[$scope] = $this->loadScope($scope);
52 5
        } elseif (!isset($this->config[$scope])) {
53 2
            return $default;
54
        }
55 6
        return $this->resolvePath(
56 6
            $path->getParts(),
57 6
            $this->config[$path->getScope()],
58 6
            $path->getSeparator()
59 6
        ) ?? $default;
60
    }
61
62 1
    public function has(string $path): bool
63
    {
64 1
        return $this->get($path) !== null;
65
    }
66
67 7
    private function loadScope(string $scope): array
68
    {
69 7
        $this->paramInterpolations[$scope] = true;
70 7
        $locations = $this->params->getLocations($scope);
71 7
        $sources = $this->params->getSources($scope);
72 7
        $loader = $this->params->getLoader($scope);
73 7
        if (!$loader instanceof ArrayConfigLoader) {
74 2
            $sources = $this->interpolateConfigValues($sources);
75
        }
76 6
        $locations = $this->interpolateConfigValues($locations);
77 6
        unset($this->paramInterpolations[$scope]);
78
79 6
        $this->config[$scope] = $loader->load($locations, $sources);
80 6
        return $this->interpolateConfigValues($this->config[$scope]);
81
    }
82
83
    /**
84
     * @param array $parts
85
     * @param mixed[] $values
86
     * @param string $separator
87
     * @return mixed|null
88
     */
89 6
    private function resolvePath(array $parts, array $values, string $separator)
90
    {
91 6
        $pos = 0;
92 6
        $length = count($parts);
93 6
        $value = &$values;
94 6
        while (!empty($parts)) {
95 6
            $pos++;
96 6
            $part = array_shift($parts);
97 6
            if ($part === ConfigPathInterface::WILDCARD_TOKEN) {
98 1
                return $this->expandWildcard($parts, $value, $separator);
99
            }
100 6
            if (!isset($value[$part])) {
101 2
                if ($pos === $length) {
102 1
                    return null;
103
                }
104 2
                array_unshift($parts, $part.$separator.array_shift($parts));
105 2
                continue;
106
            }
107 6
            Assertion::isArray(
108 6
                $value,
109 6
                sprintf('Trying to traverse non array-value with pathpart: "%s"', join($separator, $parts))
110
            );
111 6
            $value = &$value[$part];
112
        }
113 6
        return $value;
114
    }
115
116 1
    private function expandWildcard(array $parts, array $values, string $separator)
117
    {
118
        return array_merge(...array_reduce($values, function ($expanded, $value) use ($parts, $separator): array {
119 1
            $expandedValue = $this->resolvePath($parts, $value, $separator);
120 1
            if (!is_null($expandedValue)) {
121 1
                $expanded[] =  (array)$expandedValue;
122
            }
123 1
            return $expanded;
124 1
        }, []));
125
    }
126
127 7
    private function interpolateConfigValues(array $config): array
128
    {
129 7
        return array_map(
130
            /** @return mixed */
131 7
            function ($value) {
132 7
                if (is_array($value)) {
133 6
                    return $this->interpolateConfigValues($value);
134
                }
135 7
                if (is_string($value) && preg_match_all(self::INTERPOLATION_PATTERN, $value, $matches)) {
136 3
                    return str_replace($matches[0], array_map([$this, "get"], $matches[2]), $value);
137
                }
138 6
                return $value;
139 7
            },
140 7
            $config
141
        );
142
    }
143
}
144