forked from Dhii/containers
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPathContainer.php
More file actions
143 lines (127 loc) · 3.7 KB
/
PathContainer.php
File metadata and controls
143 lines (127 loc) · 3.7 KB
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<?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.
*
* @since [*next-version*]
* @see SegmentingContainer For an implementation that achieves the opposite effect.
*/
class PathContainer implements ContainerInterface
{
/**
* @since [*next-version*]
*
* @var PsrContainerInterface
*/
protected $inner;
/**
* @since [*next-version*]
*
* @var non-empty-string
*/
protected $delimiter;
/**
* Constructor.
*
* @since [*next-version*]
*
* @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
*
* @since [*next-version*]
*/
public function get($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
*
* @since [*next-version*]
*/
public function has($key)
{
/**
* @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;
}
}
}