1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Ray\Compiler; |
6
|
|
|
|
7
|
|
|
use LogicException; |
8
|
|
|
use PhpParser\Node; |
9
|
|
|
use PhpParser\Node\Expr; |
10
|
|
|
use PhpParser\Node\Scalar; |
11
|
|
|
use Ray\Compiler\Exception\NotCompiled; |
12
|
|
|
use Ray\Di\Argument; |
13
|
|
|
use Ray\Di\Exception\Unbound; |
14
|
|
|
use Ray\Di\InjectorInterface; |
15
|
|
|
use Ray\Di\SetterMethod; |
16
|
|
|
use ReflectionClass; |
17
|
|
|
|
18
|
|
|
final class NodeFactory |
19
|
|
|
{ |
20
|
|
|
/** @var InjectorInterface|null */ |
21
|
|
|
private $injector; |
22
|
|
|
|
23
|
|
|
/** @var Normalizer */ |
24
|
|
|
private $normalizer; |
25
|
|
|
|
26
|
|
|
/** @var FactoryCode */ |
27
|
|
|
private $factoryCompiler; |
28
|
|
|
|
29
|
|
|
/** @var PrivateProperty */ |
30
|
|
|
private $privateProperty; |
31
|
|
|
|
32
|
|
|
public function __construct( |
33
|
|
|
Normalizer $normalizer, |
34
|
|
|
FactoryCode $factoryCompiler, |
35
|
|
|
?InjectorInterface $injector = null |
36
|
|
|
) { |
37
|
|
|
$this->injector = $injector; |
38
|
|
|
$this->normalizer = $normalizer; |
39
|
|
|
$this->factoryCompiler = $factoryCompiler; |
40
|
|
|
$this->privateProperty = new PrivateProperty(); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Return on-demand dependency pull code for not compiled |
45
|
|
|
* |
46
|
|
|
* @return Expr|Expr\FuncCall |
47
|
|
|
*/ |
48
|
|
|
public function getNode(Argument $argument): Expr |
49
|
|
|
{ |
50
|
|
|
$dependencyIndex = (string) $argument; |
51
|
|
|
if (! $this->injector instanceof ScriptInjector) { |
52
|
|
|
return $this->getDefault($argument); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
try { |
56
|
|
|
$isSingleton = $this->injector->isSingleton($dependencyIndex); |
57
|
|
|
} catch (NotCompiled $e) { |
58
|
|
|
return $this->getDefault($argument); |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
$func = $isSingleton ? 'singleton' : 'prototype'; |
62
|
|
|
$args = $this->getInjectionProviderParams($argument); |
63
|
|
|
|
64
|
|
|
/** @var array<Node\Arg> $args */ |
65
|
|
|
return new Expr\FuncCall(new Expr\Variable($func), $args); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @param SetterMethod[] $setterMethods |
70
|
|
|
* |
71
|
|
|
* @return Expr\MethodCall[] |
72
|
|
|
*/ |
73
|
|
|
public function getSetterInjection(Expr\Variable $instance, array $setterMethods): array |
74
|
|
|
{ |
75
|
|
|
$setters = []; |
76
|
|
|
foreach ($setterMethods as $setterMethod) { |
77
|
|
|
$isOptional = ($this->privateProperty)($setterMethod, 'isOptional'); |
78
|
|
|
$method = ($this->privateProperty)($setterMethod, 'method'); |
79
|
|
|
$argumentsObject = ($this->privateProperty)($setterMethod, 'arguments'); |
80
|
|
|
$arguments = ($this->privateProperty)($argumentsObject, 'arguments'); |
81
|
|
|
/** @var array<Node\Arg> $args */ |
82
|
|
|
$args = $this->getSetterParams($arguments, $isOptional); |
83
|
|
|
if (! $args) { |
|
|
|
|
84
|
|
|
continue; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
$setters[] = new Expr\MethodCall($instance, $method, $args); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
return $setters; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
public function getPostConstruct(Expr\Variable $instance, string $postConstruct): Expr\MethodCall |
94
|
|
|
{ |
95
|
|
|
return new Expr\MethodCall($instance, $postConstruct); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Return default argument value |
100
|
|
|
*/ |
101
|
|
|
private function getDefault(Argument $argument): Expr |
102
|
|
|
{ |
103
|
|
|
if ($argument->isDefaultAvailable()) { |
104
|
|
|
$default = $argument->getDefaultValue(); |
105
|
|
|
|
106
|
|
|
return ($this->normalizer)($default); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
throw new Unbound($argument->getMeta()); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Return code for provider |
114
|
|
|
* |
115
|
|
|
* "$provider" needs [class, method, parameter] for InjectionPoint (Contextual Dependency Injection) |
116
|
|
|
* |
117
|
|
|
* @return array<Expr\Array_|Node\Arg> |
118
|
|
|
*/ |
119
|
|
|
private function getInjectionProviderParams(Argument $argument) |
120
|
|
|
{ |
121
|
|
|
$param = $argument->get(); |
122
|
|
|
$class = $param->getDeclaringClass(); |
123
|
|
|
if (! $class instanceof ReflectionClass) { |
124
|
|
|
throw new LogicException(); // @codeCoverageIgnore |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
return [ |
128
|
|
|
new Node\Arg(new Scalar\String_((string) $argument)), |
129
|
|
|
new Expr\Array_([ |
130
|
|
|
new Node\Expr\ArrayItem(new Scalar\String_($class->name)), |
131
|
|
|
new Node\Expr\ArrayItem(new Scalar\String_($param->getDeclaringFunction()->name)), |
132
|
|
|
new Node\Expr\ArrayItem(new Scalar\String_($param->name)), |
133
|
|
|
]), |
134
|
|
|
]; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* Return setter method parameters |
139
|
|
|
* |
140
|
|
|
* Return false when no dependency given and @ Inject(optional=true) annotated to setter method. |
141
|
|
|
* |
142
|
|
|
* @param Argument[] $arguments |
143
|
|
|
* |
144
|
|
|
* @return false|Node\Expr[] |
145
|
|
|
*/ |
146
|
|
|
private function getSetterParams(array $arguments, bool $isOptional) |
147
|
|
|
{ |
148
|
|
|
$args = []; |
149
|
|
|
foreach ($arguments as $argument) { |
150
|
|
|
try { |
151
|
|
|
$args[] = $this->factoryCompiler->getArgStmt($argument); |
152
|
|
|
} catch (Unbound $e) { |
153
|
|
|
if ($isOptional) { |
154
|
|
|
return false; |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
return $args; |
160
|
|
|
} |
161
|
|
|
} |
162
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.