Completed
Push — master ( 0fa39d...2d8d21 )
by Dominik
14:39
created

Container::__clone()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 0
cp 0
crap 6
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 54
76
    /**
77
     * Add standard or shared service
78 54
     *
79
     * @param ServiceType $serviceType
80 54
     *            Service type
81 6
     * @param string $id
82
     *            Service identifier
83
     * @param string $source
84 54
     *            Service to create
85 54
     * @param array $arguments
86 54
     *            Service arguments to inject into constructor
87
     * @return ContainerInterface This object
88
     * @throws ContainerException
89 54
     * @see \Kocuj\Di\Container\ContainerInterface::add()
90
     */
91
    public function add(ServiceType $serviceType, string $id, string $source, array $arguments = []): ContainerInterface
92
    {
93
        // decorate service identifier
94
        $decoratedId = $this->serviceIdDecorator->decorate($id);
95
        // check if service does not exist already
96
        if (isset($this->definitions[$decoratedId])) {
97
            throw new ContainerException(sprintf('Service "%s" already exists', $decoratedId));
98
        }
99
        // set service definition
100
        $this->definitions[$decoratedId] = [
101
            'service' => $this->serviceFactory->create($this, $serviceType, $decoratedId, $source, $arguments),
102
            'type' => $serviceType,
103
            'clonedata' => [
104
                'id' => $id,
105
                'source' => $source,
106
                'arguments' => $arguments
107
            ]
108
        ];
109
        // exit
110
        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 42
     * @param string $source
137
     *            Service to create
138
     * @param array $arguments
139 42
     *            Service arguments to inject into constructor
140
     * @return ContainerInterface This object
141 42
     * @see \Kocuj\Di\Container\ContainerInterface::addShared() @codeCoverageIgnore
142 6
     */
143
    public function addShared(string $id, string $source, array $arguments = []): ContainerInterface
144
    {
145 36
        // 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
        if (! $this->has($decoratedId)) {
163
            throw new NotFoundException(sprintf('Service "%s" does not exist', $decoratedId));
164
        }
165
        // exit
166
        return $this->definitions[$decoratedId];
167
    }
168
169
    /**
170
     * Get service
171
     *
172 36
     * @param string $id
173
     *            Service identifier
174
     * @return object Service object
175 36
     * @throws NotFoundException
176
     * @see \Psr\Container\ContainerInterface::get()
177
     */
178
    public function get($id)
179
    {
180
        // exit
181
        return $this->getServiceDefinition($id)['service']->getService();
182
    }
183
184
    /**
185
     * Get service type
186 42
     *
187
     * @param string $id
188
     *            Service identifier
189 42
     * @return ServiceType Service type
190
     * @throws NotFoundException
191 42
     * @see \Kocuj\Di\Container\ContainerInterface::getType()
192
     */
193
    public function getType(string $id): ServiceType
194
    {
195
        // exit
196
        return $this->getServiceDefinition($id)['type'];
197
    }
198
199
    /**
200
     * Check if service exists
201
     *
202
     * @param string $id
203
     *            Service
204 30
     * @return bool Service exists (true) or not (false)
205
     * @see \Psr\Container\ContainerInterface::has()
206
     */
207 30
    public function has($id): bool
208 6
    {
209
        // decorate service identifier
210
        $decoratedId = $this->serviceIdDecorator->decorate($id);
211 24
        // exit
212 24
        return isset($this->definitions[$decoratedId]);
213 6
    }
214
215
    /**
216 18
     * Call service by method get*(), where "*" is service identifier written in camel case
217
     *
218 18
     * @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
    public function __call(string $method, array $arguments)
226
    {
227
        // disallow any arguments
228
        if (! empty($arguments)) {
229
            throw new ContainerException('Service must be get without arguments');
230
        }
231
        // check prefix
232
        $prefix = substr($method, 0, 3);
233
        if ($prefix !== 'get') {
234
            trigger_error(sprintf('Call to undefined method %s()', __CLASS__ . '::' . $method), E_USER_ERROR);
235
        }
236
        // get service
237
        $service = $this->get(substr($method, 3));
238
        // exit
239
        return $service;
240
    }
241
}
242