Completed
Pull Request — master (#10)
by Alice
02:26
created

ServiceContainer::cleanServiceName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
namespace Wonderland\Container;
4
5
use Wonderland\Container\Service\InstanceDefinitionInterface;
6
use Wonderland\Container\Service\ServiceDefinitionInterface;
7
use Wonderland\Container\Exception\DuplicatedServiceException;
8
use Psr\Container\ContainerInterface;
9
10
/**
11
 * Class ServiceContainer
12
 * @package Wonderland\Container\Container
13
 * @author Alice Praud <[email protected]>
14
 */
15
class ServiceContainer implements ContainerInterface
16
{
17
	private const SERVICE_PREFIX = '@';
18
19
	/** @var ServiceDefinitionInterface[] */
20
	private $services;
21
22
	/** @var array */
23
	private $serviceInstances;
24
25
	/**
26
	 * ServiceContainer constructor.
27
	 */
28 7
	public function __construct()
29
	{
30 7
		$this->services = [];
31 7
		$this->serviceInstances = [];
32 7
	}
33
34
	/**
35
	 * @param ServiceDefinitionInterface $serviceDefinition
36
	 * @return ServiceContainer
37
	 * @throws DuplicatedServiceException
38
	 */
39 8
	public function addService(ServiceDefinitionInterface $serviceDefinition)
40
	{
41 8
		if (true === $this->has($serviceDefinition->getServiceName())) {
42 1
			throw new DuplicatedServiceException(
43 1
				'The service "' . $serviceDefinition->getServiceName() . '" is already registered in the container'
44
			);
45
		}
46
47 8
		$this->services[$serviceDefinition->getServiceName()] = $serviceDefinition;
48
49 8
		return $this;
50
	}
51
52
	/**
53
	 * @param ServiceDefinitionInterface[] $definitionList
54
	 * @return ServiceContainer
55
	 * @throws DuplicatedServiceException
56
	 */
57 1
	public function loadServices(array $definitionList)
58
	{
59 1
		foreach ($definitionList as $definition) {
60 1
			$this->addService($definition);
61
		}
62
63 1
		return $this;
64
	}
65
66
	/**
67
	 * @param InstanceDefinitionInterface $definition
68
	 * @return ServiceContainer
69
	 * @throws DuplicatedServiceException
70
	 */
71 4
	public function addServiceInstance(InstanceDefinitionInterface $definition)
72
	{
73 4
		if (true === $this->has($definition->getServiceName())) {
74 1
			throw new DuplicatedServiceException(
75 1
				'The service "' . $definition->getServiceName() . '" is already registered in the container'
76
			);
77
		}
78
79 3
		$this->serviceInstances[$definition->getServiceName()] = $definition->getInstance();
80
81 3
		return $this;
82
	}
83
84
	/**
85
	 * @param string $index
86
	 * @param bool $new
87
	 * @return mixed|null
88
	 */
89 1
	public function get($index, $new = false)
90
	{
91 1
		$index = $this->cleanServiceName($index);
92
93
		// if service is shared true and already exists we return it
94 1
		if (isset($this->serviceInstances[$index]) && false === $new) {
95 1
			return $this->serviceInstances[$index];
96
		}
97
98
		// is service not defined return null
99 1
		if (!isset($this->services[$index])) {
100 1
			return null;
101
		}
102
103
		// we create a new instance
104 1
		$instance = $this->create($this->services[$index]);
105
106
		// if shared true we set it in the current instances
107 1
		if (false === $new) {
108 1
			$this->serviceInstances[$index] = $instance;
109
		}
110
111 1
		return $instance;
112
	}
113
114
	/**
115
	 * Returns true if the container can return an entry for the given identifier.
116
	 * Returns false otherwise.
117
	 *
118
	 * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
119
	 * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
120
	 *
121
	 * @param string $index Identifier of the entry to look for.
122
	 *
123
	 * @return bool
124
	 */
125 7
	public function has($index)
126
	{
127 7
		$index = $this->cleanServiceName($index);
128
129
		// if service is shared true and already exists we return it
130 7
		if (isset($this->serviceInstances[$index])) {
131 2
			return true;
132
		}
133
134
		// is service not defined return null
135 7
		if (isset($this->services[$index])) {
136 3
			return true;
137
		}
138
139 7
		return false;
140
	}
141
142
	/**
143
	 * @param ServiceDefinitionInterface $serviceDefinition
144
	 * @return mixed
145
	 */
146 1
	private function create(ServiceDefinitionInterface $serviceDefinition)
147
	{
148
		// we get the class name in a var for the new later
149 1
		$newClass = $serviceDefinition->getClass();
150
151
		// we get the construct args
152 1
		$args = $this->checkArgsServices($serviceDefinition->getConstructArgs());
153 1
		$instance = new $newClass(...$args);
154
155
		// we call the injection methods after we instance the object
156 1
		$calls = $this->checkCallsServices($serviceDefinition->getCalls());
157 1
		foreach ($calls as $call => $params) {
158 1
			call_user_func_array([$instance, $call], $params);
159
		}
160
161
		// we create the new instance
162 1
		return $instance;
163
	}
164
165
	/**
166
	 * @param array $args
167
	 * @return array
168
	 */
169 1
	private function checkArgsServices(array $args)
170
	{
171
		// we check if any of the construct args are services themself and we create them
172 1
		foreach ($args as $k => $arg) {
173 1
			if (false === $this->isServiceName($arg)) {
174 1
				continue;
175
			}
176
177 1
			if (true === $this->has($arg)) {
178 1
				$args[$k] = $this->get($arg);
179
			}
180
		}
181
182 1
		return $args;
183
	}
184
185
	/**
186
	 * @param array $calls
187
	 * @return array
188
	 */
189 1
	private function checkCallsServices(array $calls)
190
	{
191
		// we check if any of the calls args are services themself and we create them
192 1
		foreach ($calls as $i => $callArgs) {
193 1
			foreach ($callArgs as $k => $arg) {
194 1
				if (false === $this->isServiceName($arg)) {
195 1
					continue;
196
				}
197
198 1
				if (true === $this->has($arg)) {
199 1
					$calls[$i][$k] = $this->get($arg);
200
				}
201
			}
202
		}
203
204 1
		return $calls;
205
	}
206
207
	/**
208
	 * @param string $str
209
	 * @return bool
210
	 */
211 1
	private function isServiceName(string $str)
212
	{
213 1
		return self::SERVICE_PREFIX === substr($str, 0, 1);
214
	}
215
216
	/**
217
	 * @param string $str
218
	 * @return string
219
	 */
220 7
	private function cleanServiceName(string $str)
221
	{
222 7
		return trim($str, self::SERVICE_PREFIX);
223
	}
224
225
}
226