Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
90 / 90
100.00% covered (success)
100.00%
10 / 10
CRAP
100.00% covered (success)
100.00%
1 / 1
ConfigServiceProvider
100.00% covered (success)
100.00%
90 / 90
100.00% covered (success)
100.00%
10 / 10
37
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 register
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
 boot
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 loadConfigFromFiles
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 initConfigOptions
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 loadConfigFromEnv
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
 getEnvValue
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
7
 loadConfigFromDb
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
6
 parseConfigTypes
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
7
 getConfigPath
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Config;
6
7use Carbon\Carbon as CarbonCarbon;
8use DateTimeZone;
9use Engelsystem\Application;
10use Engelsystem\Container\ServiceProvider;
11use Engelsystem\Helpers\Carbon;
12use Engelsystem\Helpers\CarbonDay;
13use Engelsystem\Models\EventConfig;
14use Exception;
15use Illuminate\Database\QueryException;
16use Illuminate\Support\Env;
17use Illuminate\Support\Str;
18
19/**
20 * Loads the configuration from files (for database connection and app config), database and environment
21 */
22class ConfigServiceProvider extends ServiceProvider
23{
24    protected array $configFiles = ['app.php', 'config.default.php', 'config.local.php', 'config.php'];
25
26    // Remember to update ConfigServiceProviderTest, config.default.php, and README.md
27    protected array $configVarsToPruneNulls = [
28        'themes',
29        'tshirt_sizes',
30        'headers',
31        'header_items',
32        'footer_items',
33        'locales',
34        'contact_options',
35    ];
36
37    protected array $envConfig = [];
38
39    public function __construct(Application $app, protected ?EventConfig $eventConfig = null)
40    {
41        parent::__construct($app);
42    }
43
44    public function register(): void
45    {
46        /** @var Config $config */
47        $config = $this->app->make(Config::class);
48        $this->app->instance(Config::class, $config);
49        $this->app->instance('config', $config);
50
51        $this->loadConfigFromFiles($config);
52        $this->initConfigOptions($config);
53        $this->loadConfigFromEnv($config);
54        $this->parseConfigTypes($config);
55
56        if (empty($config->get(null))) {
57            throw new Exception('Configuration not found');
58        }
59
60        // Prune values with null in file config to remove them
61        foreach ($this->configVarsToPruneNulls as $key) {
62            $values = $config->get($key);
63            if (!$values) {
64                // Skip values that are not defined in files or env
65                continue;
66            }
67            $config->set($key, array_filter($values, function ($v) {
68                return !is_null($v);
69            }));
70        }
71    }
72
73    public function boot(): void
74    {
75        /** @var Config $config */
76        $config = $this->app->get('config');
77
78        $this->loadConfigFromDb($config);
79        $this->loadConfigFromEnv($config);
80
81        $this->parseConfigTypes($config);
82
83        $config->set('env_config', $this->envConfig);
84    }
85
86    protected function loadConfigFromFiles(Config $config): void
87    {
88        foreach ($this->configFiles as $file) {
89            $file = $this->getConfigPath($file);
90
91            if (!file_exists($file)) {
92                continue;
93            }
94
95            $configuration = array_replace_recursive(
96                $config->get(null),
97                require $file
98            );
99            $config->set($configuration);
100        }
101    }
102
103    protected function initConfigOptions(Config $config): void
104    {
105        $configOptions = $config['config_options'];
106        if ($configOptions['system']['config']['timezone'] ?? null) {
107            // Timezone must be set for database connection
108            $configOptions['system']['config']['timezone']['data'] = array_combine(
109                DateTimeZone::listIdentifiers(),
110                DateTimeZone::listIdentifiers()
111            );
112            $config['timezone'] = $config['timezone']
113                ?? $configOptions['system']['config']['timezone']['default']
114                ?? 'UTC';
115
116            $config->set('config_options', $configOptions);
117        }
118    }
119
120    protected function loadConfigFromEnv(Config $config): void
121    {
122        foreach ($config->get('config_options', []) as $options) {
123            foreach ($options['config'] as $name => $option) {
124                $value = $this->getEnvValue(
125                    empty($option['env']) ? $name : $option['env'],
126                    ($option['type'] ?? '') == 'select_multi',
127                );
128                if (is_null($value)) {
129                    continue;
130                }
131
132                $config->set($name, $value);
133            }
134        }
135    }
136
137    protected function getEnvValue(string $name, bool $isList = false): mixed
138    {
139        $name = Str::upper($name);
140        if (isset($this->envConfig[$name])) {
141            return $this->envConfig[$name];
142        }
143
144        $file = Env::get($name . '_FILE');
145        if (!is_null($file) && is_readable($file)) {
146            $value = file_get_contents($file);
147        } else {
148            $value = Env::get($name);
149        }
150
151        if ($isList && !is_null($value)) {
152            $value = empty($value) ? [] : explode(',', preg_replace('~\s+~', '', $value));
153        }
154
155        $this->envConfig[$name] = $value;
156
157        return $value;
158    }
159
160    protected function loadConfigFromDb(Config $config): void
161    {
162        if (!$this->eventConfig) {
163            $this->eventConfig = $this->app->make(EventConfig::class);
164        }
165
166        try {
167            /** @var EventConfig[] $values */
168            $values = $this->eventConfig->newQuery()->get(['name', 'value']);
169        } catch (QueryException) {
170            return;
171        }
172
173        foreach ($values as $option) {
174            $data = $option->value;
175
176            if (is_array($data) && $config->has($option->name)) {
177                $data = array_replace_recursive(
178                    $config->get($option->name),
179                    $data
180                );
181            }
182
183            $config->set($option->name, $data);
184        }
185    }
186
187    protected function parseConfigTypes(Config $config): void
188    {
189        // Parse config types
190        foreach ($config->get('config_options', []) as $page) {
191            foreach ($page['config'] as $name => $options) {
192                $value = $config->get($name, $options['default'] ?? null);
193
194                $value = match ($options['type'] ?? null) {
195                    'datetime-local' => $value && !$value instanceof CarbonCarbon
196                        ? Carbon::createFromDatetime((string) $value)
197                        : $value,
198                    'date' => $value && !$value instanceof CarbonCarbon
199                        ? CarbonDay::createFromDay((string) $value)
200                        : $value,
201                    'boolean' => !empty($value),
202                    'number' => (float) $value,
203                    default => $value,
204                };
205
206                $config->set($name, $value);
207            }
208        }
209    }
210
211    /**
212     * Get the config path
213     */
214    protected function getConfigPath(string $path = ''): string
215    {
216        return config_path($path);
217    }
218}