ContainerBuilder::addDefinition()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 8
rs 9.4286
cc 2
eloc 4
nc 2
nop 1
1
<?php
2
/*
3
 * This file is part of the Borobudur-DependencyInjection package.
4
 *
5
 * (c) Hexacodelabs <http://hexacodelabs.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Borobudur\DependencyInjection;
12
13
use Borobudur\DependencyInjection\Exception\ImmutableParameterException;
14
use Borobudur\DependencyInjection\Exception\InvalidArgumentException;
15
use Borobudur\DependencyInjection\ParameterBag\ParameterBagInterface;
16
use Borobudur\DependencyInjection\Tag\TagInterface;
17
use Borobudur\DependencyInjection\Tag\TagProcessor;
18
19
/**
20
 * @author      Iqbal Maulana <[email protected]>
21
 * @created     8/8/15
22
 */
23
class ContainerBuilder extends Container
24
{
25
    /**
26
     * @var Definition[]
27
     */
28
    private $definitions = array();
29
30
    /**
31
     * @var array
32
     */
33
    private $lockAlias = array();
34
35
    /**
36
     * @var ArrayServiceLoader
37
     */
38
    private $arrayLoader;
39
40
    /**
41
     * @var TagProcessor
42
     */
43
    private $tagProcessor;
44
45
    /**
46
     * Constructor.
47
     *
48
     * @param ParameterBagInterface|null $parameterBag
49
     * @param Di|null                    $di
50
     */
51
    public function __construct(ParameterBagInterface $parameterBag = null, Di $di = null)
52
    {
53
        parent::__construct($parameterBag, $di);
54
        $this->arrayLoader = new ArrayServiceLoader($this);
55
        $this->tagProcessor = new TagProcessor($this);
56
    }
57
58
    /**
59
     * Clear and replace with sets of definitions.
60
     *
61
     * @param array $definitions
62
     *
63
     * @return static
64
     */
65
    public function replaceDefinition(array $definitions)
66
    {
67
        $this->definitions = array();
68
69
        return $this->addDefinition($definitions);
70
    }
71
72
    /**
73
     * Add sets of definition.
74
     *
75
     * @param array $definitions
76
     *
77
     * @return static
78
     */
79
    public function addDefinition(array $definitions)
80
    {
81
        foreach ($definitions as $id => $definition) {
82
            $this->setDefinition($id, $definition);
83
        }
84
85
        return $this;
86
    }
87
88
    /**
89
     * Set a service definition.
90
     *
91
     * @param string     $id
92
     * @param Definition $definition
93
     *
94
     * @return Definition
95
     */
96
    public function setDefinition($id, Definition $definition)
97
    {
98
        if ($this->isImmutable()) {
99
            throw new ImmutableParameterException('setDefinition');
100
        }
101
102
        $id = strtolower($id);
103
104
        $this->setAlias($id, $definition);
105
        $this->tagProcessor->process($id, $definition->getTags());
106
107
        return $this->definitions[$id] = $definition;
108
    }
109
110
    /**
111
     * Set service.
112
     *
113
     * @param string $id
114
     * @param mixed  $service
115
     */
116
    public function set($id, $service)
117
    {
118
        $id = strtolower($id);
119
120
        if (isset($this->definitions[$id])) {
121
            unset($this->definitions[$id]);
122
        }
123
124
        $this->removeAlias($id);
125
        parent::set($id, $service);
126
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131
    public function has($id)
132
    {
133
        if (false === parent::has($id)) {
134
            return $this->hasDefinition($id);
135
        }
136
137
        return true;
138
    }
139
140
    /**
141
     * Check if definition exists.
142
     *
143
     * @param string $id
144
     *
145
     * @return bool
146
     */
147
    public function hasDefinition($id)
148
    {
149
        return array_key_exists(strtolower($id), $this->definitions);
150
    }
151
152
    /**
153
     * Get definition.
154
     *
155
     * @param string $id
156
     *
157
     * @return Definition
158
     */
159
    public function getDefinition($id)
160
    {
161
        $id = strtolower($id);
162
163
        if (!array_key_exists($id, $this->definitions)) {
164
            throw new InvalidArgumentException(sprintf('Service definition "%s" is not defined.', $id));
165
        }
166
167
        return $this->definitions[$id];
168
    }
169
170
    /**
171
     * Get service.
172
     *
173
     * @param string $id
174
     * @param array  $args
175
     * @param int    $invalidBehavior
176
     *
177
     * @return mixed|object|null
178
     */
179
    public function get($id, array $args = array(), $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE)
180
    {
181
        if (null !== $service = parent::get($id, $args, self::NULL_ON_INVALID_REFERENCE)) {
182
            return $service;
183
        }
184
185
        $id = $this->getServiceId($id);
186
187
        try {
188
            $definition = $this->getDefinition($id);
189
        } catch (InvalidArgumentException $e) {
190
            if (self::NULL_ON_INVALID_REFERENCE === $invalidBehavior) {
191
                return null;
192
            }
193
194
            throw $e;
195
        }
196
197
        return $this->registerDefinition($id, $definition, $args);
198
    }
199
200
    /**
201
     * Register service and map class alias if service is object.
202
     *
203
     * @param string $id
204
     * @param mixed  $service
205
     * @param bool   $shared
206
     */
207
    public function register($id, $service, $shared = false)
208
    {
209
        if (true === $shared) {
210
            $this->share($id, $service);
211
        } else {
212
            $this->set($id, $service);
213
        }
214
215
        if (is_object($service)) {
216
            $this->setDefinition($id, new Definition(get_class($service)));
217
        }
218
    }
219
220
    /**
221
     * Create service.
222
     *
223
     * @param Definition $definition
224
     * @param array      $args
225
     *
226
     * @return InstanceManager
227
     */
228
    public function createService(Definition $definition, array $args = array())
229
    {
230
        $instance = $this->getDi()->createInstance(
231
            $definition->getClass(),
232
            array_merge($definition->getArgument()->all(), $args)
233
        );
234
235
        $instance->callMethods($definition->getMethodCall()->all());
236
        $instance->setProperties($definition->getProperty()->all());
237
238
        return $instance;
239
    }
240
241
    /**
242
     * Load services from array.
243
     *
244
     * @param array $services
245
     */
246
    public function load(array $services)
247
    {
248
        $this->arrayLoader->load($services);
249
    }
250
251
    /**
252
     * Add a tag.
253
     *
254
     * @param TagInterface $tag
255
     */
256
    public function addTag(TagInterface $tag)
257
    {
258
        $this->tagProcessor->add($tag);
259
    }
260
261
    /**
262
     * Register service definition.
263
     *
264
     * @param string     $id
265
     * @param Definition $definition
266
     * @param array      $args
267
     *
268
     * @return object
269
     */
270
    private function registerDefinition($id, Definition $definition, array $args = array())
271
    {
272
        $instance = $this->createService($definition, $args);
273
        $service = $instance->getOriginalInstance();
274
275
        if ($definition->isShared()) {
276
            $this->share($id, $service);
277
        } else {
278
            unset($this->lockAlias[$id]);
279
        }
280
281
        $this->assertImplementAbstract($definition, $service);
282
283
        return $service;
284
    }
285
286
    /**
287
     * Assert that service implement abstract.
288
     *
289
     * @param Definition $definition
290
     * @param mixed      $service
291
     */
292
    private function assertImplementAbstract(Definition $definition, $service)
293
    {
294
        if ($definition->isAbstract()
295
            && !in_array(ltrim($definition->getAbstract(), '\\'), class_implements($service))
296
        ) {
297
            throw new InvalidArgumentException(sprintf(
298
                'Class "%s" not implement "%s".',
299
                get_class($service),
300
                $definition->getAbstract()
301
            ));
302
        }
303
    }
304
305
    /**
306
     * Fix alias.
307
     *
308
     * @param string $id
309
     */
310
    private function removeAlias($id)
311
    {
312
        if (null !== ($alias = $this->getAliasFromId($id)) && !isset($this->lockAlias[$id])) {
313
            $index = array_search($id, $this->aliases[$alias]);
314
            unset($this->aliases[$alias][$index]);
315
            // re-index
316
            $this->aliases[$alias] = array_values($this->aliases[$alias]);
317
        }
318
319
        unset($this->lockAlias[$id]);
320
    }
321
322
    /**
323
     * Register abstract definition if possible.
324
     *
325
     * @param string     $id
326
     * @param Definition $definition
327
     */
328
    private function setAlias($id, Definition $definition)
329
    {
330
        if ($definition->isAbstract()) {
331
            $class = $definition->getAbstract();
332
        } else {
333
            $class = $definition->getClass();
334
        }
335
336
        $class = '\\' . ltrim($class, '\\');
337
338
        $this->aliases[$class][] = $id;
339
        $this->lockAlias[$id] = true;
340
    }
341
}
342