Completed
Pull Request — master (#67)
by Eric
03:10
created

Container::hydrate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Jarvis\Skill\DependencyInjection;
6
7
/**
8
 * Minimalist dependency injection container
9
 *
10
 * @author Eric Chau <[email protected]>
11
 */
12
class Container implements \ArrayAccess
13
{
14
    protected $locked = [];
15
    private $aliasOf = [];
16
    private $factories;
17
    private $hasAliases = [];
18
    private $raw = [];
19
    private $values = [];
20
21
    public function __construct()
22
    {
23
        $this->factories = new \SplObjectStorage();
24
    }
25
26
    /**
27
     * Checks if a parameter or an object is defined.
28
     *
29
     * @param  string $id the parameter/object identifier to check
30
     * @return boolean
31
     */
32
    public function offsetExists($id): bool
33
    {
34
        return in_array($id, $this->keys());
35
    }
36
37
    /**
38
     * Gets a parameter or an object.
39
     *
40
     * @param  string $id the parameter/object identifier
41
     * @return mixed The requested value
42
     * @throws \InvalidArgumentException if provided identifier is not defined
43
     */
44
    public function offsetGet($id)
45
    {
46
        $id = $this->resolveIdentifier($id);
47
        if (
48
            isset($this->raw[$id])
49
            || !is_object($this->values[$id])
50
            || !method_exists($this->values[$id], '__invoke')
51
        ) {
52
            return $this->values[$id];
53
        }
54
55
        if (isset($this->factories[$this->values[$id]])) {
56
            return $this->values[$id]($this);
57
        }
58
59
        $this->raw[$id] = $this->values[$id];
60
61
        return $this->values[$id] = $this->raw[$id]($this);
62
    }
63
64
    /**
65
     * Sets a parameter or an object.
66
     *
67
     * Note that you cannot override locked value, you have to call ::offsetUnset first.
68
     *
69
     * @param  string $id the identifier for parameter or object
70
     * @param  mixed  $v  the value of the parameter or an object
71
     * @throws \RuntimeException prevents override of locked value
72
     * @throws \InvalidArgumentException prevents value's identifier to be equal to an existing alias
73
     */
74
    public function offsetSet($id, $v): void
75
    {
76
        if (isset($this->locked[$id])) {
77
            throw new \RuntimeException(sprintf('Cannot override locked value "%s".', $id));
78
        }
79
80
        if (isset($this->aliasOf[$id])) {
81
            throw new \InvalidArgumentException("Value's identifier cannot be equal to existing alias.");
82
        }
83
84
        $this->values[$id] = $v;
85
    }
86
87
    /**
88
     * Unsets a parameter or an object. It can also unset an alias.
89
     *
90
     * Note that if you unset a value it will also unset all its aliases.
91
     *
92
     * @param  string $id the identifier of the object/parameter to unset
93
     */
94
    public function offsetUnset($id): void
95
    {
96
        if (isset($this->values[$id])) {
97
            if (is_object($this->values[$id])) {
98
                unset($this->factories[$this->values[$id]]);
99
            }
100
101
            if (isset($this->hasAliases[$id])) {
102
                foreach ($this->hasAliases[$id] as $alias) {
103
                    unset($this->aliasOf[$alias]);
104
                }
105
            }
106
107
            unset($this->values[$id], $this->raw[$id], $this->hasAliases[$id], $this->locked[$id]);
108
        } else {
109
            unset($this->aliasOf[$id]);
110
        }
111
    }
112
113
    /**
114
     * Adds alias to service's identifier.
115
     *
116
     * @param  string $alias the alias to identifier
117
     * @param  string $id    the identifier to alias
118
     * @return self
119
     * @throws InvalidArgumentException if provided identifier is undefined or if alias is
120
     *                                  equals to identifier
121
     */
122
    public function alias(string $alias, string $id): void
123
    {
124
        if (!$this->offsetExists($id)) {
125
            throw new \InvalidArgumentException(sprintf('Cannot create alias for undefined value "%s".', $id));
126
        }
127
128
        if ($alias === $id || array_key_exists($alias, $this->values)) {
129
            throw new \InvalidArgumentException('Alias cannot be equals to value identifier.');
130
        }
131
132
        $this->aliasOf[$alias] = $id;
133
        $this->hasAliases[$id] = $this->hasAliases[$id] ?? [];
134
        $this->hasAliases[$id][] = $alias;
135
    }
136
137
    /**
138
     * Retrieves parameter and/or object by identifier.
139
     * this method also support wildcard character (*) in identifier.
140
     *
141
     * @param  string $id this identifier can contain one or many wildcard character (*)
142
     * @return array an array of values that mached with provided identifier pattern
143
     */
144
    public function find(string $id): array
145
    {
146
        $values = [];
147
        $pattern = str_replace(['.', '*'], ['\.', '[\w\-\.]*'], $id);
148
        foreach ($this->keys() as $id) {
149
            if (1 === preg_match(sprintf('/^%s$/', $pattern), $id)) {
150
                $values[] = $this->offsetGet($id);
151
            }
152
        }
153
154
        return $values;
155
    }
156
157
    /**
158
     * Returns all defined identifiers and aliases.
159
     *
160
     * @return array
161
     */
162
    public function keys(): array
163
    {
164
        return array_merge(array_keys($this->values), array_keys($this->aliasOf));
165
    }
166
167
    /**
168
     * Add provided service as factory.
169
     *
170
     * @param  string $id      the factory identifier
171
     * @param  mixed  $factory the factory object
172
     * @return self
173
     * @throws InvalidArgumentException if provided factory is not a Closure or not an invokable object
174
     */
175
    public function factory(string $id, $factory): void
176
    {
177
        if (!is_object($factory) || !method_exists($factory, '__invoke')) {
178
            throw new \InvalidArgumentException('Service factory must be a closure or an invokable object.');
179
        }
180
181
        $this->offsetSet($id, $factory);
182
        $this->factories->attach($factory);
183
    }
184
185
    /**
186
     * Locks an object or a parameter so you can not override it until unset() is called.
187
     *
188
     * @param  string|array $ids the identifier(s) to lock
189
     * @return self
190
     */
191
    public function lock($ids): void
192
    {
193
        foreach ((array) $ids as $id) {
194
            $this->locked[$this->resolveIdentifier($id)] = true;
195
        }
196
    }
197
198
    /**
199
     * @param  ContainerProviderInterface $provider
200
     * @return self
201
     */
202
    public function hydrate(ContainerProviderInterface $provider): Container
203
    {
204
        $provider->hydrate($this);
0 ignored issues
show
Compatibility introduced by
$this of type object<Jarvis\Skill\Depe...ncyInjection\Container> is not a sub-type of object<Jarvis\Jarvis>. It seems like you assume a child class of the class Jarvis\Skill\DependencyInjection\Container to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
205
206
        return $this;
207
    }
208
209
    /**
210
     * Returns associated identifier if provided argument is an alias.
211
     *
212
     * @param  string $id the identifier to convert if needed
213
     * @return string
214
     * @throws \InvalidArgumentException if provided identifier is not defined
215
     */
216
    private function resolveIdentifier(string $id): string
217
    {
218
        if (!$this->offsetExists($id)) {
219
            throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
220
        }
221
222
        return $this->aliasOf[$id] ?? $id;
223
    }
224
}
225