Lines 100.00% 17 / 17
Functions and Methods 100.00% 7 / 7
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
ConfigLoader 100.00% 17 / 17 100.00% 7 / 7 12 100.00% 1 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 mergeConfigTree 100.00% 8 / 8 100.00% 1 / 1 3
 loadConfig 100.00% 1 / 1 100.00% 1 / 1 1
 buildValidatedConfigTree 100.00% 3 / 3 100.00% 1 / 1 1
 locateConfigFile 100.00% 1 / 1 100.00% 1 / 1 1
 findMissingDefaultFile 100.00% 2 / 2 100.00% 1 / 1 3
 readConfigFile 100.00% 1 / 1 100.00% 1 / 1 2
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Runtime\Config;
6
7use Stewart\Contracts\Exception\IdentifierException;
8use Stewart\Runtime\Exception\ConfigurationError;
9use Stewart\Runtime\Exception\ConfigurationException;
10use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
11use Symfony\Component\Config\Definition\Processor;
12
13final readonly class ConfigLoader
14{
15    private const string DEFAULT_CONFIG_FILE = 'stewart.yaml';
16
17    public function __construct(
18        private EnvironmentPlaceholders $placeholders,
19        private EnvironmentOverlay $overlay,
20        private ConfigFileReader $files,
21        private ProjectRoot $projectRoot,
22        private StewartConfigSchema $configuration,
23    ) {}
24
25    /**
26     * @param array<string, mixed> $cliOverrides
27     * @return array<array-key, mixed>
28     * @throws ConfigurationException
29     */
30    public function mergeConfigTree(?string $path, array $cliOverrides = []): array
31    {
32        $tree = $this->configuration->buildConfigTree();
33        $fileConfig = $this->placeholders->resolve($tree, $this->readConfigFile($path));
34        $configs = [$fileConfig, $this->overlay->collectEnvironmentOverrides($tree, $fileConfig)];
35
36        if ($cliOverrides !== []) {
37            $configs[] = $cliOverrides;
38        }
39
40        try {
41            return new Processor()->process($tree, $configs);
42        } catch (InvalidConfigurationException $e) {
43            throw ConfigurationException::createForReason(ConfigurationError::SchemaViolation, $e);
44        }
45    }
46
47    /**
48     * @param array<string, mixed> $cliOverrides
49     * @throws ConfigurationException
50     * @throws IdentifierException
51     */
52    public function loadConfig(?string $path, array $cliOverrides = []): StewartConfig
53    {
54        return StewartConfig::fromSection(ConfigSection::forRoot($this->mergeConfigTree($path, $cliOverrides)));
55    }
56
57    /**
58     * @return array<array-key, mixed>
59     * @throws ConfigurationException
60     * @throws IdentifierException
61     */
62    public function buildValidatedConfigTree(?string $path): array
63    {
64        $tree = $this->mergeConfigTree($path);
65        StewartConfig::fromSection(ConfigSection::forRoot($tree));
66
67        return $tree;
68    }
69
70    public function locateConfigFile(?string $path): string
71    {
72        return $path ?? $this->projectRoot->resolvePath(self::DEFAULT_CONFIG_FILE);
73    }
74
75    public function findMissingDefaultFile(?string $path): ?string
76    {
77        $defaultFile = $this->locateConfigFile(null);
78
79        return $path === null && !is_file($defaultFile) ? $defaultFile : null;
80    }
81
82    /**
83     * @return array<array-key, mixed>
84     * @throws ConfigurationException
85     */
86    private function readConfigFile(?string $path): array
87    {
88        // Container setups may configure everything through the environment.
89        return $this->findMissingDefaultFile($path) !== null ? [] : $this->files->readYamlMap($this->locateConfigFile($path));
90    }
91}