1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace RunOpenCode\AbstractBuilder\Generator; |
4
|
|
|
|
5
|
|
|
use PhpParser\Node\Param; |
6
|
|
|
use PhpParser\Node\Stmt; |
7
|
|
|
use PhpParser\Node\Stmt\ClassMethod; |
8
|
|
|
use RunOpenCode\AbstractBuilder\Ast\BuilderFactory; |
9
|
|
|
use RunOpenCode\AbstractBuilder\Ast\Metadata\ClassMetadata; |
10
|
|
|
use RunOpenCode\AbstractBuilder\Ast\Metadata\FileMetadata; |
11
|
|
|
use RunOpenCode\AbstractBuilder\Ast\Metadata\ParameterMetadata; |
12
|
|
|
use RunOpenCode\AbstractBuilder\Ast\MetadataLoader; |
13
|
|
|
use RunOpenCode\AbstractBuilder\Ast\Parser; |
14
|
|
|
use RunOpenCode\AbstractBuilder\Exception\LogicException; |
15
|
|
|
use RunOpenCode\AbstractBuilder\ReflectiveAbstractBuilder; |
16
|
|
|
use RunOpenCode\AbstractBuilder\Utils\ClassUtils; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Class BuilderClassFactory |
20
|
|
|
* |
21
|
|
|
* @package RunOpenCode\AbstractBuilder\Generator |
22
|
|
|
*/ |
23
|
|
|
class BuilderClassFactory |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* @var BuilderFactory |
27
|
|
|
*/ |
28
|
|
|
private $factory; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var \PhpParser\Parser |
32
|
|
|
*/ |
33
|
|
|
private $parser; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var ClassMetadata |
37
|
|
|
*/ |
38
|
|
|
private $subject; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var ClassMetadata |
42
|
|
|
*/ |
43
|
|
|
private $builder = null; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var bool |
47
|
|
|
*/ |
48
|
|
|
private $withReturnTypeDeclaration; |
49
|
|
|
|
50
|
|
|
public function __construct(ClassMetadata $subject, ClassMetadata $builder = null, $withReturnTypeDeclaration = false) |
51
|
|
|
{ |
52
|
|
|
$this->factory = BuilderFactory::getInstance(); |
53
|
|
|
$this->parser = Parser::getInstance(); |
54
|
|
|
$this->builder = $builder; |
55
|
|
|
$this->subject = $subject; |
56
|
|
|
$this->withReturnTypeDeclaration = $withReturnTypeDeclaration; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
public function addBuildMethod() |
60
|
|
|
{ |
61
|
|
|
if ($this->builder->hasPublicMethod('build', false)) { |
62
|
|
|
throw new LogicException(sprintf('Method "build()" is already defined in "%s".', $this->builder->getName())); |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
$method = $this->factory->method('build') |
66
|
|
|
->makePublic() |
67
|
|
|
->addStmts($this->parser->parse('<?php return parent::build();')) |
|
|
|
|
68
|
|
|
->setDocComment(sprintf( |
69
|
|
|
' |
70
|
|
|
/** |
71
|
|
|
* Builds new instance of %s from provided arguments. |
72
|
|
|
* |
73
|
|
|
* @return %s |
74
|
|
|
*/', $this->subject->getName(), $this->subject->getName())); |
75
|
|
|
|
76
|
|
|
if ($this->withReturnTypeDeclaration) { |
77
|
|
|
$method->setReturnType($this->subject->getName()); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
$this->appendMethod($method->getNode()); |
81
|
|
|
|
82
|
|
|
return $this; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
public function addCreateBuilderMethod() |
86
|
|
|
{ |
87
|
|
|
if ($this->builder->hasPublicMethod('createBuilder', false)) { |
88
|
|
|
throw new LogicException(sprintf('Method "createBuilder()" is already defined in "%s".', $this->builder->getName())); |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
if ($this->builder->isAbstract()) { |
92
|
|
|
throw new LogicException(sprintf('Method "createBuilder()" can not be generated for class "%s" since it\'s abstract.', $this->builder->getName())); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
$method = $this->factory->method('createBuilder') |
96
|
|
|
->makePublic() |
97
|
|
|
->makeStatic() |
98
|
|
|
->addStmts($this->parser->parse('<?php return parent::createBuilder();')) |
|
|
|
|
99
|
|
|
->setDocComment(sprintf( |
100
|
|
|
' |
101
|
|
|
/** |
102
|
|
|
* Produces new builder. |
103
|
|
|
* |
104
|
|
|
* @return %s |
105
|
|
|
* |
106
|
|
|
* @throws \RunOpenCode\AbstractBuilder\Exception\RuntimeException |
107
|
|
|
*/', $this->builder->getName())); |
108
|
|
|
|
109
|
|
|
if ($this->withReturnTypeDeclaration) { |
110
|
|
|
$method->setReturnType($this->builder->getName()); |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
$this->appendMethod($method->getNode()); |
114
|
|
|
|
115
|
|
|
return $this; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
View Code Duplication |
public function addGetObjectFqcnMethod() |
|
|
|
|
119
|
|
|
{ |
120
|
|
|
if ($this->builder->hasPublicMethod('getObjectFqcn', false)) { |
121
|
|
|
throw new LogicException(sprintf('Method "getObjectFqcn()" is already defined in "%s".', $this->builder->getName())); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
$method = $this->factory->method('getObjectFqcn') |
125
|
|
|
->makeProtected() |
126
|
|
|
->addStmts($this->parser->parse(sprintf('<?php return %s::class;', $this->subject->getName()))) |
|
|
|
|
127
|
|
|
->setDocComment( |
128
|
|
|
' |
129
|
|
|
/** |
130
|
|
|
* {@inheritdoc} |
131
|
|
|
*/' |
132
|
|
|
); |
133
|
|
|
|
134
|
|
|
if ($this->withReturnTypeDeclaration) { |
135
|
|
|
$method->setReturnType('string'); |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
$this->appendMethod($method->getNode()); |
139
|
|
|
|
140
|
|
|
return $this; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
View Code Duplication |
public function addConfigureParametersMethod() |
|
|
|
|
144
|
|
|
{ |
145
|
|
|
if ($this->builder->hasPublicMethod('configureParameters', false)) { |
146
|
|
|
throw new LogicException(sprintf('Method "configureParameters()" is already defined in "%s".', $this->builder->getName())); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
$method = $this->factory->method('configureParameters') |
150
|
|
|
->makeProtected() |
151
|
|
|
->addStmts($this->parser->parse("<?php \$defaults = parent::configureParameters();\n// Modify default values here\nreturn \$defaults;")) |
|
|
|
|
152
|
|
|
->setDocComment( |
153
|
|
|
' |
154
|
|
|
/** |
155
|
|
|
* You can override default building parameter values here |
156
|
|
|
* |
157
|
|
|
* {@inheritdoc} |
158
|
|
|
*/' |
159
|
|
|
); |
160
|
|
|
|
161
|
|
|
if ($this->withReturnTypeDeclaration) { |
162
|
|
|
$method->setReturnType('array'); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
$this->appendMethod($method->getNode()); |
166
|
|
|
|
167
|
|
|
return $this; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
public function addGetter($name, ParameterMetadata $parameter) |
171
|
|
|
{ |
172
|
|
|
$method = $this->factory->method($name) |
173
|
|
|
->makePublic() |
174
|
|
|
->addStmts($this->parser->parse(sprintf('<?php return $this->__doGet(\'%s\');', $parameter->getName()))) |
|
|
|
|
175
|
|
|
->setDocComment(sprintf( |
176
|
|
|
' |
177
|
|
|
/** |
178
|
|
|
* Get value for constructor parameter %s |
179
|
|
|
* |
180
|
|
|
* @return %s |
181
|
|
|
*/', $parameter->getName(), $parameter->getType() ?: 'mixed')); |
182
|
|
|
|
183
|
|
|
if ($this->withReturnTypeDeclaration && $parameter->getAst()->type) { |
184
|
|
|
$method->setReturnType($parameter->getAst()->type); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
$this->builder->getAst()->stmts[] = $method->getNode(); |
188
|
|
|
|
189
|
|
|
return $this; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
public function addSetter($name, ParameterMetadata $parameter) |
193
|
|
|
{ |
194
|
|
|
$ast = $parameter->getAst(); |
195
|
|
|
|
196
|
|
|
$method = $this->factory->method($name) |
197
|
|
|
->makePublic() |
198
|
|
|
->addParam(new Param( |
199
|
|
|
$ast->name, |
200
|
|
|
null, |
201
|
|
|
$ast->type, |
202
|
|
|
$ast->byRef, |
203
|
|
|
$ast->variadic, |
204
|
|
|
$ast->getAttributes() |
205
|
|
|
)) |
206
|
|
|
->addStmts($this->parser->parse(sprintf('<?php return $this->__doSet(\'%s\', $%s);', $parameter->getName(), $parameter->getName()))) |
|
|
|
|
207
|
|
|
->setDocComment(sprintf( |
208
|
|
|
' |
209
|
|
|
/** |
210
|
|
|
* Set value for constructor parameter %s |
211
|
|
|
* |
212
|
|
|
* @return %s |
213
|
|
|
*/', $parameter->getName(), $this->builder->getName())); |
214
|
|
|
|
215
|
|
|
if ($this->withReturnTypeDeclaration) { |
216
|
|
|
$method->setReturnType($this->builder->getName()); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
$this->appendMethod($method->getNode()); |
220
|
|
|
|
221
|
|
|
return $this; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Initializes new builder class |
226
|
|
|
* |
227
|
|
|
* @param string $filename |
228
|
|
|
* @param string $class |
229
|
|
|
* |
230
|
|
|
* @return FileMetadata |
231
|
|
|
*/ |
232
|
|
|
public function initialize($filename, $class) |
233
|
|
|
{ |
234
|
|
|
$namespace = $this->factory->namespace($ns = ClassUtils::getNamespace($class)); |
235
|
|
|
|
236
|
|
|
$namespace->addStmt($this->factory->use(ReflectiveAbstractBuilder::class)); |
237
|
|
|
|
238
|
|
|
$builderAstFactory = $this->factory->class(ClassUtils::getShortName($class)) |
239
|
|
|
->extend('ReflectiveAbstractBuilder') |
240
|
|
|
->setDocComment(sprintf( |
241
|
|
|
' |
242
|
|
|
/** |
243
|
|
|
* Class %s |
244
|
|
|
* |
245
|
|
|
* This class is implementation of builder pattern |
246
|
|
|
* for class %s. |
247
|
|
|
* |
248
|
|
|
* @package %s |
249
|
|
|
* |
250
|
|
|
* @see %s |
251
|
|
|
* @see https://en.wikipedia.org/wiki/Builder_pattern |
252
|
|
|
*/', $class, $this->subject->getName(), $ns, ReflectiveAbstractBuilder::class)); |
253
|
|
|
|
254
|
|
|
|
255
|
|
|
if ($this->subject->isFinal()) { |
256
|
|
|
$builderAstFactory->makeFinal(); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
if ($this->subject->isAbstract()) { |
260
|
|
|
$builderAstFactory->makeAbstract(); |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
$this->builder = new ClassMetadata( |
264
|
|
|
$class, |
265
|
|
|
MetadataLoader::create()->load(ReflectiveAbstractBuilder::class)->getClass(ReflectiveAbstractBuilder::class), |
266
|
|
|
[], |
267
|
|
|
$this->subject->isFinal(), |
268
|
|
|
$this->subject->isAbstract(), |
269
|
|
|
[], |
270
|
|
|
$builderAstFactory->getNode() |
271
|
|
|
); |
272
|
|
|
|
273
|
|
|
$namespace->addStmt($this->builder->getAst()); |
274
|
|
|
|
275
|
|
|
$this |
276
|
|
|
->addBuildMethod() |
277
|
|
|
->addGetObjectFqcnMethod() |
278
|
|
|
->addConfigureParametersMethod(); |
279
|
|
|
|
280
|
|
|
if (!$this->builder->isAbstract()) { |
281
|
|
|
$this->addCreateBuilderMethod(); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
return new FileMetadata($filename, [], [$this->builder], [], [$namespace->getNode()]); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
/** |
288
|
|
|
* Append method to class AST, order by visibility. |
289
|
|
|
* |
290
|
|
|
* @param ClassMethod $method |
291
|
|
|
*/ |
292
|
|
|
private function appendMethod(ClassMethod $method) |
293
|
|
|
{ |
294
|
|
|
$this->builder->getAst()->stmts[] = $method; |
295
|
|
|
|
296
|
|
|
// TODO Sort according to visibility and other parameters. |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.