Completed
Pull Request — master (#67)
by Eric
01:58
created

Container::find()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
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
Documentation introduced by
$this is of type this<Jarvis\Skill\DependencyInjection\Container>, but the function expects a object<Jarvis\Jarvis>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
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