Skip to content

Commit b56c276

Browse files
committed
Extract IniLoader, make Container format-agnostic
Container is now a pure PSR-11 container with no INI or format awareness. Its constructor accepts only typed arrays. All DSL interpretation (spaces in keys, [references], constants, new/autowire modifiers) lives in IniLoader, which operates on a Container instance.
1 parent 652685c commit b56c276

File tree

8 files changed

+785
-401
lines changed

8 files changed

+785
-401
lines changed

phpstan.neon.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ parameters:
1212
-
1313
message: '/Access to an undefined property Respect\\Config\\Container::\$/'
1414
path: tests/LazyLoadTest.php
15-
count: 2
15+
count: 1

src/Container.php

Lines changed: 5 additions & 295 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
use ArrayObject;
88
use Closure;
9-
use InvalidArgumentException;
109
use Psr\Container\ContainerInterface;
1110
use ReflectionClass;
1211
use ReflectionException;
@@ -18,38 +17,24 @@
1817
use function assert;
1918
use function call_user_func;
2019
use function class_exists;
21-
use function constant;
22-
use function count;
23-
use function current;
24-
use function defined;
25-
use function explode;
26-
use function file_exists;
2720
use function func_get_args;
2821
use function is_array;
2922
use function is_callable;
30-
use function is_object;
3123
use function is_string;
32-
use function parse_ini_file;
33-
use function parse_ini_string;
34-
use function preg_match;
35-
use function preg_replace;
36-
use function preg_replace_callback;
37-
use function str_contains;
38-
use function trim;
3924

