1: <?php declare(strict_types=1);
2:
3: namespace Salient\Core;
4:
5: use Salient\Core\Concern\ImmutableArrayAccessTrait;
6: use Salient\Core\Exception\InvalidConfigurationException;
7: use Salient\Utility\Arr;
8: use Salient\Utility\File;
9: use Salient\Utility\Regex;
10: use ArrayAccess;
11: use OutOfRangeException;
12: use ReturnTypeWillChange;
13:
14: /**
15: * Provides access to values in configuration files
16: *
17: * @api
18: *
19: * @implements ArrayAccess<string,mixed>
20: */
21: final class ConfigurationManager implements ArrayAccess
22: {
23: /** @use ImmutableArrayAccessTrait<string,mixed> */
24: use ImmutableArrayAccessTrait;
25:
26: /** @var array<string,mixed[]> */
27: private array $Items = [];
28:
29: /**
30: * @param array<string,mixed[]> $items
31: */
32: public function __construct(array $items = [])
33: {
34: $this->Items = $items;
35: }
36:
37: /**
38: * Load values from files in a directory
39: *
40: * @return $this
41: */
42: public function loadDirectory(string $directory): self
43: {
44: $items = [];
45: foreach (File::find()->in($directory)->include('/\.php$/')->doNotRecurse() as $file) {
46: $basename = $file->getBasename('.php');
47: $file = (string) $file;
48: if (Regex::match('/[\s.]|^[0-9]+$/', $basename)) {
49: throw new InvalidConfigurationException(sprintf(
50: 'Invalid configuration file name: %s',
51: $file,
52: ));
53: }
54: $values = require $file;
55: if (!is_array($values)) {
56: throw new InvalidConfigurationException(sprintf(
57: 'Invalid configuration file: %s',
58: $file,
59: ));
60: }
61: $items[$basename] = $values;
62: }
63: ksort($items, \SORT_NATURAL);
64: $this->Items = $items;
65: return $this;
66: }
67:
68: /**
69: * Check if a configuration value exists
70: */
71: public function has(string $key): bool
72: {
73: return Arr::has($this->Items, $key);
74: }
75:
76: /**
77: * Get a configuration value
78: *
79: * @param mixed $default
80: * @return mixed
81: * @throws OutOfRangeException if `$key` is not configured and no `$default`
82: * is given.
83: */
84: public function get(string $key, $default = null)
85: {
86: if (func_num_args() > 1) {
87: return Arr::get($this->Items, $key, $default);
88: }
89: return Arr::get($this->Items, $key);
90: }
91:
92: /**
93: * Get multiple configuration values
94: *
95: * @param array<string|int,mixed|string> $keys An array that optionally maps
96: * keys to default values.
97: * @return array<string,mixed>
98: * @throws OutOfRangeException if a key is not configured and no default
99: * value is given.
100: */
101: public function getMany(array $keys): array
102: {
103: foreach ($keys as $key => $default) {
104: if (is_int($key)) {
105: /** @var string */
106: $key = $default;
107: $values[$key] = Arr::get($this->Items, $key);
108: continue;
109: }
110: $values[$key] = Arr::get($this->Items, $key, $default);
111: }
112: return $values ?? [];
113: }
114:
115: /**
116: * Get all configuration values
117: *
118: * @return array<string,mixed[]>
119: */
120: public function all(): array
121: {
122: return $this->Items;
123: }
124:
125: /**
126: * @internal
127: *
128: * @param string $offset
129: */
130: public function offsetExists($offset): bool
131: {
132: return Arr::get($this->Items, $offset, null) !== null;
133: }
134:
135: /**
136: * @internal
137: *
138: * @param string $offset
139: * @return mixed
140: * @disregard P1038
141: */
142: #[ReturnTypeWillChange]
143: public function offsetGet($offset)
144: {
145: return Arr::get($this->Items, $offset);
146: }
147: }
148: