Passed
Branch master (4d9c37)
by Dominik
02:10
created

Container   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 219
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 219
ccs 35
cts 35
cp 1
rs 10
c 0
b 0
f 0
wmc 15

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A addStandard() 0 4 1
A __clone() 0 8 2
A addShared() 0 4 1
A has() 0 6 1
A getType() 0 4 1
A getServiceDefinition() 0 10 2
A add() 0 20 2
A __call() 0 15 3
A get() 0 4 1
1
<?php
2
3
/**
4
 * Container.php
5
 *
6
 * @author Dominik Kocuj
7
 * @license https://opensource.org/licenses/MIT The MIT License
8
 * @copyright Copyright (c) 2017 kocuj.pl
9
 * @package kocuj_di
10
 */
11
namespace Kocuj\Di\Container;
12
13
use Kocuj\Di\Service\ServiceFactory;
14
use Kocuj\Di\Service\ServiceFactoryInterface;
15
use Kocuj\Di\Service\ServiceType;
16
use Kocuj\Di\ServiceIdDecorator\ServiceIdDecoratorInterface;
17
18
/**
19
 * Dependency injection container for services
20
 */
21
class Container implements ContainerInterface
22
{
23
24
    /**
25
     * Service identifier decorator
26
     *
27
     * @var ServiceIdDecoratorInterface
28
     */
29
    private $serviceIdDecorator;
30
31
    /**
32
     * Service factory
33
     *
34
     * @var ServiceFactoryInterface
35
     */
36
    private $serviceFactory;
37
38
    /**
39
     * Definitions
40
     *
41
     * @var array
42
     */
43
    private $definitions = [];
44
45
    /**
46
     * Constructor
47
     *
48
     * @param ServiceIdDecoratorInterface $serviceIdDecorator
49
     *            Service identifier decorator
50
     * @param ServiceFactoryInterface $serviceFactory
51
     *            Service factory
52
     */
53 60
    public function __construct(ServiceIdDecoratorInterface $serviceIdDecorator, ServiceFactoryInterface $serviceFactory)
54
    {
55
        // remember arguments
56 60
        $this->serviceIdDecorator = $serviceIdDecorator;
57 60
        $this->serviceFactory = $serviceFactory;
58 60
    }
59
60
    /**
61
     * Cloning container
62
     *
63
     * @return void @codeCoverageIgnore
64
     */
65
    public function __clone()
66
    {
67
        // copy services definitions
68
        $oldDefinitions = $this->definitions;
69
        // recreate services
70
        $this->definitions = [];
71
        foreach ($oldDefinitions as $definition) {
72
            $this->add($definition['type'], $definition['clonedata']['id'], $definition['clonedata']['source'], $definition['clonedata']['arguments']);
73
        }
74
    }
75
76
    /**
77
     * Add standard or shared service
78
     *
79
     * @param ServiceType $serviceType
80
     *            Service type
81
     * @param string $id
82
     *            Service identifier
83
     * @param string $source
84
     *            Service to create
85
     * @param array $arguments
86
     *            Service arguments to inject into constructor
87
     * @return ContainerInterface This object
88
     * @throws ContainerException
89
     * @see \Kocuj\Di\Container\ContainerInterface::add()
90
     */
91 54
    public function add(ServiceType $serviceType, string $id, string $source, array $arguments = []): ContainerInterface
92
    {
93
        // decorate service identifier
94 54
        $decoratedId = $this->serviceIdDecorator->decorate($id);
95
        // check if service does not exist already
96 54
        if (isset($this->definitions[$decoratedId])) {
97 6
            throw new ContainerException(sprintf('Service "%s" already exists', $decoratedId));
98
        }
99
        // set service definition
100 54
        $this->definitions[$decoratedId] = [
101 54
            'service' => $this->serviceFactory->create($this, $serviceType, $decoratedId, $source, $arguments),
102 54
            'type' => $serviceType,
103
            'clonedata' => [
104 54
                'id' => $id,
105 54
                'source' => $source,
106 54
                'arguments' => $arguments
107
            ]
108
        ];
109
        // exit
110 54
        return $this;
111
    }
112
113
    /**
114
     * Add standard service
115
     *
116
     * @param string $id
117
     *            Service identifier
118
     * @param string $source
119
     *            Service to create
120
     * @param array $arguments
121
     *            Service arguments to inject into constructor
122
     * @return ContainerInterface This object
123
     * @see \Kocuj\Di\Container\ContainerInterface::addStandard() @codeCoverageIgnore
124
     */
125
    public function addStandard(string $id, string $source, array $arguments = []): ContainerInterface
126
    {
127
        // exit
128
        return $this->add(new ServiceType(ServiceType::STANDARD), $id, $source, $arguments);
129
    }
130
131
    /**
132
     * Add shared service
133
     *
134
     * @param string $id
135
     *            Service identifier
136
     * @param string $source
137
     *            Service to create
138
     * @param array $arguments
139
     *            Service arguments to inject into constructor
140
     * @return ContainerInterface This object
141
     * @see \Kocuj\Di\Container\ContainerInterface::addShared() @codeCoverageIgnore
142
     */
143
    public function addShared(string $id, string $source, array $arguments = []): ContainerInterface
144
    {
145
        // exit
146
        return $this->add(new ServiceType(ServiceType::SHARED), $id, $source, $arguments);
147
    }
148
149
    /**
150
     * Get service definition
151
     *
152
     * @param string $id
153
     *            Service identifier
154
     * @return array Service definition
155
     * @throws NotFoundException
156
     */
157 42
    private function getServiceDefinition(string $id)
158
    {
159
        // decorate service identifier
160 42
        $decoratedId = $this->serviceIdDecorator->decorate($id);
161
        // check if service exists
162 42
        if (! $this->has($decoratedId)) {
163 6
            throw new NotFoundException(sprintf('Service "%s" does not exist', $decoratedId));
164
        }
165
        // exit
166 36
        return $this->definitions[$decoratedId];
167
    }
168
169
    /**
170
     * Get service
171
     *
172
     * @param string $id
173
     *            Service identifier
174
     * @return object Service object
175
     * @throws NotFoundException
176
     * @see \Psr\Container\ContainerInterface::get()
177
     */
178 42
    public function get($id)
179
    {
180
        // exit
181 42
        return $this->getServiceDefinition($id)['service']->getService();
182
    }
183
184
    /**
185
     * Get service type
186
     *
187
     * @param string $id
188
     *            Service identifier
189
     * @return ServiceType Service type
190
     * @throws NotFoundException
191
     * @see \Kocuj\Di\Container\ContainerInterface::getType()
192
     */
193 36
    public function getType(string $id): ServiceType
194
    {
195
        // exit
196 36
        return $this->getServiceDefinition($id)['type'];
197
    }
198
199
    /**
200
     * Check if service exists
201
     *
202
     * @param string $id
203
     *            Service
204
     * @return bool Service exists (true) or not (false)
205
     * @see \Psr\Container\ContainerInterface::has()
206
     */
207 42
    public function has($id): bool
208
    {
209
        // decorate service identifier
210 42
        $decoratedId = $this->serviceIdDecorator->decorate($id);
211
        // exit
212 42
        return isset($this->definitions[$decoratedId]);
213
    }
214
215
    /**
216
     * Call service by method get*(), where "*" is service identifier written in camel case
217
     *
218
     * @param string $method
219
     *            Method to call
220
     * @param array $arguments
221
     *            Arguments for called method
222
     * @return object Service object
223
     * @throws ContainerException
224
     */
225 30
    public function __call(string $method, array $arguments)
226
    {
227
        // disallow any arguments
228 30
        if (! empty($arguments)) {
229 6
            throw new ContainerException('Service must be get without arguments');
230
        }
231
        // check prefix
232 24
        $prefix = substr($method, 0, 3);
233 24
        if ($prefix !== 'get') {
234 6
            trigger_error(sprintf('Call to undefined method %s()', __CLASS__ . '::' . $method), E_USER_ERROR);
235
        }
236
        // get service
237 18
        $service = $this->get(substr($method, 3));
238
        // exit
239 18
        return $service;
240
    }
241
}
242