-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathPathContainer.php
126 lines (110 loc) · 3.46 KB
/
PathContainer.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<?php
declare(strict_types=1);
namespace Dhii\Container;
use Dhii\Collection\ContainerInterface;
use Dhii\Container\Exception\ContainerException;
use Dhii\Container\Exception\NotFoundException;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* A container implementation that decorates a hierarchy of {@link ContainerInterface} instances to allow path-like
* access to deep containers or data.
*
* **Example usage**
*
* Consider the below hierarchy of containers:
*
* ```php
* $container = new Container([
* 'config' => new Container([
* 'db' => new Container([
* 'host' => 'localhost',
* 'port' => 3306
* ])
* ])
* ]);
* ```
*
* A {@link PathContainer} can decorate the `$container` to substitute this:
*
* ```php
* $host = $container->get('config')->get('db')->get('port');
* ```
*
* With this:
*
* ```php
* $pContainer = new PathContainer($container, '.');
* $pContainer->get('config.db.port');
* ```
*
* Note that this implementation DOES NOT create containers for hierarchical _values_. Each segment in a given path
* must correspond to a child {@link ContainerInterface} instance.
*
* @see SegmentingContainer For an implementation that achieves the opposite effect.
*/
class PathContainer implements ContainerInterface
{
/** @var PsrContainerInterface */
protected $inner;
/** @var non-empty-string */
protected $delimiter;
/**
* @param PsrContainerInterface $inner The container instance to decorate.
* @param non-empty-string $delimiter The path delimiter to use.
*/
public function __construct(PsrContainerInterface $inner, string $delimiter = '/')
{
$this->inner = $inner;
$this->delimiter = $delimiter;
}
/**
* @inheritDoc
*/
public function get(string $key)
{
$tKey = (strpos($key, $this->delimiter) === 0)
? substr($key, strlen($this->delimiter))
: $key;
$delimiter = $this->delimiter;
if (!strlen($delimiter)) {
throw new ContainerException('Cannot use empty delimiter');
}
/**
* @psalm-suppress PossiblyFalseArgument
* Result of explode() will never be false, because delimiter is never empty - see above.
*/
$path = array_filter(explode($this->delimiter, $tKey));
if (empty($path)) {
throw new NotFoundException('The path is empty');
}
$current = $this->inner;
$head = $path[0];
while (!empty($path)) {
if (!($current instanceof PsrContainerInterface)) {
$tail = implode($this->delimiter, $path);
throw new NotFoundException(sprintf('Key "%1$s" does not exist at path "%2$s"', $head, $tail));
}
$head = array_shift($path);
$current = $current->get($head);
}
return $current;
}
/**
* @inheritDoc
*/
public function has(string $key): bool
{
/**
* @psalm-suppress InvalidCatch
* The base interface does not extend Throwable, but in fact everything that is possible
* in theory to catch will be Throwable, and PSR-11 exceptions will implement this interface
*/
try {
$this->get($key);
return true;
} catch (NotFoundExceptionInterface $exception) {
return false;
}
}
}