4025
/** @extends ArrayObject<string, mixed> */
4126
class Container extends ArrayObject implements ContainerInterface
4227
{
43-
public function __construct(protected mixed $configurator = null)
28+
/** @param array<string, mixed> $definitions */
29+
public function __construct(array $definitions = [])
4430
{
31+
foreach ($definitions as $key => $value) {
32+
$this->offsetSet((string) $key, $value);
33+
}
4534
}
4635

4736
public function has(string $id): bool
4837
{
49-
if ($this->configurator) {
50-
$this->configure();
51-
}
52-
5338
if (!parent::offsetExists($id)) {
5439
return false;
5540
}
@@ -64,10 +49,6 @@ public function has(string $id): bool
6449

6550
public function getItem(string $name, bool $raw = false): mixed
6651
{
67-
if ($this->configurator) {
68-
$this->configure();
69-
}
70-
7152
if (!isset($this[$name])) {
7253
throw new NotFoundException('Item ' . $name . ' not found');
7354
}
@@ -88,44 +69,6 @@ public function get(string $id): mixed
8869
return $this->getItem($id);
8970
}
9071

91-
public function loadString(string $configurator): void
92-
{
93-
$iniData = parse_ini_string($configurator, true);
94-
if ($iniData === false || count($iniData) === 0) {
95-
throw new InvalidArgumentException('Invalid configuration string');
96-
}
97-
98-
$this->loadArray($iniData);
99-
}
100-
101-
public function loadFile(string $configurator): void
102-
{
103-
$iniData = parse_ini_file($configurator, true);
104-
if ($iniData === false) {
105-
throw new InvalidArgumentException('Invalid configuration INI file');
106-
}
107-
108-
$this->loadArray($iniData);
109-
}
110-
111-
/** @param array<string, mixed> $configurator */
112-
public function loadArray(array $configurator): void
113-
{
114-
foreach ($this->state() + $configurator as $key => $value) {
115-
if ($value instanceof Closure) {
116-
$this->offsetSet((string) $key, $value);
117-
continue;
118-
}
119-
120-
if ($value instanceof Instantiator) {
121-
$this->offsetSet((string) $key, $value);
122-
continue;
123-
}
124-
125-
$this->parseItem($key, $value);
126-
}
127-
}
128-
12972
public function offsetSet(mixed $key, mixed $value): void
13073
{
13174
if ($value instanceof Autowire) {
@@ -140,235 +83,6 @@ public function set(string $name, mixed $value): void
14083
$this[$name] = $value;
14184
}
14285

143-
protected function configure(): void
144-
{
145-
$configurator = $this->configurator;
146-
$this->configurator = null;
147-
148-
if ($configurator === null) {
149-
return;
150-
}
151-
152-
if (is_array($configurator)) {
153-
$this->loadArray($configurator);
154-
155-
return;
156-
}
157-
158-
if (is_string($configurator) && file_exists($configurator)) {
159-
$this->loadFile($configurator);
160-
161-
return;
162-
}
163-
164-
if (is_string($configurator)) {
165-
$this->loadString($configurator);
166-
167-
return;
168-
}
169-
170-
throw new InvalidArgumentException('Invalid input. Must be a valid file or array');
171-
}
172-
173-
/** @return array<string, mixed> */
174-
protected function state(): array
175-
{
176-
return array_filter(
177-
$this->getArrayCopy(),
178-
static fn($v): bool => !is_object($v) || !$v instanceof Instantiator,
179-
);
180-
}
181-
182-
protected function keyHasStateInstance(string $key, mixed &$k): bool
183-
{
184-
return $this->offsetExists($k = current(explode(' ', $key)));
185-
}
186-
187-
protected function keyHasInstantiator(string $key): bool
188-
{
189-
return str_contains($key, ' ');
190-
}
191-
192-
protected function parseItem(string|int $key, mixed $value): void
193-
{
194-
$key = trim((string) $key);
195-
if ($this->keyHasInstantiator($key)) {
196-
if ($this->keyHasStateInstance($key, $k)) {
197-
$this->offsetSet($key, $this[$k]);
198-
} else {
199-
$this->parseInstantiator($key, $value);
200-
}
201-
} else {
202-
$this->parseStandardItem($key, $value);
203-
}
204-
}
205-
206-
/**
207-
* @param array<mixed> $value
208-
*
209-
* @return array<mixed>
210-
*/
211-
protected function parseSubValues(array &$value): array
212-
{
213-
foreach ($value as &$subValue) {
214-
$subValue = $this->parseValue($subValue);
215-
}
216-
217-
return $value;
218-
}
219-
220-
protected function parseStandardItem(string $key, mixed &$value): void
221-
{
222-
if (is_array($value)) {
223-
$this->parseSubValues($value);
224-
} else {
225-
$value = $this->parseValue($value);
226-
}
227-
228-
$this->offsetSet($key, $value);
229-
}
230-
231-
protected function removeDuplicatedSpaces(string $string): string
232-
{
233-
return (string) preg_replace('/\s+/', ' ', $string);
234-
}
235-
236-
protected function parseInstantiator(string $key, mixed $value): void
237-
{
238-
$key = $this->removeDuplicatedSpaces($key);
239-
[$keyName, $keyClass] = explode(' ', $key, 2);
240-
if ($keyName === 'instanceof') {
241-
$keyName = $keyClass;
242-
}
243-
244-
/** @var class-string $keyClass */
245-
$instantiator = $this->createInstantiator($keyClass);
246-
247-
if (is_array($value)) {
248-
foreach ($value as $property => $pValue) {
249-
$instantiator->setParam($property, $this->parseValue($pValue));
250-
}
251-
} else {
252-
$instantiator->setParam('__construct', $this->parseValue($value));
253-
}
254-
255-
$this->offsetSet($keyName, $instantiator);
256-
}
257-
258-
/** @param class-string $keyClass */
259-
protected function createInstantiator(string $keyClass): Instantiator
260-
{
261-
if (!str_contains($keyClass, ' ')) {
262-
return new Instantiator($keyClass);
263-
}
264-
265-
[$modifier, $className] = explode(' ', $keyClass, 2);
266-
267-
/** @var class-string $className */
268-
return match ($modifier) {
269-
'new' => new Factory($className),
270-
'autowire' => new Autowire($className),
271-
default => new Instantiator($keyClass),
272-
};
273-
}
274-
275-
protected function parseValue(mixed $value): mixed
276-
{
277-
if ($value instanceof Instantiator) {
278-
return $value;
279-
}
280-
281-
if (is_array($value)) {
282-
return $this->parseSubValues($value);
283-
}
284-
285-
if (empty($value)) {
286-
return null;
287-
}
288-
289-
if (!is_string($value)) {
290-
return $value;
291-
}
292-
293-
return $this->parseSingleValue($value);
294-
}
295-
296-
protected function hasCompleteBrackets(string $value): bool
297-
{
298-
return str_contains($value, '[') && str_contains($value, ']');
299-
}
300-
301-
protected function parseSingleValue(string $value): mixed
302-
{
303-
$value = trim($value);
304-
if ($this->hasCompleteBrackets($value)) {
305-
return $this->parseBrackets($value);
306-
}
307-
308-
return $this->parseConstants($value);
309-
}
310-
311-
protected function parseConstants(string $value): mixed
312-
{
313-
if (preg_match('/^[\\\\a-zA-Z_]+([:]{2}[A-Z_]+)?$/', $value) && defined($value)) {
314-
return constant($value);
315-
}
316-
317-
return $value;
318-
}
319-
320-
protected function matchSequence(string &$value): bool
321-
{
322-
if (preg_match('/^\[(.*?,.*?)\]$/', $value, $match)) {
323-
$value = $match[1];
324-
325-
return true;
326-
}
327-
328-
return false;
329-
}
330-
331-
protected function matchReference(string &$value): bool
332-
{
333-
if (preg_match('/^\[([[:alnum:]_\\\\]+)\]$/', $value, $match)) {
334-
$value = $match[1];
335-
336-
return true;
337-
}
338-
339-
return false;
340-
}
341-
342-
protected function parseBrackets(string $value): mixed
343-
{
344-
if ($this->matchSequence($value)) {
345-
return $this->parseArgumentList($value);
346-
}
347-
348-
if ($this->matchReference($value)) {
349-
return $this->getItem($value, true);
350-
}
351-
352-
return $this->parseVariables($value);
353-
}
354-
355-
protected function parseVariables(string $value): string
356-
{
357-
return (string) preg_replace_callback(
358-
'/\[(\w+)\]/',
359-
fn(array $match): string => $this[$match[1]] ?: '',
360-
$value,
361-
);
362-
}
363-
364-
/** @return array<mixed> */
365-
protected function parseArgumentList(string $value): array
366-
{
367-
$subValues = explode(',', $value);
368-
369-
return $this->parseSubValues($subValues);
370-
}
371-
37286
protected function lazyLoad(string $name): mixed
37387
{
37488
$callback = $this[$name];
@@ -431,10 +145,6 @@ static function ($param) use ($container) {
431145
parent::offsetSet($name, $item);
432146
}
433147

434-
if ($this->configurator) {
435-
$this->configure();
436-
}
437-
438148
return $this;
439149
}
440150

0 commit comments

Comments
 (0)