1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Hgraca\MicroDI; |
4
|
|
|
|
5
|
|
|
use Closure; |
6
|
|
|
use Hgraca\Helper\InstanceHelper; |
7
|
|
|
use Hgraca\MicroDI\DependencyResolver\DependencyResolverInterface; |
8
|
|
|
use Hgraca\MicroDI\Exception\CanNotInstantiateDependenciesException; |
9
|
|
|
use Hgraca\MicroDI\Port\ContainerInterface; |
10
|
|
|
use InvalidArgumentException; |
11
|
|
|
|
12
|
|
|
final class Builder implements BuilderInterface |
13
|
|
|
{ |
14
|
|
|
/** @var ContainerInterface */ |
15
|
|
|
private $container; |
16
|
|
|
|
17
|
|
|
/** @var string[] */ |
18
|
|
|
private $dependentStack = []; |
19
|
|
|
|
20
|
|
|
/** @var string[] */ |
21
|
|
|
private $argumentNameStack = []; |
22
|
|
|
|
23
|
|
|
/** @var DependencyResolverInterface */ |
24
|
|
|
private $dependencyResolver; |
25
|
|
|
|
26
|
26 |
|
public function __construct(ContainerInterface $container, DependencyResolverInterface $dependencyResolver) |
27
|
|
|
{ |
28
|
26 |
|
$this->container = $container; |
29
|
26 |
|
$this->dependencyResolver = $dependencyResolver; |
30
|
26 |
|
} |
31
|
|
|
|
32
|
5 |
|
public function getInstance(string $class, array $arguments = []) |
33
|
|
|
{ |
34
|
5 |
|
if (!$this->container->hasInstance($class)) { |
35
|
4 |
|
$instance = $this->buildInstance($class, $arguments); |
36
|
4 |
|
$this->container->addInstance($instance); |
37
|
|
|
} |
38
|
|
|
|
39
|
5 |
|
return $this->container->getInstance($class); |
40
|
|
|
} |
41
|
|
|
|
42
|
5 |
|
public function buildInstance(string $class, array $arguments = []) |
43
|
|
|
{ |
44
|
5 |
|
return InstanceHelper::createInstance( |
45
|
|
|
$class, |
46
|
5 |
|
$this->buildDependencies([$class, '__construct'], $arguments) |
47
|
|
|
); |
48
|
|
|
} |
49
|
|
|
|
50
|
3 |
|
public function buildFromFactory(string $factoryClass, array $arguments = []) |
51
|
|
|
{ |
52
|
3 |
|
if (!is_a($factoryClass, FactoryInterface::class, $allowString = true)) { |
53
|
1 |
|
throw new InvalidArgumentException( |
54
|
1 |
|
"The given factory class $factoryClass must implement " . FactoryInterface::class |
55
|
|
|
); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
/** @var FactoryInterface $factory */ |
59
|
2 |
|
$factory = $this->getInstance($factoryClass, $arguments); |
60
|
|
|
|
61
|
2 |
|
$context = $this->container->hasFactoryContext($factoryClass) |
62
|
1 |
|
? $this->container->getFactoryContext($factoryClass) |
63
|
2 |
|
: []; |
64
|
|
|
|
65
|
2 |
|
return $factory->create($context); |
66
|
|
|
} |
67
|
|
|
|
68
|
6 |
|
public function buildDependencies($callable, array $arguments = []): array |
69
|
|
|
{ |
70
|
6 |
|
if ($callable instanceof Closure) { |
71
|
1 |
|
throw new InvalidArgumentException("Closures support is not implemented. The \$callable must be an array or an object with an '__invoke' method."); |
72
|
5 |
|
} elseif (is_array($callable)) { |
73
|
4 |
|
$dependentClass = is_string($callable[0]) ? $callable[0] : get_class($callable[0]); |
74
|
4 |
|
$dependentMethod = $callable[1] ?? '__construct'; |
75
|
2 |
|
} elseif (is_object($callable)) { |
76
|
1 |
|
$dependentClass = get_class($callable); |
77
|
1 |
|
$dependentMethod = '__invoke'; |
78
|
|
|
} else { |
79
|
1 |
|
throw new InvalidArgumentException("The \$callable must be an array or an object with an '__invoke' method."); |
80
|
|
|
} |
81
|
|
|
|
82
|
4 |
|
$dependencies = $this->dependencyResolver->resolveDependencies($dependentClass, $dependentMethod); |
83
|
|
|
|
84
|
4 |
|
return $this->prepareDependencies($dependentClass, $dependencies, $arguments); |
85
|
|
|
} |
86
|
|
|
|
87
|
4 |
|
private function prepareDependencies(string $dependentClass, array $parameters, array $arguments): array |
88
|
|
|
{ |
89
|
4 |
|
$dependencies = []; |
90
|
|
|
|
91
|
4 |
|
foreach ($parameters as $parameter) { |
92
|
3 |
|
$dependencies[$parameter['name']] = $this->getParameterArgument($dependentClass, $parameter, $arguments); |
93
|
|
|
} |
94
|
|
|
|
95
|
4 |
|
return $dependencies; |
96
|
|
|
} |
97
|
|
|
|
98
|
3 |
|
private function getParameterArgument(string $dependentClass, array $parameter, array $arguments) |
99
|
|
|
{ |
100
|
3 |
|
$parameterName = $parameter['name']; |
101
|
|
|
switch (true) { |
102
|
3 |
|
case $this->isDependencyInGivenArguments($parameterName, $arguments): |
|
|
|
|
103
|
2 |
|
return $this->getDependencyFromArguments($parameterName, $arguments); |
104
|
3 |
|
case $this->isDependencyClassOrInterface($parameter): |
105
|
3 |
|
return $this->getDependencyRecursively($dependentClass, $parameter); |
106
|
3 |
|
case $this->isDependencyNameInContainer($parameterName): |
107
|
2 |
|
return $this->getDependencyFromParameterInContainer($parameterName); |
108
|
|
|
default: |
109
|
1 |
|
throw new CanNotInstantiateDependenciesException( |
110
|
1 |
|
"Could not get dependency for class '$dependentClass', parameter '$parameterName'." |
111
|
|
|
); |
112
|
|
|
} |
113
|
|
|
} |
114
|
|
|
|
115
|
3 |
|
private function isDependencyInGivenArguments(string $parameterName, array $arguments): bool |
116
|
|
|
{ |
117
|
3 |
|
return array_key_exists($parameterName, $arguments); |
118
|
|
|
} |
119
|
|
|
|
120
|
2 |
|
private function getDependencyFromArguments(string $parameterName, array $arguments) |
121
|
|
|
{ |
122
|
2 |
|
return $arguments[$parameterName]; |
123
|
|
|
} |
124
|
|
|
|
125
|
3 |
|
private function isDependencyClassOrInterface(array $parameter): bool |
126
|
|
|
{ |
127
|
3 |
|
return isset($parameter['class']) && |
128
|
3 |
|
(class_exists($parameter['class']) || interface_exists($parameter['class'])); |
129
|
|
|
} |
130
|
|
|
|
131
|
3 |
|
private function getDependencyRecursively( |
132
|
|
|
string $dependentClass, |
133
|
|
|
array $parameter |
134
|
|
|
) { |
135
|
3 |
|
$this->pushDependentClass($dependentClass); |
136
|
3 |
|
$this->pushArgumentName($parameter['name']); |
137
|
3 |
|
$dependency = $this->getInstance($parameter['class']); |
138
|
3 |
|
$this->popDependentClass(); |
139
|
3 |
|
$this->popArgumentName(); |
140
|
|
|
|
141
|
3 |
|
return $dependency; |
142
|
|
|
} |
143
|
|
|
|
144
|
3 |
|
private function isDependencyNameInContainer(string $parameterName): bool |
145
|
|
|
{ |
146
|
3 |
|
return $this->container->hasArgument($parameterName); |
147
|
|
|
} |
148
|
|
|
|
149
|
2 |
|
private function getDependencyFromParameterInContainer(string $parameterName) |
150
|
|
|
{ |
151
|
2 |
|
return $this->container->getArgument($parameterName); |
152
|
|
|
} |
153
|
|
|
|
154
|
3 |
|
private function pushDependentClass(string $dependentClass) |
155
|
|
|
{ |
156
|
3 |
|
$this->dependentStack[] = $dependentClass; |
157
|
3 |
|
} |
158
|
|
|
|
159
|
3 |
|
private function popDependentClass(): string |
160
|
|
|
{ |
161
|
3 |
|
return array_pop($this->dependentStack); |
162
|
|
|
} |
163
|
|
|
|
164
|
3 |
|
private function pushArgumentName(string $argumentName) |
165
|
|
|
{ |
166
|
3 |
|
$this->argumentNameStack[] = $argumentName; |
167
|
3 |
|
} |
168
|
|
|
|
169
|
3 |
|
private function popArgumentName(): string |
170
|
|
|
{ |
171
|
3 |
|
return array_pop($this->argumentNameStack); |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
|
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.