Completed
Push — master ( e5f1f2...99373d )
by Jonathan
02:32
created

AbstractContainer::getNames()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
declare(strict_types=1);
3
/**
4
 * Caridea
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
7
 * use this file except in compliance with the License. You may obtain a copy of
8
 * the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
 * License for the specific language governing permissions and limitations under
16
 * the License.
17
 *
18
 * @copyright 2015-2016 LibreWorks contributors
19
 * @license   http://opensource.org/licenses/Apache-2.0 Apache 2.0 License
20
 */
21
namespace Caridea\Container;
22
23
/**
24
 * Abstract dependency injection container.
25
 *
26
 * @copyright 2015-2016 LibreWorks contributors
27
 * @license   http://opensource.org/licenses/Apache-2.0 Apache 2.0 License
28
 */
29
abstract class AbstractContainer implements Container
30
{
31
    /**
32
     * @var Container The parent container
33
     */
34
    protected $parent;
35
    /**
36
     * @var string[] with string keys
37
     */
38
    protected $types = [];
39
    /**
40
     * @var string[] the list of PHP native types
41
     */
42
    protected static $primitives = ['array', 'bool', 'float', 'int', 'resource', 'string'];
43
    
44
    /**
45
     * Creates a new AbstractContainer.
46
     *
47
     * @param string[] $types with string keys
48
     * @param \Caridea\Container\Container $parent The parent container
49
     */
50
    protected function __construct(array $types, Container $parent = null)
51
    {
52
        $this->types = $types;
53
        $this->parent = $parent;
54
    }
55
    
56
    /**
57
     * Whether this container or its parent contains a component with the given name.
58
     *
59
     * @param string $name The component name
60
     * @return bool
61
     */
62
    public function contains(string $name): bool
63
    {
64
        return isset($this->types[$name]) ||
65
            ($this->parent ? $this->parent->contains($name) : false);
66
    }
67
    
68
    /**
69
     * Whether this container or its parent contains a component with the given type.
70
     *
71
     * @param string $type The name of a class or one of PHP's language types
72
     *     (i.e. bool, int, float, string, array, resource)
73
     * @return bool
74
     */
75
    public function containsType(string $type): bool
76
    {
77
        if ($type === null) {
78
            return false;
79
        }
80
        $isObject = !in_array($type, self::$primitives, true);
81
        foreach ($this->types as $ctype) {
82
            if ($type === $ctype || ($isObject && is_a($ctype, $type, true))) {
83
                return true;
84
            }
85
        }
86
        return $this->parent ? $this->parent->containsType($type) : false;
87
    }
88
    
89
    /**
90
     * Gets a component by name.
91
     *
92
     * If this container doesn't have a value for that name, it will delegate to
93
     * its parent.
94
     *
95
     * @param string $name The component name
96
     * @return mixed The component or null if the name isn't registered
97
     */
98
    public function get(string $name)
99
    {
100
        return isset($this->types[$name]) ? $this->doGet($name) :
101
            ($this->parent ? $this->parent->get($name) : null);
102
    }
103
104
    /**
105
     * Gets the components in the contanier for the given type.
106
     *
107
     * The parent container is called first, then any values of this container
108
     * are appended to the array. Values in this container supercede any with
109
     * duplicate names in the parent.
110
     *
111
     * @param string $type The name of a class or one of PHP's language types
112
     *     (i.e. bool, int, float, string, array, resource)
113
     * @return array keys are component names, values are components themselves
114
     */
115
    public function getByType(string $type): array
116
    {
117
        $components = $this->parent ? $this->parent->getByType($type) : [];
118
        $isObject = !in_array($type, self::$primitives, true);
119
        foreach ($this->types as $name => $ctype) {
120
            if ($type === $ctype || ($isObject && is_a($ctype, $type, true))) {
121
                $components[$name] = $this->doGet($name);
122
            }
123
        }
124
        return $components;
125
    }
126
127
    /**
128
     * Gets the first compenent found by type.
129
     *
130
     * If this container doesn't have a value of the type, it will delegate to
131
     * its parent.
132
     *
133
     * @param string $type The name of a class or one of PHP's language types
134
     *     (i.e. bool, int, float, string, array, resource)
135
     * @return mixed The component or null if one isn't registered
136
     */
137
    public function getFirst(string $type)
138
    {
139
        $isObject = !in_array($type, self::$primitives, true);
140
        foreach ($this->types as $name => $ctype) {
141
            if ($type === $ctype || ($isObject && is_a($ctype, $type, true))) {
142
                return $this->doGet($name);
143
            }
144
        }
145
        return $this->parent ? $this->parent->getFirst($type) : null;
146
    }
147
148
    /**
149
     * Retrieves the value
150
     *
151
     * @param string $name The value name
152
     */
153
    abstract protected function doGet(string $name);
154
    
155
    /**
156
     * Gets all registered component names (excluding any in the parent container).
157
     *
158
     * @return array of strings
159
     */
160
    public function getNames(): array
161
    {
162
        return array_keys($this->types);
163
    }
164
165
    /**
166
     * Gets the parent container.
167
     *
168
     * @return Container
169
     */
170
    public function getParent()
171
    {
172
        return $this->parent;
173
    }
174
    
175
    /**
176
     * Gets the type of component with the given name.
177
     *
178
     * If this container doesn't have a value for that name, it will delegate to
179
     * its parent.
180
     *
181
     * @param string $name The component name
182
     * @return string The component type, either a class name or one of PHP's language types
183
     *     (i.e. bool, int, float, string, array, resource)
184
     */
185
    public function getType(string $name)
186
    {
187
        return isset($this->types[$name]) ? $this->types[$name] :
188
            ($this->parent ? $this->parent->getType($name) : null);
189
    }
190
191
    /**
192
     * Gets a component by name and ensures its type.
193
     *
194
     * If this container doesn't have a value for that name, it will delegate to
195
     * its parent.
196
     * 
197
     * If the value isn't an instance of the type provided, an exception is
198
     * thrown, including when the value is `null`.
199
     *
200
     * @param string $name The component name
201
     * @param string $type The expected type
202
     * @return mixed The type-checked component
203
     * @throws \UnexpectedValueException if the `$type` doesn't match the value
204
     */
205
    public function named(string $name, string $type)
206
    {
207
        if (isset($this->types[$name])) {
208
            $ctype = $this->types[$name];
209
            $isObject = !in_array($type, self::$primitives, true);
210
            if ($type !== $ctype && (!$isObject || !is_a($ctype, $type, true))) {
211
                throw new \UnexpectedValueException("A $type was requested, but a $ctype was found");
212
            }
213
            return $this->doGet($name);
214
        } elseif ($this->parent !== null) {
215
            return $this->parent->named($name, $type);
216
        }
217
        throw new \UnexpectedValueException("A $type was requested, but null was found");
218
    }
219
}
220