1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Bdf\Prime\Entity; |
4
|
|
|
|
5
|
|
|
use Bdf\Prime\Mapper\Info\InfoInterface; |
6
|
|
|
use Bdf\Prime\Mapper\Info\MapperInfo; |
7
|
|
|
use Bdf\Prime\Mapper\Info\ObjectPropertyInfo; |
8
|
|
|
use Bdf\Prime\Mapper\Info\PropertyInfo; |
9
|
|
|
use Bdf\Prime\Mapper\Mapper; |
10
|
|
|
use Bdf\Prime\ServiceLocator; |
11
|
|
|
use Bdf\Prime\Types\PhpTypeInterface; |
12
|
|
|
use Doctrine\Inflector\Inflector; |
13
|
|
|
use Doctrine\Inflector\Inflector as InflectorObject; |
14
|
|
|
use Doctrine\Inflector\InflectorFactory; |
15
|
|
|
use Nette\PhpGenerator\ClassType; |
16
|
|
|
use Nette\PhpGenerator\Constant; |
17
|
|
|
use Nette\PhpGenerator\Method; |
18
|
|
|
use Nette\PhpGenerator\PhpFile; |
19
|
|
|
use Nette\PhpGenerator\PhpNamespace; |
20
|
|
|
use Nette\PhpGenerator\Printer; |
21
|
|
|
use Nette\PhpGenerator\PromotedParameter; |
22
|
|
|
use Nette\PhpGenerator\Property; |
23
|
|
|
use Nette\PhpGenerator\TraitUse; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* Generic class used to generate PHP 7 and 8 entity classes from Mapper. |
27
|
|
|
* |
28
|
|
|
* [php] |
29
|
|
|
* $mapper = $service->mappers()->build('Entity'); |
30
|
|
|
* |
31
|
|
|
* $generator = new EntityGenerator(); |
32
|
|
|
* $generator->setGenerateStubMethods(true); |
33
|
|
|
* $generator->setRegenerateEntityIfExists(false); |
34
|
|
|
* $generator->setUpdateEntityIfExists(true); |
35
|
|
|
* $generator->generate($mapper, '/path/to/generate/entities'); |
36
|
|
|
*/ |
37
|
|
|
class EntityGenerator |
38
|
|
|
{ |
39
|
|
|
// @todo should not be there : should be on PhpTypeInterface |
40
|
|
|
/** |
41
|
|
|
* Map prime types to php 7.4 property type |
42
|
|
|
*/ |
43
|
|
|
public const PROPERTY_TYPE_MAP = [ |
44
|
|
|
PhpTypeInterface::BOOLEAN => 'bool', |
45
|
|
|
PhpTypeInterface::DOUBLE => 'float', |
46
|
|
|
PhpTypeInterface::INTEGER => 'int', |
47
|
|
|
]; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Specifies class fields should be protected. |
51
|
|
|
*/ |
52
|
|
|
public const FIELD_VISIBLE_PROTECTED = ClassType::VisibilityProtected; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* Specifies class fields should be private. |
56
|
|
|
*/ |
57
|
|
|
public const FIELD_VISIBLE_PRIVATE = ClassType::VisibilityPrivate; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* The prime service locator |
61
|
|
|
* |
62
|
|
|
* @var ServiceLocator |
63
|
|
|
*/ |
64
|
|
|
private ServiceLocator $prime; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* The inflector instance |
68
|
|
|
* |
69
|
|
|
* @var InflectorObject |
70
|
|
|
*/ |
71
|
|
|
private InflectorObject $inflector; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* The mapper info |
75
|
|
|
* |
76
|
|
|
* @var MapperInfo |
77
|
|
|
*/ |
78
|
|
|
private MapperInfo $mapperInfo; |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* The extension to use for written php files. |
82
|
|
|
* |
83
|
|
|
* @var string |
84
|
|
|
*/ |
85
|
|
|
private string $extension = '.php'; |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Whether or not the current Mapper instance is new or old. |
89
|
|
|
* |
90
|
|
|
* @var boolean |
91
|
|
|
*/ |
92
|
|
|
private bool $isNew = true; |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Number of spaces to use for indention in generated code. |
96
|
|
|
*/ |
97
|
|
|
private int $numSpaces = 4; |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* The class all generated entities should extend. |
101
|
|
|
* |
102
|
|
|
* @var class-string|null |
|
|
|
|
103
|
|
|
*/ |
104
|
|
|
private ?string $classToExtend = null; |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* The interfaces all generated entities should implement. |
108
|
|
|
* |
109
|
|
|
* @var array<class-string, class-string> |
|
|
|
|
110
|
|
|
*/ |
111
|
|
|
private array $interfaces = []; |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* The traits |
115
|
|
|
* |
116
|
|
|
* @var array<string, string> |
117
|
|
|
*/ |
118
|
|
|
private array $traits = []; |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* Whether or not to generate sub methods. |
122
|
|
|
* |
123
|
|
|
* @var boolean |
124
|
|
|
*/ |
125
|
|
|
private bool $generateEntityStubMethods = true; |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Whether or not to update the entity class if it exists already. |
129
|
|
|
* |
130
|
|
|
* @var boolean |
131
|
|
|
*/ |
132
|
|
|
private bool $updateEntityIfExists = false; |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Whether or not to re-generate entity class if it exists already. |
136
|
|
|
* |
137
|
|
|
* @var boolean |
138
|
|
|
*/ |
139
|
|
|
private bool $regenerateEntityIfExists = false; |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* The name of get methods will not contains the 'get' prefix |
143
|
|
|
* |
144
|
|
|
* @var boolean |
145
|
|
|
*/ |
146
|
|
|
private bool $useGetShortcutMethod = true; |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Visibility of the field |
150
|
|
|
* |
151
|
|
|
* @var self::FIELD_* |
152
|
|
|
*/ |
153
|
|
|
private string $fieldVisibility = self::FIELD_VISIBLE_PROTECTED; |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Use type on generated properties |
157
|
|
|
* Note: only compatible with PHP >= 7.4 |
158
|
|
|
* |
159
|
|
|
* @var bool |
160
|
|
|
*/ |
161
|
|
|
private bool $useTypedProperties = false; |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Enable generation of PHP 8 constructor with promoted properties |
165
|
|
|
* If used, the constructor will not call import for filling the entity |
166
|
|
|
* |
167
|
|
|
* Note: only compatible with PHP >= 8.0 |
168
|
|
|
* |
169
|
|
|
* @var bool |
170
|
|
|
*/ |
171
|
|
|
private bool $useConstructorPropertyPromotion = false; |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Set prime service locator |
175
|
|
|
*/ |
176
|
38 |
|
public function __construct(ServiceLocator $prime, ?InflectorObject $inflector = null) |
177
|
|
|
{ |
178
|
38 |
|
$this->prime = $prime; |
179
|
38 |
|
$this->inflector = $inflector ?? InflectorFactory::create()->build(); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Generates and writes entity classes |
184
|
|
|
* |
185
|
|
|
* @param Mapper $mapper |
186
|
|
|
* @param string|null $file Entity file name |
187
|
|
|
* |
188
|
|
|
* @return string|false If no generation |
189
|
|
|
* |
190
|
|
|
* @api |
191
|
|
|
*/ |
192
|
36 |
|
public function generate(Mapper $mapper, ?string $file = null) |
193
|
|
|
{ |
194
|
36 |
|
$this->isNew = !$file || !file_exists($file) || $this->regenerateEntityIfExists; |
195
|
|
|
|
196
|
|
|
// If entity doesn't exist or we're re-generating the entities entirely |
197
|
36 |
|
if ($this->isNew || !$file) { |
198
|
30 |
|
return $this->generateEntityClass($mapper); |
199
|
|
|
// If entity exists and we're allowed to update the entity class |
200
|
6 |
|
} elseif ($this->updateEntityIfExists) { |
201
|
6 |
|
return $this->generateUpdatedEntityClass($mapper, $file); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
return false; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Generates a PHP5 Doctrine 2 entity class from the given Mapper instance. |
209
|
|
|
* |
210
|
|
|
* @param Mapper $mapper |
211
|
|
|
* |
212
|
|
|
* @return string |
213
|
|
|
*/ |
214
|
31 |
|
public function generateEntityClass(Mapper $mapper): string |
215
|
|
|
{ |
216
|
31 |
|
$this->mapperInfo = $mapper->info(); |
217
|
31 |
|
$className = $this->mapperInfo->className(); |
218
|
|
|
|
219
|
31 |
|
$file = new PhpFile(); |
220
|
|
|
|
221
|
31 |
|
$nsSeparatorPos = strrpos($className, '\\'); |
222
|
|
|
|
223
|
31 |
|
$namespace = $file->addNamespace($nsSeparatorPos !== false ? substr($className, 0, $nsSeparatorPos) : ''); |
224
|
31 |
|
$class = $namespace->addClass(substr($className, $nsSeparatorPos + 1)); |
225
|
|
|
|
226
|
31 |
|
$generator = new EntityClassGenerator($class, $namespace); |
227
|
|
|
|
228
|
31 |
|
$this->generateEntityClassDeclaration($class); |
229
|
31 |
|
$this->generateEntityUse($generator); |
230
|
31 |
|
$this->generateEntityBody($generator); |
231
|
|
|
|
232
|
31 |
|
return (new ConfigurableEntityPrinter($this)) |
233
|
31 |
|
->printFile($file); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
/** |
237
|
|
|
* Generates the updated code for the given Mapper and entity at path. |
238
|
|
|
* |
239
|
|
|
* @param Mapper $mapper |
240
|
|
|
* @param string $filename |
241
|
|
|
* |
242
|
|
|
* @return string |
243
|
|
|
*/ |
244
|
6 |
|
public function generateUpdatedEntityClass(Mapper $mapper, string $filename): string |
245
|
|
|
{ |
246
|
6 |
|
$this->mapperInfo = $mapper->info(); |
247
|
|
|
|
248
|
6 |
|
$currentCode = file_get_contents($filename); |
249
|
6 |
|
$file = PhpFile::fromCode($currentCode); |
250
|
|
|
|
251
|
6 |
|
$namespace = null; |
252
|
6 |
|
$class = null; |
253
|
|
|
|
254
|
6 |
|
foreach ($file->getNamespaces() as $foundNs) { |
255
|
6 |
|
foreach ($foundNs->getClasses() as $foundClass) { |
256
|
6 |
|
if ($this->mapperInfo->className() === $foundNs->getName() . '\\' . $foundClass->getName()) { |
257
|
6 |
|
$namespace = $foundNs; |
258
|
6 |
|
$class = $foundClass; |
259
|
6 |
|
break; |
260
|
|
|
} |
261
|
|
|
} |
262
|
|
|
} |
263
|
|
|
|
264
|
6 |
|
if (!$namespace || !$class) { |
265
|
|
|
throw new \InvalidArgumentException('The file do not contains class definition of ' . $this->mapperInfo->className()); |
266
|
|
|
} |
267
|
|
|
|
268
|
6 |
|
$this->generateEntityBody(new EntityClassGenerator($class, $namespace)); |
269
|
|
|
|
270
|
6 |
|
return (new ConfigurableEntityPrinter($this)) |
271
|
6 |
|
->printFile($file); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Generate class inheritance and traits |
276
|
|
|
*/ |
277
|
31 |
|
protected function generateEntityClassDeclaration(ClassType $class): void |
278
|
|
|
{ |
279
|
31 |
|
$class->addComment($class->getName()); |
|
|
|
|
280
|
|
|
|
281
|
31 |
|
if ($this->classToExtend) { |
282
|
2 |
|
$class->setExtends($this->classToExtend); |
283
|
|
|
} |
284
|
|
|
|
285
|
31 |
|
foreach ($this->interfaces as $interface) { |
286
|
3 |
|
$class->addImplement($interface); |
287
|
|
|
} |
288
|
|
|
|
289
|
31 |
|
$class->setTraits($this->traits); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Generate use part |
294
|
|
|
*/ |
295
|
31 |
|
protected function generateEntityUse(EntityClassGenerator $generator): void |
296
|
|
|
{ |
297
|
31 |
|
if (($parentClass = $this->getClassToExtend())) { |
298
|
2 |
|
$generator->addUse($parentClass); |
299
|
|
|
} |
300
|
|
|
|
301
|
31 |
|
foreach ($this->interfaces as $interface) { |
302
|
3 |
|
$generator->addUse($interface); |
303
|
|
|
} |
304
|
|
|
|
305
|
31 |
|
foreach ($this->traits as $trait) { |
306
|
2 |
|
$generator->addUse($trait); |
307
|
|
|
} |
308
|
|
|
|
309
|
31 |
|
foreach ($this->mapperInfo->objects() as $info) { |
310
|
26 |
|
$className = $info->className(); |
311
|
26 |
|
if (!$info->belongsToRoot()) { |
312
|
4 |
|
continue; |
313
|
|
|
} |
314
|
|
|
|
315
|
26 |
|
$generator->addUse($className); |
|
|
|
|
316
|
|
|
|
317
|
26 |
|
if ($info->wrapper() !== null) { |
318
|
1 |
|
$repository = $this->prime->repository($className); |
319
|
1 |
|
$wrapperClass = $repository->collectionFactory()->wrapperClass($info->wrapper()); |
|
|
|
|
320
|
|
|
|
321
|
1 |
|
$generator->addUse($wrapperClass); |
322
|
|
|
} |
323
|
|
|
} |
324
|
|
|
} |
325
|
|
|
|
326
|
37 |
|
protected function generateEntityBody(EntityClassGenerator $generator): void |
327
|
|
|
{ |
328
|
|
|
$properties = [ |
329
|
37 |
|
...$this->generateEntityFieldMappingProperties($generator, $this->useConstructorPropertyPromotion), |
330
|
37 |
|
...$this->generateEntityEmbeddedProperties($generator, $this->useConstructorPropertyPromotion) |
331
|
|
|
]; |
332
|
|
|
|
333
|
37 |
|
if (!$this->useConstructorPropertyPromotion) { |
334
|
33 |
|
foreach ($properties as $property) { |
335
|
31 |
|
$property->addProperty($generator); |
336
|
|
|
} |
337
|
|
|
} |
338
|
|
|
|
339
|
37 |
|
if ($this->generateEntityStubMethods) { |
340
|
36 |
|
$this->generateEntityStubMethods($generator); |
341
|
|
|
} |
342
|
|
|
|
343
|
37 |
|
$this->generateEntityConstructor($generator, $this->useConstructorPropertyPromotion, $properties); |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* @param bool $propertyPromotion Generate constructor with property promotion |
348
|
|
|
* @param list<PropertyGenerator> $properties |
|
|
|
|
349
|
|
|
*/ |
350
|
37 |
|
protected function generateEntityConstructor(EntityClassGenerator $generator, bool $propertyPromotion, array $properties): void |
351
|
|
|
{ |
352
|
37 |
|
$initializable = in_array(InitializableInterface::class, $this->interfaces); |
353
|
37 |
|
$isImportable = in_array(ImportableInterface::class, $this->interfaces) |
354
|
37 |
|
|| is_subclass_of($this->classToExtend, ImportableInterface::class); |
355
|
|
|
|
356
|
37 |
|
if ($propertyPromotion) { |
357
|
4 |
|
$this->generateConstructorWithPromotedProperties($generator, $initializable, $properties); |
358
|
|
|
} else { |
359
|
33 |
|
$this->generateClassicConstructor($generator, $isImportable, $initializable, $properties); |
360
|
|
|
} |
361
|
|
|
|
362
|
37 |
|
if (!$generator->hasMethod('initialize') && $initializable) { |
363
|
1 |
|
$init = Method::from([InitializableInterface::class, 'initialize']) |
364
|
1 |
|
->addComment('{@inheritdoc}'); |
365
|
|
|
|
366
|
1 |
|
foreach ($properties as $property) { |
367
|
1 |
|
$property->addInitializeLine($init); |
368
|
|
|
} |
369
|
|
|
|
370
|
1 |
|
$generator->addMember($init); |
371
|
|
|
} |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
/** |
375
|
|
|
* Generate PHP 8 constructor |
376
|
|
|
* |
377
|
|
|
* @param bool $initializable Does the entity class implements InitializableInterface ? |
378
|
|
|
* @param list<PropertyGenerator> $properties Properties to declare an initialize |
379
|
|
|
*/ |
380
|
4 |
|
private function generateConstructorWithPromotedProperties(EntityClassGenerator $generator, bool $initializable, array $properties): void |
381
|
|
|
{ |
382
|
4 |
|
$isUpdate = $generator->hasMethod('__construct'); |
383
|
4 |
|
$constructor = $isUpdate ? $generator->getMethod('__construct') : $generator->addMethod('__construct'); |
384
|
|
|
|
385
|
4 |
|
foreach ($properties as $property) { |
386
|
3 |
|
$property->addPromotedProperty($constructor, $generator); |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
// Only declare new properties |
390
|
4 |
|
if ($isUpdate) { |
391
|
2 |
|
return; |
392
|
|
|
} |
393
|
|
|
|
394
|
2 |
|
if ($initializable) { |
395
|
|
|
$constructor->addBody('$this->initialize();'); |
396
|
|
|
} else { |
397
|
2 |
|
foreach ($properties as $property) { |
398
|
|
|
// Assignment operator : use null coalesce assignment with property promotion |
399
|
|
|
// because assignation is performed before initializing default value |
400
|
2 |
|
$property->addInitializeLine($constructor, '??='); |
401
|
|
|
} |
402
|
|
|
} |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
/** |
406
|
|
|
* Generate classic constructor |
407
|
|
|
* |
408
|
|
|
* @param bool $isImportable Does the entity class implements InitializableInterface ? |
409
|
|
|
* @param bool $initializable Does the entity class implements ImportableInterface ? |
410
|
|
|
* @param list<PropertyGenerator> $properties Properties to initialize |
411
|
|
|
*/ |
412
|
33 |
|
private function generateClassicConstructor(EntityClassGenerator $generator, bool $isImportable, bool $initializable, array $properties): void |
413
|
|
|
{ |
414
|
|
|
// Do not support constructor update |
415
|
33 |
|
if ($generator->hasMethod('__construct')) { |
416
|
2 |
|
return; |
417
|
|
|
} |
418
|
|
|
|
419
|
31 |
|
if ($isImportable) { |
420
|
2 |
|
$constructor = $generator->addMethod('__construct'); |
421
|
|
|
|
422
|
|
|
$constructor |
423
|
2 |
|
->addParameter('data', []) |
424
|
2 |
|
->setType('array') |
425
|
|
|
; |
426
|
|
|
|
427
|
2 |
|
if ($initializable) { |
428
|
1 |
|
$constructor->addBody('$this->initialize();'); |
429
|
|
|
} else { |
430
|
1 |
|
foreach ($properties as $property) { |
431
|
1 |
|
$property->addInitializeLine($constructor); |
432
|
|
|
} |
433
|
|
|
} |
434
|
|
|
|
435
|
2 |
|
$constructor->addBody('$this->import($data);'); |
436
|
29 |
|
} elseif (!$initializable) { |
437
|
29 |
|
$constructor = null; |
438
|
|
|
|
439
|
29 |
|
foreach ($properties as $property) { |
440
|
27 |
|
if ($property->hasInitialisation()) { |
441
|
|
|
// Add a constructor only if it's necessary |
442
|
23 |
|
$constructor = $constructor ?? $generator->addMethod('__construct'); |
443
|
23 |
|
$property->addInitializeLine($constructor); |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
} |
447
|
|
|
} |
448
|
|
|
|
449
|
36 |
|
protected function generateEntityStubMethods(EntityClassGenerator $generator): void |
450
|
|
|
{ |
451
|
36 |
|
foreach ($this->mapperInfo->properties() as $property) { |
452
|
36 |
|
$this->generateSetter($generator, $property); |
453
|
36 |
|
$this->generateGetter($generator, $property); |
454
|
|
|
} |
455
|
|
|
|
456
|
36 |
|
foreach ($this->mapperInfo->objects() as $property) { |
457
|
25 |
|
if (!$property->belongsToRoot()) { |
458
|
4 |
|
continue; |
459
|
|
|
} |
460
|
|
|
|
461
|
25 |
|
if ($property->isArray() && $property->wrapper() === null) { |
462
|
19 |
|
$this->generateAdder($generator, $property); |
463
|
|
|
} |
464
|
|
|
|
465
|
25 |
|
$this->generateSetter($generator, $property); |
466
|
25 |
|
$this->generateGetter($generator, $property); |
467
|
|
|
} |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
/** |
471
|
|
|
* @param bool $forceNullable Force typehint to be nullable. Useful property promotion |
472
|
|
|
* @return list<PropertyGenerator> |
473
|
|
|
*/ |
474
|
37 |
|
protected function generateEntityFieldMappingProperties(EntityClassGenerator $class, bool $forceNullable = false): array |
475
|
|
|
{ |
476
|
37 |
|
$properties = []; |
477
|
|
|
|
478
|
37 |
|
foreach ($this->mapperInfo->properties() as $property) { |
479
|
37 |
|
if ($class->hasProperty($property->name())) { |
480
|
8 |
|
continue; |
481
|
|
|
} |
482
|
|
|
|
483
|
34 |
|
$properties[] = $generator = new PropertyGenerator($property->name()); |
484
|
|
|
|
485
|
34 |
|
$generator->setNullable($forceNullable || $property->isNullable()); |
486
|
34 |
|
$generator->setVisibility($this->fieldVisibility); |
487
|
34 |
|
$generator->setVarTag($property->phpType()); |
488
|
|
|
|
489
|
34 |
|
if ($this->useTypedProperties) { |
490
|
16 |
|
$generator->setTypeHint($property->phpType()); |
491
|
|
|
} |
492
|
|
|
|
493
|
34 |
|
if ($property->hasDefault() && !$property->isDateTime()) { |
494
|
5 |
|
$generator->setDefaultValue($property->convert($property->getDefault())); |
495
|
34 |
|
} elseif ($property->isArray()) { |
496
|
17 |
|
$generator->setDefaultValue([]); |
497
|
32 |
|
} elseif (($forceNullable || ($this->useTypedProperties && $property->isNullable()))) { |
498
|
|
|
// A nullable property should be defined as null by default |
499
|
|
|
// A property is considered as nullable if it's explicitly defined on mapper or if the field is auto-generated |
500
|
14 |
|
$generator->setDefaultValue(null); |
501
|
|
|
} |
502
|
|
|
|
503
|
34 |
|
if ($property->hasDefault() && $property->isDateTime()) { |
504
|
4 |
|
$constructorArgs = ''; |
505
|
|
|
// Add the default timezone from the property type. |
506
|
4 |
|
if ($timezone = $property->getTimezone()) { |
507
|
4 |
|
$constructorArgs = "'now', new \DateTimeZone('$timezone')"; |
508
|
|
|
} |
509
|
|
|
|
510
|
4 |
|
$generator->setInitialize('new '.$property->phpType().'('.$constructorArgs.')'); |
511
|
|
|
} |
512
|
|
|
} |
513
|
|
|
|
514
|
37 |
|
return $properties; |
|
|
|
|
515
|
|
|
} |
516
|
|
|
|
517
|
|
|
/** |
518
|
|
|
* @param bool $forceNullable Force typehint to be nullable. Useful property promotion |
519
|
|
|
* @return list<PropertyGenerator> |
520
|
|
|
*/ |
521
|
37 |
|
protected function generateEntityEmbeddedProperties(EntityClassGenerator $class, bool $forceNullable = false): array |
522
|
|
|
{ |
523
|
37 |
|
$properties = []; |
524
|
|
|
|
525
|
37 |
|
foreach ($this->mapperInfo->objects() as $property) { |
526
|
26 |
|
if (!$property->belongsToRoot() || $class->hasProperty($property->name())) { |
527
|
6 |
|
continue; |
528
|
|
|
} |
529
|
|
|
|
530
|
26 |
|
$properties[] = $generator = new PropertyGenerator($property->name()); |
531
|
26 |
|
$generator->setVisibility($this->fieldVisibility); |
532
|
|
|
|
533
|
|
|
// Embedded property : should not be null |
534
|
26 |
|
if (!$property->isRelation()) { |
535
|
4 |
|
$generator->setNullable($forceNullable); |
536
|
4 |
|
$generator->setVarTag($property->className()); |
|
|
|
|
537
|
4 |
|
$generator->setInitialize('new '.$class->simplifyType($property->className()).'()'); |
|
|
|
|
538
|
|
|
|
539
|
4 |
|
if ($this->useTypedProperties) { |
540
|
3 |
|
$generator->setTypeHint($property->className()); |
|
|
|
|
541
|
|
|
} |
542
|
|
|
|
543
|
4 |
|
continue; |
544
|
|
|
} |
545
|
|
|
|
546
|
25 |
|
$generator->setNullable($nullable = $forceNullable || $this->useTypedProperties); |
547
|
|
|
|
548
|
25 |
|
switch (true) { |
549
|
25 |
|
case $property->isArray() && $property->wrapper() === null: |
550
|
|
|
// Simple array relation |
551
|
18 |
|
$generator->setDefaultValue([]); |
552
|
18 |
|
$generator->setVarTag($property->className() . '[]'); |
553
|
|
|
|
554
|
18 |
|
if ($this->useTypedProperties) { |
555
|
7 |
|
$generator->setTypeHint('array'); |
556
|
|
|
} |
557
|
18 |
|
break; |
558
|
|
|
|
559
|
25 |
|
case $property->isArray() && $property->wrapper() !== null: |
560
|
|
|
// Array relation with wrapper |
561
|
1 |
|
$repository = $this->prime->repository($property->className()); |
562
|
1 |
|
$generator->setVarTag($repository->collectionFactory()->wrapperClass($property->wrapper()) . '|' . $property->className() . '[]'); |
|
|
|
|
563
|
|
|
|
564
|
|
|
// The value is an object : so the default value must be null |
565
|
1 |
|
if ($nullable) { |
566
|
1 |
|
$generator->setDefaultValue(null); |
567
|
|
|
} |
568
|
|
|
|
569
|
1 |
|
if ($this->useTypedProperties) { |
570
|
1 |
|
$generator->setTypeHint($repository->collectionFactory()->wrapperClass($property->wrapper())); |
571
|
|
|
} |
572
|
|
|
|
573
|
|
|
// @todo handle other wrapper types |
574
|
1 |
|
if ($property->wrapper() === 'collection') { |
575
|
1 |
|
$generator->setInitialize($class->simplifyType($property->className()).'::collection()'); |
576
|
|
|
} |
577
|
|
|
|
578
|
1 |
|
break; |
579
|
|
|
|
580
|
|
|
default: |
581
|
|
|
// Simple relation |
582
|
25 |
|
$generator->setVarTag($property->className()); |
583
|
25 |
|
$generator->setInitialize('new '.$class->simplifyType($property->className()).'()'); |
584
|
|
|
|
585
|
|
|
// The value is an object : so the default value must be null |
586
|
25 |
|
if ($nullable) { |
587
|
12 |
|
$generator->setDefaultValue(null); |
588
|
|
|
} |
589
|
|
|
|
590
|
25 |
|
if ($this->useTypedProperties) { |
591
|
12 |
|
$generator->setTypeHint($property->className()); |
592
|
|
|
} |
593
|
|
|
} |
594
|
|
|
} |
595
|
|
|
|
596
|
37 |
|
return $properties; |
|
|
|
|
597
|
|
|
} |
598
|
|
|
|
599
|
|
|
/** |
600
|
|
|
* Get accessor metadata for a given property |
601
|
|
|
* |
602
|
|
|
* @param EntityClassGenerator $generator |
603
|
|
|
* @param InfoInterface $propertyInfo |
604
|
|
|
* @param string|null $prefix Accessor prefix. Can be null to use the field name as method name. |
605
|
|
|
* @param bool $one In case of array property, get metadata for single item instead of the whole array. |
606
|
|
|
* |
607
|
|
|
* @return array{method: string, variable: string, field: string, typeHint: string, docType: string, nullable: bool}|null Accessor metadata, or null if the method already exists. |
608
|
|
|
*/ |
609
|
36 |
|
protected function accessorMetadata(EntityClassGenerator $generator, InfoInterface $propertyInfo, ?string $prefix, bool $one = false): ?array |
610
|
|
|
{ |
611
|
36 |
|
$fieldName = $propertyInfo->name(); |
612
|
|
|
|
613
|
36 |
|
if (!$prefix) { |
614
|
35 |
|
$variableName = $this->inflector->camelize($fieldName); |
615
|
35 |
|
$methodName = $variableName; |
616
|
|
|
} else { |
617
|
36 |
|
$methodName = $prefix . $this->inflector->classify($fieldName); |
618
|
36 |
|
$variableName = $this->inflector->camelize($fieldName); |
619
|
|
|
} |
620
|
|
|
|
621
|
36 |
|
if ($one) { |
622
|
19 |
|
$methodName = $this->inflector->singularize($methodName); |
623
|
19 |
|
$variableName = $this->inflector->singularize($variableName); |
624
|
|
|
} |
625
|
|
|
|
626
|
36 |
|
if ($generator->hasMethod($methodName)) { |
627
|
5 |
|
return null; |
628
|
|
|
} |
629
|
|
|
|
630
|
34 |
|
if ($propertyInfo->isObject()) { |
631
|
|
|
/** @var ObjectPropertyInfo $propertyInfo */ |
632
|
25 |
|
$variableType = $generator->simplifyType($propertyInfo->className()); |
|
|
|
|
633
|
|
|
// Only makes nullable for single relation |
634
|
25 |
|
$methodTypeHint = $propertyInfo->className(); |
635
|
25 |
|
$nullable = (!$one && !$propertyInfo->isEmbedded()); |
636
|
|
|
} else { |
637
|
|
|
/** @var PropertyInfo $propertyInfo */ |
638
|
34 |
|
$variableType = $propertyInfo->phpType(); |
639
|
34 |
|
$methodTypeHint = self::PROPERTY_TYPE_MAP[$variableType] ?? $variableType; |
640
|
34 |
|
$nullable = $propertyInfo->isNullable(); |
641
|
|
|
} |
642
|
|
|
|
643
|
34 |
|
if ($propertyInfo->isArray() && $one === false) { |
644
|
20 |
|
if ($propertyInfo->isObject() && $propertyInfo->wrapper() !== null) { |
|
|
|
|
645
|
|
|
/** @var ObjectPropertyInfo $propertyInfo */ |
646
|
1 |
|
$repository = $this->prime->repository($propertyInfo->className()); |
647
|
|
|
|
648
|
1 |
|
$methodTypeHint = $repository->collectionFactory()->wrapperClass($propertyInfo->wrapper()); |
|
|
|
|
649
|
1 |
|
$variableType .= '[]|'.$generator->simplifyType($methodTypeHint); |
650
|
|
|
} else { |
651
|
19 |
|
$methodTypeHint = 'array'; |
652
|
|
|
|
653
|
19 |
|
if ($variableType !== 'array') { |
654
|
19 |
|
$variableType .= '[]'; |
655
|
|
|
} |
656
|
|
|
} |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
return [ |
660
|
34 |
|
'field' => $fieldName, |
661
|
|
|
'variable' => $variableName, |
662
|
|
|
'method' => $methodName, |
663
|
|
|
'typeHint' => $methodTypeHint, |
664
|
|
|
'docType' => $variableType, |
665
|
|
|
'nullable' => $nullable, |
666
|
|
|
]; |
667
|
|
|
} |
668
|
|
|
|
669
|
36 |
|
protected function generateGetter(EntityClassGenerator $generator, InfoInterface $propertyInfo): void |
670
|
|
|
{ |
671
|
36 |
|
$metadata = $this->accessorMetadata($generator, $propertyInfo, $this->useGetShortcutMethod ? null : 'get'); |
672
|
|
|
|
673
|
36 |
|
if (!$metadata) { |
674
|
5 |
|
return; |
675
|
|
|
} |
676
|
|
|
|
677
|
34 |
|
$method = $generator->addMethod($metadata['method']); |
678
|
34 |
|
$method->addComment('Get ' . $metadata['variable']); |
679
|
34 |
|
$method->addComment(''); |
680
|
34 |
|
$method->setReturnType($metadata['typeHint']); |
681
|
34 |
|
$method->setReturnNullable($metadata['nullable']); |
682
|
34 |
|
$method->setBody('return $this->?;', [$metadata['field']]); |
683
|
34 |
|
$method->addComment('@return ' . $metadata['docType']); |
684
|
|
|
} |
685
|
|
|
|
686
|
36 |
|
protected function generateSetter(EntityClassGenerator $generator, InfoInterface $propertyInfo): void |
687
|
|
|
{ |
688
|
36 |
|
$metadata = $this->accessorMetadata($generator, $propertyInfo, 'set'); |
689
|
|
|
|
690
|
36 |
|
if (!$metadata) { |
691
|
5 |
|
return; |
692
|
|
|
} |
693
|
|
|
|
694
|
34 |
|
$method = $generator->addMethod($metadata['method']); |
695
|
34 |
|
$method->addComment('Set ' . $metadata['variable']); |
696
|
34 |
|
$method->addComment(''); |
697
|
34 |
|
$method->addComment('@param ' . $metadata['docType'] . ' $' . $metadata['variable']); |
698
|
34 |
|
$method->addComment(''); |
699
|
34 |
|
$method->addComment('@return $this'); |
700
|
34 |
|
$method->setReturnType('self'); |
701
|
|
|
$method |
702
|
34 |
|
->addParameter($metadata['variable']) |
703
|
34 |
|
->setType($metadata['typeHint']) |
704
|
34 |
|
->setNullable($metadata['nullable']) |
705
|
|
|
; |
706
|
34 |
|
$method->addBody('$this->? = $?;', [$metadata['field'], $metadata['variable']]); |
707
|
34 |
|
$method->addBody(''); |
708
|
34 |
|
$method->addBody('return $this;'); |
709
|
|
|
} |
710
|
|
|
|
711
|
19 |
|
protected function generateAdder(EntityClassGenerator $generator, InfoInterface $propertyInfo): void |
712
|
|
|
{ |
713
|
19 |
|
$metadata = $this->accessorMetadata($generator, $propertyInfo, 'add', true); |
714
|
|
|
|
715
|
19 |
|
if (!$metadata) { |
716
|
|
|
return; |
717
|
|
|
} |
718
|
|
|
|
719
|
19 |
|
$method = $generator->addMethod($metadata['method']); |
720
|
19 |
|
$method->addComment('Add ' . $metadata['variable']); |
721
|
19 |
|
$method->addComment(''); |
722
|
19 |
|
$method->addComment('@param ' . $metadata['docType'] . ' $' . $metadata['variable']); |
723
|
19 |
|
$method->addComment(''); |
724
|
19 |
|
$method->addComment('@return $this'); |
725
|
19 |
|
$method->setReturnType('self'); |
726
|
|
|
$method |
727
|
19 |
|
->addParameter($metadata['variable']) |
728
|
19 |
|
->setType($metadata['typeHint']) |
729
|
19 |
|
->setNullable($metadata['nullable']) |
730
|
|
|
; |
731
|
19 |
|
$method->addBody('$this->?[] = $?;', [$metadata['field'], $metadata['variable']]); |
732
|
19 |
|
$method->addBody(''); |
733
|
19 |
|
$method->addBody('return $this;'); |
734
|
|
|
} |
735
|
|
|
|
736
|
|
|
//---------------------- mutators |
737
|
|
|
|
738
|
|
|
/** |
739
|
|
|
* Sets the number of spaces the exported class should have. |
740
|
|
|
* |
741
|
|
|
* @api |
742
|
|
|
*/ |
743
|
2 |
|
public function setNumSpaces(int $numSpaces): void |
744
|
|
|
{ |
745
|
2 |
|
$this->numSpaces = $numSpaces; |
746
|
|
|
} |
747
|
|
|
|
748
|
|
|
/** |
749
|
|
|
* Gets the indentation spaces |
750
|
|
|
*/ |
751
|
38 |
|
public function getNumSpaces(): int |
752
|
|
|
{ |
753
|
38 |
|
return $this->numSpaces; |
754
|
|
|
} |
755
|
|
|
|
756
|
|
|
/** |
757
|
|
|
* Sets the extension to use when writing php files to disk. |
758
|
|
|
* |
759
|
|
|
* @api |
760
|
|
|
*/ |
761
|
1 |
|
public function setExtension(string $extension): void |
762
|
|
|
{ |
763
|
1 |
|
$this->extension = $extension; |
764
|
|
|
} |
765
|
|
|
|
766
|
|
|
/** |
767
|
|
|
* Get the file extension |
768
|
|
|
*/ |
769
|
1 |
|
public function getExtension(): string |
770
|
|
|
{ |
771
|
1 |
|
return $this->extension; |
772
|
|
|
} |
773
|
|
|
|
774
|
|
|
/** |
775
|
|
|
* Sets the name of the class the generated classes should extend from. |
776
|
|
|
* |
777
|
|
|
* @api |
778
|
|
|
*/ |
779
|
3 |
|
public function setClassToExtend(string $classToExtend): void |
780
|
|
|
{ |
781
|
3 |
|
$this->classToExtend = $classToExtend; |
782
|
|
|
} |
783
|
|
|
|
784
|
|
|
/** |
785
|
|
|
* Get the class to extend |
786
|
|
|
*/ |
787
|
32 |
|
public function getClassToExtend(): ?string |
788
|
|
|
{ |
789
|
32 |
|
return $this->classToExtend; |
790
|
|
|
} |
791
|
|
|
|
792
|
|
|
/** |
793
|
|
|
* Add interface to implement |
794
|
|
|
* |
795
|
|
|
* @api |
796
|
|
|
* |
797
|
|
|
* @return void |
798
|
|
|
*/ |
799
|
2 |
|
public function addInterface(string $interface): void |
800
|
|
|
{ |
801
|
2 |
|
$this->interfaces[$interface] = $interface; |
802
|
|
|
} |
803
|
|
|
|
804
|
|
|
/** |
805
|
|
|
* Sets the interfaces |
806
|
|
|
* |
807
|
|
|
* @param string[] $interfaces |
808
|
|
|
* |
809
|
|
|
* @api |
810
|
|
|
*/ |
811
|
2 |
|
public function setInterfaces(array $interfaces): void |
812
|
|
|
{ |
813
|
2 |
|
$this->interfaces = $interfaces; |
814
|
|
|
} |
815
|
|
|
|
816
|
|
|
/** |
817
|
|
|
* Get the registered interfaces |
818
|
|
|
*/ |
819
|
1 |
|
public function getInterfaces(): array |
820
|
|
|
{ |
821
|
1 |
|
return $this->interfaces; |
822
|
|
|
} |
823
|
|
|
|
824
|
|
|
/** |
825
|
|
|
* Add trait |
826
|
|
|
* |
827
|
|
|
* @api |
828
|
|
|
* |
829
|
|
|
* @return void |
830
|
|
|
*/ |
831
|
2 |
|
public function addTrait(string $trait): void |
832
|
|
|
{ |
833
|
2 |
|
$this->traits[$trait] = $trait; |
834
|
|
|
} |
835
|
|
|
|
836
|
|
|
/** |
837
|
|
|
* Sets the traits |
838
|
|
|
* |
839
|
|
|
* @param string[] $traits |
840
|
|
|
* |
841
|
|
|
* @api |
842
|
|
|
*/ |
843
|
1 |
|
public function setTraits(array $traits): void |
844
|
|
|
{ |
845
|
1 |
|
$this->traits = $traits; |
846
|
|
|
} |
847
|
|
|
|
848
|
|
|
/** |
849
|
|
|
* Get the registered traits |
850
|
|
|
*/ |
851
|
1 |
|
public function getTraits(): array |
852
|
|
|
{ |
853
|
1 |
|
return $this->traits; |
854
|
|
|
} |
855
|
|
|
|
856
|
|
|
/** |
857
|
|
|
* Sets the class fields visibility for the entity (can either be private or protected). |
858
|
|
|
* |
859
|
|
|
* @throws \InvalidArgumentException |
860
|
|
|
* |
861
|
|
|
* @api |
862
|
|
|
*/ |
863
|
2 |
|
public function setFieldVisibility(string $visibility): void |
864
|
|
|
{ |
865
|
2 |
|
if ($visibility !== static::FIELD_VISIBLE_PRIVATE && $visibility !== static::FIELD_VISIBLE_PROTECTED) { |
866
|
|
|
throw new \InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility); |
867
|
|
|
} |
868
|
|
|
|
869
|
2 |
|
$this->fieldVisibility = $visibility; |
|
|
|
|
870
|
|
|
} |
871
|
|
|
|
872
|
|
|
/** |
873
|
|
|
* Get the field visibility |
874
|
|
|
*/ |
875
|
1 |
|
public function getFieldVisibility(): string |
876
|
|
|
{ |
877
|
1 |
|
return $this->fieldVisibility; |
878
|
|
|
} |
879
|
|
|
|
880
|
|
|
/** |
881
|
|
|
* Sets whether or not to try and update the entity if it already exists. |
882
|
|
|
* |
883
|
|
|
* @api |
884
|
|
|
*/ |
885
|
7 |
|
public function setUpdateEntityIfExists(bool $bool): void |
886
|
|
|
{ |
887
|
7 |
|
$this->updateEntityIfExists = $bool; |
888
|
|
|
} |
889
|
|
|
|
890
|
|
|
/** |
891
|
|
|
* Get the flag for updating the entity |
892
|
|
|
*/ |
893
|
1 |
|
public function getUpdateEntityIfExists(): bool |
894
|
|
|
{ |
895
|
1 |
|
return $this->updateEntityIfExists; |
896
|
|
|
} |
897
|
|
|
|
898
|
|
|
/** |
899
|
|
|
* Sets whether or not to regenerate the entity if it exists. |
900
|
|
|
* |
901
|
|
|
* @api |
902
|
|
|
*/ |
903
|
1 |
|
public function setRegenerateEntityIfExists(bool $bool): void |
904
|
|
|
{ |
905
|
1 |
|
$this->regenerateEntityIfExists = $bool; |
906
|
|
|
} |
907
|
|
|
|
908
|
|
|
/** |
909
|
|
|
* Get the flag for regenerating entity |
910
|
|
|
*/ |
911
|
1 |
|
public function getRegenerateEntityIfExists(): bool |
912
|
|
|
{ |
913
|
1 |
|
return $this->regenerateEntityIfExists; |
914
|
|
|
} |
915
|
|
|
|
916
|
|
|
/** |
917
|
|
|
* Sets whether or not to generate stub methods for the entity. |
918
|
|
|
* |
919
|
|
|
* @api |
920
|
|
|
*/ |
921
|
2 |
|
public function setGenerateStubMethods(bool $bool): void |
922
|
|
|
{ |
923
|
2 |
|
$this->generateEntityStubMethods = $bool; |
924
|
|
|
} |
925
|
|
|
|
926
|
|
|
/** |
927
|
|
|
* Get the flag for generating stub methods |
928
|
|
|
*/ |
929
|
1 |
|
public function getGenerateStubMethods(): bool |
930
|
|
|
{ |
931
|
1 |
|
return $this->generateEntityStubMethods; |
932
|
|
|
} |
933
|
|
|
|
934
|
|
|
/** |
935
|
|
|
* Sets whether or not the get mehtod will be suffixed by 'get'. |
936
|
|
|
* |
937
|
|
|
* @param bool $flag |
938
|
|
|
* |
939
|
|
|
* @return void |
940
|
|
|
* |
941
|
|
|
* @api |
942
|
|
|
*/ |
943
|
2 |
|
public function useGetShortcutMethod(bool $flag = true) |
944
|
|
|
{ |
945
|
2 |
|
$this->useGetShortcutMethod = $flag; |
946
|
|
|
} |
947
|
|
|
|
948
|
|
|
/** |
949
|
|
|
* Get the flag for get mehtod name. |
950
|
|
|
*/ |
951
|
1 |
|
public function getUseGetShortcutMethod(): bool |
952
|
|
|
{ |
953
|
1 |
|
return $this->useGetShortcutMethod; |
954
|
|
|
} |
955
|
|
|
|
956
|
|
|
/** |
957
|
|
|
* @return bool |
958
|
|
|
*/ |
959
|
|
|
public function getUseTypedProperties(): bool |
960
|
|
|
{ |
961
|
|
|
return $this->useTypedProperties; |
962
|
|
|
} |
963
|
|
|
|
964
|
|
|
/** |
965
|
|
|
* Enable usage of php 7.4 type properties |
966
|
|
|
* |
967
|
|
|
* @param bool $useTypedProperties |
968
|
|
|
*/ |
969
|
17 |
|
public function useTypedProperties(bool $useTypedProperties = true): void |
970
|
|
|
{ |
971
|
17 |
|
$this->useTypedProperties = $useTypedProperties; |
972
|
|
|
} |
973
|
|
|
|
974
|
|
|
/** |
975
|
|
|
* @return bool |
976
|
|
|
*/ |
977
|
|
|
public function getUseConstructorPropertyPromotion(): bool |
978
|
|
|
{ |
979
|
|
|
return $this->useConstructorPropertyPromotion; |
980
|
|
|
} |
981
|
|
|
|
982
|
|
|
/** |
983
|
|
|
* Enable usage of PHP 8 promoted properties on constructor instead of array import |
984
|
|
|
* |
985
|
|
|
* @param bool $useConstructorPropertyPromotion |
986
|
|
|
*/ |
987
|
4 |
|
public function useConstructorPropertyPromotion(bool $useConstructorPropertyPromotion = true): void |
988
|
|
|
{ |
989
|
4 |
|
$this->useConstructorPropertyPromotion = $useConstructorPropertyPromotion; |
990
|
|
|
} |
991
|
|
|
} |
992
|
|
|
|
993
|
|
|
/** |
994
|
|
|
* @internal |
995
|
|
|
*/ |
996
|
|
|
class PropertyGenerator |
997
|
|
|
{ |
998
|
|
|
private string $name; |
999
|
|
|
private ?string $typeHint = null; |
1000
|
|
|
private bool $nullable = false; |
1001
|
|
|
private ?string $varTag = null; |
1002
|
|
|
private string $visibility = EntityGenerator::FIELD_VISIBLE_PROTECTED; |
1003
|
|
|
private $defaultValue; |
1004
|
|
|
private bool $hasDefaultValue = false; |
1005
|
|
|
private ?string $initialize = null; |
1006
|
|
|
|
1007
|
|
|
/** |
1008
|
|
|
* @param string $name |
1009
|
|
|
*/ |
1010
|
34 |
|
public function __construct(string $name) |
1011
|
|
|
{ |
1012
|
34 |
|
$this->name = $name; |
1013
|
|
|
} |
1014
|
|
|
|
1015
|
16 |
|
public function setTypeHint(string $typeHint): void |
1016
|
|
|
{ |
1017
|
16 |
|
$this->typeHint = $typeHint; |
1018
|
|
|
} |
1019
|
|
|
|
1020
|
34 |
|
public function setNullable(bool $nullable): void |
1021
|
|
|
{ |
1022
|
34 |
|
$this->nullable = $nullable; |
1023
|
|
|
} |
1024
|
|
|
|
1025
|
34 |
|
public function setVarTag(string $varTag): void |
1026
|
|
|
{ |
1027
|
34 |
|
$this->varTag = $varTag; |
1028
|
|
|
} |
1029
|
|
|
|
1030
|
34 |
|
public function setVisibility(string $visibility): void |
1031
|
|
|
{ |
1032
|
34 |
|
$this->visibility = $visibility; |
1033
|
|
|
} |
1034
|
|
|
|
1035
|
30 |
|
public function setDefaultValue($value): void |
1036
|
|
|
{ |
1037
|
30 |
|
$this->defaultValue = $value; |
1038
|
30 |
|
$this->hasDefaultValue = true; |
1039
|
|
|
} |
1040
|
|
|
|
1041
|
29 |
|
public function setInitialize(string $initialize): void |
1042
|
|
|
{ |
1043
|
29 |
|
$this->initialize = $initialize; |
1044
|
|
|
} |
1045
|
|
|
|
1046
|
27 |
|
public function hasInitialisation(): bool |
1047
|
|
|
{ |
1048
|
27 |
|
return $this->initialize !== null; |
1049
|
|
|
} |
1050
|
|
|
|
1051
|
31 |
|
public function addProperty(EntityClassGenerator $generator): void |
1052
|
|
|
{ |
1053
|
31 |
|
$property = $generator->addProperty($this->name); |
1054
|
|
|
|
1055
|
|
|
$property |
1056
|
31 |
|
->setNullable($this->nullable) |
1057
|
31 |
|
->setVisibility($this->visibility) |
1058
|
|
|
; |
1059
|
|
|
|
1060
|
31 |
|
if ($this->typeHint) { |
1061
|
13 |
|
$typehint = EntityGenerator::PROPERTY_TYPE_MAP[$this->typeHint] ?? $this->typeHint; |
1062
|
13 |
|
$property->setType($typehint); |
1063
|
|
|
} |
1064
|
|
|
|
1065
|
31 |
|
if ($this->hasDefaultValue) { |
1066
|
27 |
|
$property->setValue($this->defaultValue); |
1067
|
|
|
} |
1068
|
|
|
|
1069
|
31 |
|
if ($this->varTag) { |
1070
|
31 |
|
$type = $this->varTag; |
1071
|
|
|
|
1072
|
31 |
|
if (!isset(EntityGenerator::PROPERTY_TYPE_MAP[$this->varTag])) { |
1073
|
31 |
|
$type = $this->simplifyType($type, $generator); |
1074
|
|
|
} |
1075
|
|
|
|
1076
|
31 |
|
$property->addComment("\n@var $type"); |
1077
|
|
|
} |
1078
|
|
|
} |
1079
|
|
|
|
1080
|
3 |
|
public function addPromotedProperty(Method $constructor, EntityClassGenerator $generator): void |
1081
|
|
|
{ |
1082
|
3 |
|
$parameter = $constructor->addPromotedParameter($this->name); |
1083
|
|
|
|
1084
|
|
|
$parameter |
1085
|
3 |
|
->setNullable($this->nullable) |
1086
|
3 |
|
->setVisibility($this->visibility) |
1087
|
|
|
; |
1088
|
|
|
|
1089
|
3 |
|
if ($this->typeHint) { |
1090
|
3 |
|
$typehint = EntityGenerator::PROPERTY_TYPE_MAP[$this->typeHint] ?? $this->typeHint; |
1091
|
3 |
|
$parameter->setType($typehint); |
1092
|
|
|
} |
1093
|
|
|
|
1094
|
3 |
|
if ($this->hasDefaultValue) { |
1095
|
3 |
|
$parameter->setDefaultValue($this->defaultValue); |
1096
|
|
|
} |
1097
|
|
|
|
1098
|
3 |
|
if ($this->varTag) { |
1099
|
3 |
|
$type = $this->varTag; |
1100
|
|
|
|
1101
|
3 |
|
if (!isset(EntityGenerator::PROPERTY_TYPE_MAP[$this->varTag])) { |
1102
|
3 |
|
$type = $this->simplifyType($type, $generator); |
1103
|
|
|
} |
1104
|
|
|
|
1105
|
3 |
|
$parameter->addComment("\n@var $type"); |
1106
|
|
|
} |
1107
|
|
|
} |
1108
|
|
|
|
1109
|
27 |
|
public function addInitializeLine(Method $initializeMethod, string $assignationOperator = '='): void |
1110
|
|
|
{ |
1111
|
27 |
|
if ($this->initialize) { |
1112
|
27 |
|
$initializeMethod->addBody('$this->'.$this->name.' '.$assignationOperator.' '.$this->initialize.';'); |
1113
|
|
|
} |
1114
|
|
|
} |
1115
|
|
|
|
1116
|
34 |
|
private function simplifyType(string $type, EntityClassGenerator $generator): string |
1117
|
|
|
{ |
1118
|
34 |
|
$types = explode('|', $type); |
1119
|
|
|
|
1120
|
34 |
|
foreach ($types as &$part) { |
1121
|
34 |
|
$atomicType = $part; |
1122
|
34 |
|
$isArray = false; |
1123
|
|
|
|
1124
|
34 |
|
if (str_ends_with($atomicType, '[]')) { |
1125
|
19 |
|
$atomicType = substr($atomicType, 0, -2); |
1126
|
19 |
|
$isArray = true; |
1127
|
|
|
} |
1128
|
|
|
|
1129
|
34 |
|
if (isset(EntityGenerator::PROPERTY_TYPE_MAP[$atomicType])) { |
1130
|
|
|
continue; |
1131
|
|
|
} |
1132
|
|
|
|
1133
|
34 |
|
$part = $generator->simplifyType($atomicType) . ($isArray ? '[]' : ''); |
1134
|
|
|
} |
1135
|
|
|
|
1136
|
34 |
|
return implode('|', $types); |
1137
|
|
|
} |
1138
|
|
|
} |
1139
|
|
|
|
1140
|
|
|
/** |
1141
|
|
|
* @internal |
1142
|
|
|
*/ |
1143
|
|
|
class ConfigurableEntityPrinter extends Printer |
1144
|
|
|
{ |
1145
|
37 |
|
public function __construct(EntityGenerator $generator) |
1146
|
|
|
{ |
1147
|
37 |
|
parent::__construct(); |
1148
|
|
|
|
1149
|
37 |
|
$this->linesBetweenMethods = 1; |
1150
|
37 |
|
$this->linesBetweenProperties = 1; |
1151
|
37 |
|
$this->indentation = str_repeat(' ', $generator->getNumSpaces()); |
1152
|
|
|
} |
1153
|
|
|
|
1154
|
37 |
|
public function printClass($class, ?PhpNamespace $namespace = null): string |
1155
|
|
|
{ |
1156
|
37 |
|
$code = parent::printClass($class, $namespace); |
1157
|
|
|
|
1158
|
|
|
// Reformat property docblock : nette will generate property doc on single line |
1159
|
37 |
|
return preg_replace('#^( *)/\*\*(.*)\s+\*/$#m', "$1/**\n$1 *$2\n$1 */", $code); |
1160
|
|
|
} |
1161
|
|
|
} |
1162
|
|
|
|
1163
|
|
|
/** |
1164
|
|
|
* @internal |
1165
|
|
|
*/ |
1166
|
|
|
class EntityClassGenerator |
1167
|
|
|
{ |
1168
|
|
|
private ClassType $class; |
1169
|
|
|
private PhpNamespace $namespace; |
1170
|
|
|
|
1171
|
|
|
/** |
1172
|
|
|
* @param ClassType $class |
1173
|
|
|
* @param PhpNamespace $namespace |
1174
|
|
|
*/ |
1175
|
37 |
|
public function __construct(ClassType $class, PhpNamespace $namespace) |
1176
|
|
|
{ |
1177
|
37 |
|
$this->class = $class; |
1178
|
37 |
|
$this->namespace = $namespace; |
1179
|
|
|
} |
1180
|
|
|
|
1181
|
|
|
/** |
1182
|
|
|
* Add use statement if necessary |
1183
|
|
|
* Ignore classes without namespace or in current namespace |
1184
|
|
|
* |
1185
|
|
|
* @param string $class |
1186
|
|
|
* @return void |
1187
|
|
|
*/ |
1188
|
26 |
|
public function addUse(string $class): void |
1189
|
|
|
{ |
1190
|
26 |
|
$nsSeparatorPos = strrpos(ltrim($class, '\\'), '\\'); |
1191
|
|
|
|
1192
|
|
|
// Not namespaced : do not import |
1193
|
26 |
|
if ($nsSeparatorPos === false) { |
1194
|
2 |
|
return; |
1195
|
|
|
} |
1196
|
|
|
|
1197
|
26 |
|
$ns = substr($class, 0, $nsSeparatorPos); |
1198
|
|
|
|
1199
|
|
|
// Same namespace : import is not necessary |
1200
|
26 |
|
if ($ns === $this->namespace->getName()) { |
1201
|
26 |
|
return; |
1202
|
|
|
} |
1203
|
|
|
|
1204
|
4 |
|
$this->namespace->addUse($class); |
1205
|
|
|
} |
1206
|
|
|
|
1207
|
|
|
/** |
1208
|
|
|
* Check if the given method exists on the current generated class |
1209
|
|
|
* This method will check parent class and used traits |
1210
|
|
|
* |
1211
|
|
|
* @param string $method |
1212
|
|
|
* @return bool |
1213
|
|
|
*/ |
1214
|
37 |
|
public function hasMethod(string $method): bool |
1215
|
|
|
{ |
1216
|
|
|
/** @psalm-suppress InvalidArgument */ |
1217
|
37 |
|
if ($this->class->getExtends() && method_exists($this->class->getExtends(), $method)) { |
1218
|
2 |
|
return true; |
1219
|
|
|
} |
1220
|
|
|
|
1221
|
37 |
|
foreach ($this->class->getTraits() as $trait) { |
1222
|
2 |
|
if ($trait instanceof TraitUse) { |
1223
|
|
|
$trait = $trait->getName(); |
1224
|
|
|
} |
1225
|
|
|
|
1226
|
2 |
|
if (method_exists($trait, $method)) { |
1227
|
|
|
return true; |
1228
|
|
|
} |
1229
|
|
|
} |
1230
|
|
|
|
1231
|
37 |
|
return $this->class->hasMethod($method); |
1232
|
|
|
} |
1233
|
|
|
|
1234
|
|
|
/** |
1235
|
|
|
* Add a new method into the class |
1236
|
|
|
* |
1237
|
|
|
* @param string $name Method name |
1238
|
|
|
* @return Method |
1239
|
|
|
*/ |
1240
|
35 |
|
public function addMethod(string $name): Method |
1241
|
|
|
{ |
1242
|
35 |
|
return $this->class->addMethod($name); |
1243
|
|
|
} |
1244
|
|
|
|
1245
|
|
|
/** |
1246
|
|
|
* Check if the given property exists on the current generated class |
1247
|
|
|
* This method will check parent class and used traits |
1248
|
|
|
* It will also check promoted parameters on constructor |
1249
|
|
|
* |
1250
|
|
|
* @param string $property |
1251
|
|
|
* @return bool |
1252
|
|
|
*/ |
1253
|
37 |
|
public function hasProperty(string $property): bool |
1254
|
|
|
{ |
1255
|
37 |
|
if ($this->class->getExtends() && property_exists($this->class->getExtends(), $property)) { |
1256
|
2 |
|
return true; |
1257
|
|
|
} |
1258
|
|
|
|
1259
|
37 |
|
foreach ($this->class->getTraits() as $trait) { |
1260
|
2 |
|
if ($trait instanceof TraitUse) { |
1261
|
|
|
$trait = $trait->getName(); |
1262
|
|
|
} |
1263
|
|
|
|
1264
|
2 |
|
if (property_exists($trait, $property)) { |
1265
|
|
|
return true; |
1266
|
|
|
} |
1267
|
|
|
} |
1268
|
|
|
|
1269
|
37 |
|
if ($this->class->hasProperty($property)) { |
1270
|
4 |
|
return true; |
1271
|
|
|
} |
1272
|
|
|
|
1273
|
35 |
|
if (!$this->class->hasMethod('__construct')) { |
1274
|
33 |
|
return false; |
1275
|
|
|
} |
1276
|
|
|
|
1277
|
2 |
|
$parameter = $this->class->getMethod('__construct')->getParameters()[$property] ?? null; |
1278
|
|
|
|
1279
|
2 |
|
return $parameter instanceof PromotedParameter; |
1280
|
|
|
} |
1281
|
|
|
|
1282
|
|
|
/** |
1283
|
|
|
* Get a method by its name |
1284
|
|
|
* |
1285
|
|
|
* @param string $name |
1286
|
|
|
* @return Method |
1287
|
|
|
*/ |
1288
|
2 |
|
public function getMethod(string $name): Method |
1289
|
|
|
{ |
1290
|
2 |
|
return $this->class->getMethod($name); |
1291
|
|
|
} |
1292
|
|
|
|
1293
|
|
|
/** |
1294
|
|
|
* Add a new property on the entity class |
1295
|
|
|
* |
1296
|
|
|
* @param string $name Property name |
1297
|
|
|
* |
1298
|
|
|
* @return Property |
1299
|
|
|
*/ |
1300
|
31 |
|
public function addProperty(string $name): Property |
1301
|
|
|
{ |
1302
|
31 |
|
return $this->class->addProperty($name); |
1303
|
|
|
} |
1304
|
|
|
|
1305
|
|
|
/** |
1306
|
|
|
* Simplify a typename if imported or in the current namespace |
1307
|
|
|
* |
1308
|
|
|
* @param string $type |
1309
|
|
|
* @return string |
1310
|
|
|
*/ |
1311
|
34 |
|
public function simplifyType(string $type): string |
1312
|
|
|
{ |
1313
|
34 |
|
return $this->namespace->simplifyName($type); |
1314
|
|
|
} |
1315
|
|
|
|
1316
|
|
|
/** |
1317
|
|
|
* @param Method|Property|Constant|TraitUse $classMember |
1318
|
|
|
* |
1319
|
|
|
* @return void |
1320
|
|
|
*/ |
1321
|
1 |
|
public function addMember($classMember): void |
1322
|
|
|
{ |
1323
|
1 |
|
$this->class->addMember($classMember); |
1324
|
|
|
} |
1325
|
|
|
} |
1326
|
|
|
|