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