Passed
Push — feature-FRAM-58-refactor-entit... ( e3cd10 )
by Vincent
06:19
created

ConfigurableEntityPrinter   A

Complexity

Total Complexity 2

Size/Duplication

Total Lines 17
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 2
eloc 7
c 1
b 0
f 0
dl 0
loc 17
ccs 8
cts 8
cp 1
rs 10

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A printClass() 0 6 1
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\Method;
17
use Nette\PhpGenerator\PhpFile;
18
use Nette\PhpGenerator\PhpNamespace;
19
use Nette\PhpGenerator\Printer;
20
use Nette\PhpGenerator\PsrPrinter;
21
22
/**
23
 * Generic class used to generate PHP 7 and 8 entity classes from Mapper.
24
 *
25
 *     [php]
26
 *     $mapper = $service->mappers()->build('Entity');
27
 *
28
 *     $generator = new EntityGenerator();
29
 *     $generator->setGenerateStubMethods(true);
30
 *     $generator->setRegenerateEntityIfExists(false);
31
 *     $generator->setUpdateEntityIfExists(true);
32
 *     $generator->generate($mapper, '/path/to/generate/entities');
33
 *
34
 * @todo version 3.6 et 4.0 pour nette generator
35
 */
36
class EntityGenerator
37
{
38
    // @todo should not be there : should be on PhpTypeInterface
39
    /**
40
     * Map prime types to php 7.4 property type
41
     */
42
    public const PROPERTY_TYPE_MAP = [
43
        PhpTypeInterface::BOOLEAN => 'bool',
44
        PhpTypeInterface::DOUBLE => 'float',
45
        PhpTypeInterface::INTEGER => 'int',
46
    ];
47
48
    /**
49
     * Specifies class fields should be protected.
50
     */
51
    public const FIELD_VISIBLE_PROTECTED = ClassType::VISIBILITY_PROTECTED;
52
53
    /**
54
     * Specifies class fields should be private.
55
     */
56
    public const FIELD_VISIBLE_PRIVATE = ClassType::VISIBILITY_PRIVATE;
57
58
    /**
59
     * The prime service locator
60
     *
61
     * @var ServiceLocator
62
     */
63
    private ServiceLocator $prime;
64
65
    /**
66
     * The inflector instance
67
     *
68
     * @var InflectorObject
69
     */
70
    private InflectorObject $inflector;
71
72
    /**
73
     * The mapper info
74
     *
75
     * @var MapperInfo
76
     */
77
    private MapperInfo $mapperInfo;
78
79
    /**
80
     * The extension to use for written php files.
81
     *
82
     * @var string
83
     */
84
    private string $extension = '.php';
85
86
    /**
87
     * Whether or not the current Mapper instance is new or old.
88
     *
89
     * @var boolean
90
     */
91
    private bool $isNew = true;
92
93
    /**
94
     * @var array<class-string, array{properties: list<string>, methods: list<string>}>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string, arra...methods: list<string>}> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string, array{properties: list<string>, methods: list<string>}>.
Loading history...
95
     */
96
    private array $staticReflection = [];
97
98
    /**
99
     * Number of spaces to use for indention in generated code.
100
     */
101
    private int $numSpaces = 4;
102
103
    /**
104
     * The class all generated entities should extend.
105
     *
106
     * @var class-string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|null at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|null.
Loading history...
107
     */
108
    private ?string $classToExtend = null;
109
110
    /**
111
     * The interfaces all generated entities should implement.
112
     *
113
     * @var array<class-string, class-string>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string, class-string> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string, class-string>.
Loading history...
114
     */
115
    private array $interfaces = [];
116
117
    /**
118
     * The traits
119
     *
120
     * @var array<string, string>
121
     */
122
    private array $traits = [];
123
124
    /**
125
     * Whether or not to generate sub methods.
126
     *
127
     * @var boolean
128
     */
129
    private bool $generateEntityStubMethods = true;
130
131
    /**
132
     * Whether or not to update the entity class if it exists already.
133
     *
134
     * @var boolean
135
     */
136
    private bool $updateEntityIfExists = false;
137
138
    /**
139
     * Whether or not to re-generate entity class if it exists already.
140
     *
141
     * @var boolean
142
     */
143
    private bool $regenerateEntityIfExists = false;
144
145
    /**
146
     * The name of get methods will not contains the 'get' prefix
147
     *
148
     * @var boolean
149
     */
150
    private bool $useGetShortcutMethod = true;
151
152
    /**
153
     * Visibility of the field
154
     *
155
     * @var self::FIELD_*
156
     */
157
    private string $fieldVisibility = self::FIELD_VISIBLE_PROTECTED;
158
159
    /**
160
     * Use type on generated properties
161
     * Note: only compatible with PHP >= 7.4
162
     *
163
     * @var bool
164
     */
165
    private bool $useTypedProperties = false;
166
167
    /**
168
     * Enable generation of PHP 8 constructor with promoted properties
169
     * If used, the constructor will not call import for filling the entity
170
     *
171
     * Note: only compatible with PHP >= 8.0
172
     *
173
     * @var bool
174
     */
175
    private bool $useConstructorPropertyPromotion = false;
176
177
    /**
178
     * Set prime service locator
179
     */
180 36
    public function __construct(ServiceLocator $prime, ?InflectorObject $inflector = null)
181
    {
182 36
        $this->prime = $prime;
183 36
        $this->inflector = $inflector ?? InflectorFactory::create()->build();
184
    }
185
186
    /**
187
     * Generates and writes entity classes
188
     *
189
     * @param Mapper $mapper
190
     * @param string|null $file Entity file name
191
     *
192
     * @return string|false If no generation
193
     *
194
     * @api
195
     */
196 34
    public function generate(Mapper $mapper, ?string $file = null)
197
    {
198 34
        $this->isNew = !$file || !file_exists($file) || $this->regenerateEntityIfExists;
199
200
        // If entity doesn't exist or we're re-generating the entities entirely
201 34
        if ($this->isNew || !$file) {
202 30
            return $this->generateEntityClass($mapper);
203
        // If entity exists and we're allowed to update the entity class
204 4
        } elseif ($this->updateEntityIfExists) {
205 4
            return $this->generateUpdatedEntityClass($mapper, $file);
206
        }
207
208
        return false;
209
    }
210
211
    /**
212
     * Generates a PHP5 Doctrine 2 entity class from the given Mapper instance.
213
     *
214
     * @param Mapper $mapper
215
     *
216
     * @return string
217
     */
218 31
    public function generateEntityClass(Mapper $mapper): string
219
    {
220 31
        $this->mapperInfo = $mapper->info();
221
222 31
        $file = new PhpFile();
223
224 31
        $namespace = $file->addNamespace($this->hasNamespace($this->mapperInfo->className()) ? $this->getNamespace($this->mapperInfo->className()) : '');
225 31
        $generator = $namespace->addClass($this->getClassName($this->mapperInfo->className()));
226
227 31
        $this->staticReflection[$this->mapperInfo->className()] = ['properties' => [], 'methods' => []];
228
229 31
        $this->generateEntityClassDeclaration($generator);
230 31
        $this->generateEntityUse($namespace);
231 31
        $this->generateEntityBody($generator, $namespace);
232
233 31
        return (new ConfigurableEntityPrinter($this))
234 31
            ->printFile($file);
235
    }
236
237
    /**
238
     * Generates the updated code for the given Mapper and entity at path.
239
     *
240
     * @param Mapper $mapper
241
     * @param string $filename
242
     *
243
     * @return string
244
     */
245 4
    public function generateUpdatedEntityClass(Mapper $mapper, string $filename): string
246
    {
247 4
        $this->mapperInfo = $mapper->info();
248
249 4
        $currentCode = file_get_contents($filename);
250 4
        $file = PhpFile::fromCode($currentCode);
251 4
        $generator = $file->getClasses()[$this->mapperInfo->className()];
252 4
        $namespace = $file->getNamespaces()[$this->getNamespace($this->mapperInfo->className())];
253
254 4
        $this->parseTokensInEntityFile($currentCode);
255
256 4
        $this->generateEntityBody($generator, $namespace);
257
258 4
        return (new ConfigurableEntityPrinter($this))
259 4
            ->printFile($file);
260
    }
261
262
    /**
263
     * Generate class inheritance and traits
264
     */
265 31
    protected function generateEntityClassDeclaration(ClassType $generator): void
266
    {
267 31
        $generator->addComment($this->getClassName($this->mapperInfo->className()));
268
269 31
        if ($this->classToExtend) {
270 2
            if (method_exists($generator, 'setExtends')) {
271 2
                $generator->setExtends($this->classToExtend);
272
            } else {
273
                $generator->addExtend($this->classToExtend);
274
            }
275
        }
276
277 31
        foreach ($this->interfaces as $interface) {
278 3
            $generator->addImplement($interface);
279
        }
280
281 31
        $generator->setTraits($this->traits);
282
    }
283
284
    /**
285
     * Generate use part
286
     */
287 31
    protected function generateEntityUse(PhpNamespace $namespace): void
288
    {
289 31
        if (($parentClass = $this->getClassToExtend()) && $this->hasNamespace($parentClass)) {
290 2
            $namespace->addUse($parentClass);
291
        }
292
293 31
        foreach ($this->interfaces as $interface) {
294 3
            if ($this->hasNamespace($interface)) {
295 3
                $namespace->addUse($interface);
296
            }
297
        }
298
299 31
        foreach ($this->traits as $trait) {
300 2
            if ($this->hasNamespace($trait)) {
301
                $namespace->addUse($trait);
302
            }
303
        }
304
305 31
        foreach ($this->mapperInfo->objects() as $info) {
306 26
            $className = $info->className();
307 26
            if (!$info->belongsToRoot()) {
308 4
                continue;
309
            }
310
311 26
            if ($this->hasNamespace($className) && $this->getNamespace($className) !== $namespace->getName()) {
0 ignored issues
show
Bug introduced by
It seems like $className can also be of type null; however, parameter $className of Bdf\Prime\Entity\EntityGenerator::getNamespace() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

311
            if ($this->hasNamespace($className) && $this->getNamespace(/** @scrutinizer ignore-type */ $className) !== $namespace->getName()) {
Loading history...
Bug introduced by
It seems like $className can also be of type null; however, parameter $className of Bdf\Prime\Entity\EntityGenerator::hasNamespace() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

311
            if ($this->hasNamespace(/** @scrutinizer ignore-type */ $className) && $this->getNamespace($className) !== $namespace->getName()) {
Loading history...
312
                $namespace->addUse($className);
0 ignored issues
show
Bug introduced by
It seems like $className can also be of type null; however, parameter $name of Nette\PhpGenerator\PhpNamespace::addUse() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

312
                $namespace->addUse(/** @scrutinizer ignore-type */ $className);
Loading history...
313
            }
314
315 26
            if ($info->wrapper() !== null) {
316 1
                $repository = $this->prime->repository($className);
317 1
                $wrapperClass = $repository->collectionFactory()->wrapperClass($info->wrapper());
0 ignored issues
show
Bug introduced by
It seems like $info->wrapper() can also be of type callable; however, parameter $wrapper of Bdf\Prime\Collection\Col...Factory::wrapperClass() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

317
                $wrapperClass = $repository->collectionFactory()->wrapperClass(/** @scrutinizer ignore-type */ $info->wrapper());
Loading history...
318
319 1
                if ($this->hasNamespace($wrapperClass)) {
320 1
                    $namespace->addUse($wrapperClass);
321
                }
322
            }
323
        }
324
    }
325
326 35
    protected function generateEntityBody(ClassType $generator, PhpNamespace $namespace): void
327
    {
328
        $properties = [
329 35
            ...$this->generateEntityFieldMappingProperties($this->useConstructorPropertyPromotion),
330 35
            ...$this->generateEntityEmbeddedProperties($namespace, $this->useConstructorPropertyPromotion)
331
        ];
332
333 35
        if (!$this->useConstructorPropertyPromotion) {
334 33
            foreach ($properties as $property) {
335 31
                $property->addProperty($generator, $namespace);
336
            }
337
        }
338
339 35
        if ($this->generateEntityStubMethods) {
340 34
            $this->generateEntityStubMethods($generator, $namespace);
341
        }
342
343 35
        $this->generateEntityConstructor($generator, $namespace, $this->useConstructorPropertyPromotion, $properties);
344
    }
345
346
    /**
347
     * @param bool $propertyPromotion Generate constructor with property promotion
348
     * @param list<PropertyGenerator> $properties
0 ignored issues
show
Bug introduced by
The type Bdf\Prime\Entity\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
349
     */
350 35
    protected function generateEntityConstructor(ClassType $generator, PhpNamespace $namespace, bool $propertyPromotion, array $properties): void
351
    {
352 35
        $initializable = in_array(InitializableInterface::class, $this->interfaces);
353 35
        $isImportable  = in_array(ImportableInterface::class, $this->interfaces)
354 35
                    || is_subclass_of($this->classToExtend, ImportableInterface::class);
355
356 35
        if (!$this->hasMethod('__construct')) {
357 29
            if ($propertyPromotion) {
358 2
                $this->generateConstructorWithPromotedProperties($generator, $namespace, $initializable, $properties);
359
            } else {
360 27
                $this->generateClassicConstructor($generator, $namespace, $isImportable, $initializable, $properties);
361
            }
362
        }
363
364 35
        if (!$this->hasMethod('initialize') && $initializable) {
365 1
            $init = Method::from([InitializableInterface::class, 'initialize'])
366 1
                ->addComment('{@inheritdoc}');
367
368 1
            foreach ($properties as $property) {
369 1
                $property->addInitializeLine($init);
370
            }
371
372 1
            $generator->addMember($init);
373
        }
374
    }
375
376
    /**
377
     * Generate PHP 8 constructor
378
     *
379
     * @param bool $initializable Does the entity class implements InitializableInterface ?
380
     * @param list<PropertyGenerator> $properties Properties to declare an initialize
381
     */
382 2
    private function generateConstructorWithPromotedProperties(ClassType $generator, PhpNamespace $namespace, bool $initializable, array $properties): void
383
    {
384 2
        $constructor = $generator->addMethod('__construct');
385
386 2
        foreach ($properties as $property) {
387 2
            $property->addPromotedProperty($constructor, $namespace);
388
        }
389
390 2
        if ($initializable) {
391
            $constructor->addBody('$this->initialize();');
392
        } else {
393 2
            foreach ($properties as $property) {
394
                // Assignment operator : use null coalesce assignment with property promotion
395
                // because assignation is performed before initializing default value
396 2
                $property->addInitializeLine($constructor, '??=');
397
            }
398
        }
399
    }
400
401
    /**
402
     * Generate classic constructor
403
     *
404
     * @param bool $isImportable Does the entity class implements InitializableInterface ?
405
     * @param bool $initializable Does the entity class implements ImportableInterface ?
406
     * @param list<PropertyGenerator> $properties Properties to initialize
407
     */
408 27
    private function generateClassicConstructor(ClassType $generator, PhpNamespace $namespace, bool $isImportable, bool $initializable, array $properties): void
0 ignored issues
show
Unused Code introduced by
The parameter $namespace is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

408
    private function generateClassicConstructor(ClassType $generator, /** @scrutinizer ignore-unused */ PhpNamespace $namespace, bool $isImportable, bool $initializable, array $properties): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
409
    {
410 27
        if ($isImportable) {
411 2
            $constructor = $generator->addMethod('__construct');
412
413
            $constructor
414 2
                ->addParameter('data', [])
415 2
                ->setType('array')
416
            ;
417
418 2
            if ($initializable) {
419 1
                $constructor->addBody('$this->initialize();');
420
            } else {
421 1
                foreach ($properties as $property) {
422 1
                    $property->addInitializeLine($constructor);
423
                }
424
            }
425
426 2
            $constructor->addBody('$this->import($data);');
427 25
        } elseif (!$initializable) {
428 25
            $constructor = null;
429
430 25
            foreach ($properties as $property) {
431 25
                if ($property->hasInitialisation()) {
432
                    // Add a constructor only if it's necessary
433 23
                    $constructor = $constructor ?? $generator->addMethod('__construct');
434 23
                    $property->addInitializeLine($constructor);
435
                }
436
            }
437
        }
438
    }
439
440 34
    protected function generateEntityStubMethods(ClassType $generator, PhpNamespace $namespace): void
441
    {
442 34
        foreach ($this->mapperInfo->properties() as $property) {
443 34
            $this->generateSetter($generator, $namespace, $property);
444 34
            $this->generateGetter($generator, $namespace, $property);
445
        }
446
447 34
        foreach ($this->mapperInfo->objects() as $property) {
448 25
            if (!$property->belongsToRoot()) {
449 4
                continue;
450
            }
451
452 25
            if ($property->isArray() && $property->wrapper() === null) {
453 19
                $this->generateAdder($generator, $namespace, $property);
454
            }
455
456 25
            $this->generateSetter($generator, $namespace, $property);
457 25
            $this->generateGetter($generator, $namespace, $property);
458
        }
459
    }
460
461
    /**
462
     * @param bool $forceNullable Force typehint to be nullable. Useful property promotion
463
     * @return list<PropertyGenerator>
464
     */
465 35
    protected function generateEntityFieldMappingProperties(bool $forceNullable = false): array
466
    {
467 35
        $properties = [];
468
469 35
        foreach ($this->mapperInfo->properties() as $property) {
470 35
            if ($this->hasProperty($property->name())) {
471 6
                continue;
472
            }
473
474 33
            $properties[] = $generator = new PropertyGenerator($property->name());
475
476 33
            $generator->setNullable($forceNullable || $property->isNullable());
477 33
            $generator->setVisibility($this->fieldVisibility);
478 33
            $generator->setVarTag($property->phpType());
479
480 33
            if ($this->useTypedProperties) {
481 15
                $generator->setTypeHint($property->phpType());
482
            }
483
484 33
            if ($property->hasDefault() && !$property->isDateTime()) {
485 5
                $generator->setDefaultValue($property->convert($property->getDefault()));
486 33
            } elseif ($property->isArray()) {
487 17
                $generator->setDefaultValue([]);
488 31
            } elseif (($forceNullable || ($this->useTypedProperties && $property->isNullable()))) {
489
                // A nullable property should be defined as null by default
490
                // A property is considered as nullable if it's explicitly defined on mapper or if the field is auto-generated
491 13
                $generator->setDefaultValue(null);
492
            }
493
494 33
            if ($property->hasDefault() && $property->isDateTime()) {
495 4
                $constructorArgs = '';
496
                // Add the default timezone from the property type.
497 4
                if ($timezone = $property->getTimezone()) {
498 4
                    $constructorArgs = "'now', new \DateTimeZone('$timezone')";
499
                }
500
501 4
                $generator->setInitialize('new '.$property->phpType().'('.$constructorArgs.')');
502
            }
503
        }
504
505 35
        return $properties;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $properties returns the type Bdf\Prime\Entity\PropertyGenerator[]|array which is incompatible with the documented return type Bdf\Prime\Entity\list.
Loading history...
506
    }
507
508
    /**
509
     * @param bool $forceNullable Force typehint to be nullable. Useful property promotion
510
     * @return list<PropertyGenerator>
511
     */
512 35
    protected function generateEntityEmbeddedProperties(PhpNamespace $namespace, bool $forceNullable = false): array
513
    {
514 35
        $properties = [];
515
516 35
        foreach ($this->mapperInfo->objects() as $property) {
517 26
            if (!$property->belongsToRoot() || $this->hasProperty($property->name())) {
518 6
                continue;
519
            }
520
521 26
            $properties[] = $generator = new PropertyGenerator($property->name());
522 26
            $generator->setVisibility($this->fieldVisibility);
523
524
            // Embedded property : should not be null
525 26
            if (!$property->isRelation()) {
526 4
                $generator->setNullable($forceNullable);
527 4
                $generator->setVarTag($property->className());
0 ignored issues
show
Bug introduced by
It seems like $property->className() can also be of type null; however, parameter $varTag of Bdf\Prime\Entity\PropertyGenerator::setVarTag() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

527
                $generator->setVarTag(/** @scrutinizer ignore-type */ $property->className());
Loading history...
528 4
                $generator->setInitialize('new '.$namespace->simplifyName($property->className()).'()');
0 ignored issues
show
Bug introduced by
It seems like $property->className() can also be of type null; however, parameter $name of Nette\PhpGenerator\PhpNamespace::simplifyName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

528
                $generator->setInitialize('new '.$namespace->simplifyName(/** @scrutinizer ignore-type */ $property->className()).'()');
Loading history...
529
530 4
                if ($this->useTypedProperties) {
531 3
                    $generator->setTypeHint($property->className());
0 ignored issues
show
Bug introduced by
It seems like $property->className() can also be of type null; however, parameter $typeHint of Bdf\Prime\Entity\PropertyGenerator::setTypeHint() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

531
                    $generator->setTypeHint(/** @scrutinizer ignore-type */ $property->className());
Loading history...
532
                }
533
534 4
                continue;
535
            }
536
537 25
            $generator->setNullable($nullable = $forceNullable || $this->useTypedProperties);
538
539 25
            switch(true) {
540 25
                case $property->isArray() && $property->wrapper() === null:
541
                    // Simple array relation
542 18
                    $generator->setDefaultValue([]);
543 18
                    $generator->setVarTag($property->className() . '[]');
544
545 18
                    if ($this->useTypedProperties) {
546 7
                        $generator->setTypeHint('array');
547
                    }
548 18
                    break;
549
550 25
                case $property->isArray() && $property->wrapper() !== null:
551
                    // Array relation with wrapper
552 1
                    $repository = $this->prime->repository($property->className());
553 1
                    $generator->setVarTag($repository->collectionFactory()->wrapperClass($property->wrapper()) . '|' . $property->className() . '[]');
0 ignored issues
show
Bug introduced by
It seems like $property->wrapper() can also be of type callable and null; however, parameter $wrapper of Bdf\Prime\Collection\Col...Factory::wrapperClass() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

553
                    $generator->setVarTag($repository->collectionFactory()->wrapperClass(/** @scrutinizer ignore-type */ $property->wrapper()) . '|' . $property->className() . '[]');
Loading history...
554
555
                    // The value is an object : so the default value must be null
556 1
                    if ($nullable) {
557 1
                        $generator->setDefaultValue(null);
558
                    }
559
560 1
                    if ($this->useTypedProperties) {
561 1
                        $generator->setTypeHint($repository->collectionFactory()->wrapperClass($property->wrapper()));
562
                    }
563
564
                    // @todo handle other wrapper types
565 1
                    if ($property->wrapper() === 'collection') {
566 1
                        $generator->setInitialize($namespace->simplifyName($property->className()).'::collection()');
567
                    }
568
569 1
                    break;
570
571
                default:
572
                    // Simple relation
573 25
                    $generator->setVarTag($property->className());
574 25
                    $generator->setInitialize('new '.$namespace->simplifyName($property->className()).'()');
575
576
                    // The value is an object : so the default value must be null
577 25
                    if ($nullable) {
578 12
                        $generator->setDefaultValue(null);
579
                    }
580
581 25
                    if ($this->useTypedProperties) {
582 12
                        $generator->setTypeHint($property->className());
583
                    }
584
            }
585
        }
586
587 35
        return $properties;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $properties returns the type Bdf\Prime\Entity\PropertyGenerator[]|array which is incompatible with the documented return type Bdf\Prime\Entity\list.
Loading history...
588
    }
589
590
    /**
591
     * Get accessor metadata for a given property
592
     *
593
     * @param PhpNamespace $namespace
594
     * @param InfoInterface $propertyInfo
595
     * @param string|null $prefix Accessor prefix. Can be null to use the field name as method name.
596
     * @param bool $one In case of array property, get metadata for single item instead of the whole array.
597
     *
598
     * @return array{method: string, variable: string, field: string, typeHint: string, docType: string, nullable: bool}|null Accessor metadata, or null if the method already exists.
599
     */
600 34
    protected function accessorMetadata(PhpNamespace $namespace, InfoInterface $propertyInfo, ?string $prefix, bool $one = false): ?array
601
    {
602 34
        $fieldName = $propertyInfo->name();
603
604 34
        if (!$prefix) {
605 33
            $variableName = $this->inflector->camelize($fieldName);
606 33
            $methodName = $variableName;
607
        } else {
608 34
            $methodName = $prefix . $this->inflector->classify($fieldName);
609 34
            $variableName = $this->inflector->camelize($fieldName);
610
        }
611
612 34
        if ($one) {
613 19
            $methodName = $this->inflector->singularize($methodName);
614 19
            $variableName = $this->inflector->singularize($variableName);
615
        }
616
617 34
        if ($this->hasMethod($methodName)) {
618 3
            return null;
619
        }
620
621 33
        $this->staticReflection[$this->mapperInfo->className()]['methods'][] = strtolower($methodName);
622
623 33
        if ($propertyInfo->isObject()) {
624
            /** @var ObjectPropertyInfo $propertyInfo */
625 25
            $variableType = $namespace->simplifyName($propertyInfo->className());
0 ignored issues
show
Bug introduced by
It seems like $propertyInfo->className() can also be of type null; however, parameter $name of Nette\PhpGenerator\PhpNamespace::simplifyName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

625
            $variableType = $namespace->simplifyName(/** @scrutinizer ignore-type */ $propertyInfo->className());
Loading history...
626
            // Only makes nullable for single relation
627 25
            $methodTypeHint = $propertyInfo->className();
628 25
            $nullable = (!$one && !$propertyInfo->isEmbedded());
629
        } else {
630
            /** @var PropertyInfo $propertyInfo */
631 33
            $variableType = $propertyInfo->phpType();
632 33
            $methodTypeHint = self::PROPERTY_TYPE_MAP[$variableType] ?? $variableType;
633 33
            $nullable = $propertyInfo->isNullable();
634
        }
635
636 33
        if ($propertyInfo->isArray() && $one === false) {
637 20
            if ($propertyInfo->isObject() && $propertyInfo->wrapper() !== null) {
0 ignored issues
show
Bug introduced by
The method wrapper() does not exist on Bdf\Prime\Mapper\Info\PropertyInfo. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

637
            if ($propertyInfo->isObject() && $propertyInfo->/** @scrutinizer ignore-call */ wrapper() !== null) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
638
                /** @var ObjectPropertyInfo $propertyInfo */
639 1
                $repository = $this->prime->repository($propertyInfo->className());
640
641 1
                $methodTypeHint = $repository->collectionFactory()->wrapperClass($propertyInfo->wrapper());
0 ignored issues
show
Bug introduced by
It seems like $propertyInfo->wrapper() can also be of type callable; however, parameter $wrapper of Bdf\Prime\Collection\Col...Factory::wrapperClass() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

641
                $methodTypeHint = $repository->collectionFactory()->wrapperClass(/** @scrutinizer ignore-type */ $propertyInfo->wrapper());
Loading history...
642 1
                $variableType .= '[]|'.$namespace->simplifyName($methodTypeHint);
643
            } else {
644 19
                $methodTypeHint = 'array';
645
646 19
                if ($variableType !== 'array') {
647 19
                    $variableType .= '[]';
648
                }
649
            }
650
        }
651
652
        return [
653 33
            'field' => $fieldName,
654
            'variable' => $variableName,
655
            'method' => $methodName,
656
            'typeHint' => $methodTypeHint,
657
            'docType' => $variableType,
658
            'nullable' => $nullable,
659
        ];
660
    }
661
662 34
    protected function generateGetter(ClassType $generator, PhpNamespace $namespace, InfoInterface $propertyInfo): void
663
    {
664 34
        $metadata = $this->accessorMetadata($namespace, $propertyInfo, $this->useGetShortcutMethod ? null : 'get');
665
666 34
        if (!$metadata) {
667 3
            return;
668
        }
669
670 33
        $method = $generator->addMethod($metadata['method']);
671 33
        $method->addComment('Get ' . $metadata['variable']);
672 33
        $method->addComment('');
673 33
        $method->setReturnType($metadata['typeHint']);
674 33
        $method->setReturnNullable($metadata['nullable']);
675 33
        $method->setBody('return $this->?;', [$metadata['field']]);
676 33
        $method->addComment('@return ' . $metadata['docType']);
677
    }
678
679 34
    protected function generateSetter(ClassType $generator, PhpNamespace $namespace, InfoInterface $propertyInfo): void
680
    {
681 34
        $metadata = $this->accessorMetadata($namespace, $propertyInfo, 'set');
682
683 34
        if (!$metadata) {
684 3
            return;
685
        }
686
687 33
        $method = $generator->addMethod($metadata['method']);
688 33
        $method->addComment('Set ' . $metadata['variable']);
689 33
        $method->addComment('');
690 33
        $method->addComment('@param ' . $metadata['docType'] . ' $' . $metadata['variable']);
691 33
        $method->addComment('');
692 33
        $method->addComment('@return $this');
693 33
        $method->setReturnType('self');
694
        $method
695 33
            ->addParameter($metadata['variable'])
696 33
            ->setType($metadata['typeHint'])
697 33
            ->setNullable($metadata['nullable'])
698
        ;
699 33
        $method->addBody('$this->? = $?;', [$metadata['field'], $metadata['variable']]);
700 33
        $method->addBody('');
701 33
        $method->addBody('return $this;');
702
    }
703
704 19
    protected function generateAdder(ClassType $generator, PhpNamespace $namespace, InfoInterface $propertyInfo): void
705
    {
706 19
        $metadata = $this->accessorMetadata($namespace, $propertyInfo, 'add', true);
707
708 19
        if (!$metadata) {
709
            return;
710
        }
711
712 19
        $method = $generator->addMethod($metadata['method']);
713 19
        $method->addComment('Add ' . $metadata['variable']);
714 19
        $method->addComment('');
715 19
        $method->addComment('@param ' . $metadata['docType'] . ' $' . $metadata['variable']);
716 19
        $method->addComment('');
717 19
        $method->addComment('@return $this');
718 19
        $method->setReturnType('self');
719
        $method
720 19
            ->addParameter($metadata['variable'])
721 19
            ->setType($metadata['typeHint'])
722 19
            ->setNullable($metadata['nullable'])
723
        ;
724 19
        $method->addBody('$this->?[] = $?;', [$metadata['field'], $metadata['variable']]);
725 19
        $method->addBody('');
726 19
        $method->addBody('return $this;');
727
    }
728
729
    //
730
    //---------- tools methods
731
    //
732
733
    /**
734
     * @todo this won't work if there is a namespace in brackets and a class outside of it.
735
     *
736
     * @param string $src
737
     *
738
     * @return void
739
     */
740 4
    protected function parseTokensInEntityFile(string $src): void
741
    {
742 4
        $tokens = token_get_all($src);
743 4
        $lastSeenNamespace = "";
744
        /* @var class-string $lastSeenClass */
745 4
        $lastSeenClass = null;
746
747 4
        $inNamespace = false;
748 4
        $inClass = false;
749
750 4
        for ($i = 0; $i < count($tokens); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
751 4
            $token = $tokens[$i];
752 4
            if (in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
753 4
                continue;
754
            }
755
756 4
            if ($inNamespace) {
757 4
                if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING || (defined('T_NAME_QUALIFIED') && $token[0] == T_NAME_QUALIFIED)) {
758 4
                    $lastSeenNamespace .= $token[1];
759 4
                } elseif (is_string($token) && in_array($token, [';', '{'])) {
760 4
                    $inNamespace = false;
761
                }
762
            }
763
764 4
            if ($inClass) {
765 4
                $inClass = false;
766 4
                $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
767 4
                $this->staticReflection[$lastSeenClass]['properties'] = [];
768 4
                $this->staticReflection[$lastSeenClass]['methods'] = [];
769
            }
770
771 4
            if ($token[0] == T_NAMESPACE) {
772 4
                $lastSeenNamespace = "";
773 4
                $inNamespace = true;
774 4
            } elseif ($token[0] == T_CLASS && $tokens[$i-1][0] != T_DOUBLE_COLON) {
775 4
                $inClass = true;
776 4
            } elseif ($token[0] == T_FUNCTION) {
777 3
                if ($tokens[$i+2][0] == T_STRING) {
778 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+2][1]);
779
                } elseif ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) {
780 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+3][1]);
781
                }
782 4
            } elseif (in_array($token[0], [T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED]) && $tokens[$i+2][0] != T_FUNCTION) {
783 4
                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
784
            }
785
        }
786
    }
787
788
    /**
789
     * @param string $property
790
     *
791
     * @return bool
792
     */
793 35
    protected function hasProperty(string $property): bool
794
    {
795 35
        if ($this->classToExtend) {
796
            // don't generate property if its already on the base class.
797 2
            $reflClass = new \ReflectionClass($this->getClassToExtend());
798 2
            if ($reflClass->hasProperty($property)) {
799 2
                return true;
800
            }
801
        }
802
803
        // check traits for existing property
804 35
        foreach ($this->getTraitsReflections() as $trait) {
805
            if ($trait->hasProperty($property)) {
806
                return true;
807
            }
808
        }
809
810
        return (
811 35
            isset($this->staticReflection[$this->mapperInfo->className()]) &&
812 35
            in_array($property, $this->staticReflection[$this->mapperInfo->className()]['properties'])
813
        );
814
    }
815
816
    /**
817
     * @param string $method
818
     *
819
     * @return bool
820
     */
821 35
    protected function hasMethod(string $method): bool
822
    {
823 35
        if ($this->classToExtend || (!$this->isNew && class_exists($this->mapperInfo->className()))) {
824
            // don't generate method if its already on the base class.
825 6
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $this->mapperInfo->className());
826
827 6
            if ($reflClass->hasMethod($method)) {
828 6
                return true;
829
            }
830
        }
831
832
        // check traits for existing method
833 35
        foreach ($this->getTraitsReflections() as $trait) {
834
            if ($trait->hasMethod($method)) {
835
                return true;
836
            }
837
        }
838
839
        return (
840 35
            isset($this->staticReflection[$this->mapperInfo->className()]) &&
841 35
            in_array(strtolower($method), $this->staticReflection[$this->mapperInfo->className()]['methods'])
842
        );
843
    }
844
845
    /**
846
     * Get the class short name
847
     *
848
     * @param string $className
849
     *
850
     * @return string
851
     */
852 31
    protected function getClassName(string $className): string
853
    {
854 31
        $parts = explode('\\', $className);
855 31
        return array_pop($parts);
856
    }
857
858
    /**
859
     * @param string $className
860
     *
861
     * @return string
862
     */
863 35
    protected function getNamespace(string $className): string
864
    {
865 35
        $parts = explode('\\', $className);
866 35
        array_pop($parts);
867
868 35
        return implode('\\', $parts);
869
    }
870
871
    /**
872
     * @param string $className
873
     *
874
     * @return bool
875
     */
876 31
    protected function hasNamespace(string $className): bool
877
    {
878 31
        return strrpos($className, '\\') != 0;
879
    }
880
881
    /**
882
     * @return array<trait-string, \ReflectionClass>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<trait-string, \ReflectionClass> at position 2 could not be parsed: Unknown type name 'trait-string' at position 2 in array<trait-string, \ReflectionClass>.
Loading history...
883
     */
884 35
    protected function getTraitsReflections(): array
885
    {
886 35
        if ($this->isNew) {
887 31
            return [];
888
        }
889
890 4
        $reflClass = new \ReflectionClass($this->mapperInfo->className());
891
892 4
        $traits = [];
893
894 4
        while ($reflClass !== false) {
895 4
            $traits = array_merge($traits, $reflClass->getTraits());
896
897 4
            $reflClass = $reflClass->getParentClass();
898
        }
899
900 4
        return $traits;
901
    }
902
903
    //---------------------- mutators
904
905
    /**
906
     * Sets the number of spaces the exported class should have.
907
     *
908
     * @api
909
     */
910 2
    public function setNumSpaces(int $numSpaces): void
911
    {
912 2
        $this->numSpaces = $numSpaces;
913
    }
914
915
    /**
916
     * Gets the indentation spaces
917
     */
918 36
    public function getNumSpaces(): int
919
    {
920 36
        return $this->numSpaces;
921
    }
922
923
    /**
924
     * Sets the extension to use when writing php files to disk.
925
     *
926
     * @api
927
     */
928 1
    public function setExtension(string $extension): void
929
    {
930 1
        $this->extension = $extension;
931
    }
932
933
    /**
934
     * Get the file extension
935
     */
936 1
    public function getExtension(): string
937
    {
938 1
        return $this->extension;
939
    }
940
941
    /**
942
     * Sets the name of the class the generated classes should extend from.
943
     *
944
     * @api
945
     */
946 3
    public function setClassToExtend(string $classToExtend): void
947
    {
948 3
        $this->classToExtend = $classToExtend;
949
    }
950
951
    /**
952
     * Get the class to extend
953
     */
954 36
    public function getClassToExtend(): ?string
955
    {
956 36
        return $this->classToExtend;
957
    }
958
959
    /**
960
     * Add interface to implement
961
     *
962
     * @api
963
     *
964
     * @return void
965
     */
966 2
    public function addInterface(string $interface): void
967
    {
968 2
        $this->interfaces[$interface] = $interface;
969
    }
970
971
    /**
972
     * Sets the interfaces
973
     *
974
     * @param string[] $interfaces
975
     *
976
     * @api
977
     */
978 2
    public function setInterfaces(array $interfaces): void
979
    {
980 2
        $this->interfaces = $interfaces;
981
    }
982
983
    /**
984
     * Get the registered interfaces
985
     */
986 1
    public function getInterfaces(): array
987
    {
988 1
        return $this->interfaces;
989
    }
990
991
    /**
992
     * Add trait
993
     *
994
     * @api
995
     *
996
     * @return void
997
     */
998 2
    public function addTrait(string $trait): void
999
    {
1000 2
        $this->traits[$trait] = $trait;
1001
    }
1002
1003
    /**
1004
     * Sets the traits
1005
     *
1006
     * @param string[] $traits
1007
     *
1008
     * @api
1009
     */
1010 1
    public function setTraits(array $traits): void
1011
    {
1012 1
        $this->traits = $traits;
1013
    }
1014
1015
    /**
1016
     * Get the registered traits
1017
     */
1018 1
    public function getTraits(): array
1019
    {
1020 1
        return $this->traits;
1021
    }
1022
1023
    /**
1024
     * Sets the class fields visibility for the entity (can either be private or protected).
1025
     *
1026
     * @throws \InvalidArgumentException
1027
     *
1028
     * @api
1029
     */
1030 2
    public function setFieldVisibility(string $visibility): void
1031
    {
1032 2
        if ($visibility !== static::FIELD_VISIBLE_PRIVATE && $visibility !== static::FIELD_VISIBLE_PROTECTED) {
1033
            throw new \InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility);
1034
        }
1035
1036 2
        $this->fieldVisibility = $visibility;
0 ignored issues
show
Documentation Bug introduced by
It seems like $visibility of type string is incompatible with the declared type Bdf\Prime\Entity\EntityGenerator of property $fieldVisibility.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1037
    }
1038
1039
    /**
1040
     * Get the field visibility
1041
     */
1042 1
    public function getFieldVisibility(): string
1043
    {
1044 1
        return $this->fieldVisibility;
1045
    }
1046
1047
    /**
1048
     * Sets whether or not to try and update the entity if it already exists.
1049
     *
1050
     * @api
1051
     */
1052 5
    public function setUpdateEntityIfExists(bool $bool): void
1053
    {
1054 5
        $this->updateEntityIfExists = $bool;
1055
    }
1056
1057
    /**
1058
     * Get the flag for updating the entity
1059
     */
1060 1
    public function getUpdateEntityIfExists(): bool
1061
    {
1062 1
        return $this->updateEntityIfExists;
1063
    }
1064
1065
    /**
1066
     * Sets whether or not to regenerate the entity if it exists.
1067
     *
1068
     * @api
1069
     */
1070 1
    public function setRegenerateEntityIfExists(bool $bool): void
1071
    {
1072 1
        $this->regenerateEntityIfExists = $bool;
1073
    }
1074
1075
    /**
1076
     * Get the flag for regenerating entity
1077
     */
1078 1
    public function getRegenerateEntityIfExists(): bool
1079
    {
1080 1
        return $this->regenerateEntityIfExists;
1081
    }
1082
1083
    /**
1084
     * Sets whether or not to generate stub methods for the entity.
1085
     *
1086
     * @api
1087
     */
1088 2
    public function setGenerateStubMethods(bool $bool): void
1089
    {
1090 2
        $this->generateEntityStubMethods = $bool;
1091
    }
1092
1093
    /**
1094
     * Get the flag for generating stub methods
1095
     */
1096 1
    public function getGenerateStubMethods(): bool
1097
    {
1098 1
        return $this->generateEntityStubMethods;
1099
    }
1100
1101
    /**
1102
     * Sets whether or not the get mehtod will be suffixed by 'get'.
1103
     *
1104
     * @param bool $flag
1105
     *
1106
     * @return void
1107
     *
1108
     * @api
1109
     */
1110 2
    public function useGetShortcutMethod(bool $flag = true)
1111
    {
1112 2
        $this->useGetShortcutMethod = $flag;
1113
    }
1114
1115
    /**
1116
     * Get the flag for get mehtod name.
1117
     */
1118 1
    public function getUseGetShortcutMethod(): bool
1119
    {
1120 1
        return $this->useGetShortcutMethod;
1121
    }
1122
1123
    /**
1124
     * @return bool
1125
     */
1126
    public function getUseTypedProperties(): bool
1127
    {
1128
        return $this->useTypedProperties;
1129
    }
1130
1131
    /**
1132
     * Enable usage of php 7.4 type properties
1133
     *
1134
     * @param bool $useTypedProperties
1135
     */
1136 15
    public function useTypedProperties(bool $useTypedProperties = true): void
1137
    {
1138 15
        $this->useTypedProperties = $useTypedProperties;
1139
    }
1140
1141
    /**
1142
     * @return bool
1143
     */
1144
    public function getUseConstructorPropertyPromotion(): bool
1145
    {
1146
        return $this->useConstructorPropertyPromotion;
1147
    }
1148
1149
    /**
1150
     * Enable usage of PHP 8 promoted properties on constructor instead of array import
1151
     *
1152
     * @param bool $useConstructorPropertyPromotion
1153
     */
1154 2
    public function useConstructorPropertyPromotion(bool $useConstructorPropertyPromotion = true): void
1155
    {
1156 2
        $this->useConstructorPropertyPromotion = $useConstructorPropertyPromotion;
1157
    }
1158
}
1159
1160
/**
1161
 * @internal
1162
 */
1163
class PropertyGenerator
1164
{
1165
    private string $name;
1166
    private ?string $typeHint = null;
1167
    private bool $nullable = false;
1168
    private ?string $varTag = null;
1169
    private string $visibility = EntityGenerator::FIELD_VISIBLE_PROTECTED;
1170
    private $defaultValue;
1171
    private bool $hasDefaultValue = false;
1172
    private ?string $initialize = null;
1173
1174
    /**
1175
     * @param string $name
1176
     */
1177 33
    public function __construct(string $name)
1178
    {
1179 33
        $this->name = $name;
1180
    }
1181
1182 15
    public function setTypeHint(string $typeHint): void
1183
    {
1184 15
        $this->typeHint = $typeHint;
1185
    }
1186
1187 33
    public function setNullable(bool $nullable): void
1188
    {
1189 33
        $this->nullable = $nullable;
1190
    }
1191
1192 33
    public function setVarTag(string $varTag): void
1193
    {
1194 33
        $this->varTag = $varTag;
1195
    }
1196
1197 33
    public function setVisibility(string $visibility): void
1198
    {
1199 33
        $this->visibility = $visibility;
1200
    }
1201
1202 29
    public function setDefaultValue($value): void
1203
    {
1204 29
        $this->defaultValue = $value;
1205 29
        $this->hasDefaultValue = true;
1206
    }
1207
1208 29
    public function setInitialize(string $initialize): void
1209
    {
1210 29
        $this->initialize = $initialize;
1211
    }
1212
1213 25
    public function hasInitialisation(): bool
1214
    {
1215 25
        return $this->initialize !== null;
1216
    }
1217
1218 31
    public function addProperty(ClassType $generator, ?PhpNamespace $namespace): void
1219
    {
1220 31
        $property = $generator->addProperty($this->name);
1221
1222
        $property
1223 31
            ->setNullable($this->nullable)
1224 31
            ->setVisibility($this->visibility)
1225
        ;
1226
1227 31
        if ($this->typeHint) {
1228 13
            $typehint = EntityGenerator::PROPERTY_TYPE_MAP[$this->typeHint] ?? $this->typeHint;
1229 13
            $property->setType($typehint);
1230
        }
1231
1232 31
        if ($this->hasDefaultValue) {
1233 27
            $property->setValue($this->defaultValue);
1234
        }
1235
1236 31
        if ($this->varTag) {
1237 31
            $type = $this->varTag;
1238
1239 31
            if (!isset(EntityGenerator::PROPERTY_TYPE_MAP[$this->varTag]) && $namespace) {
1240 31
                $type = $this->simplifyType($type, $namespace);
1241
            }
1242
1243 31
            $property->addComment("\n@var $type");
1244
        }
1245
    }
1246
1247 2
    public function addPromotedProperty(Method $constructor, ?PhpNamespace $namespace): void
1248
    {
1249 2
        $parameter = $constructor->addPromotedParameter($this->name);
1250
1251
        $parameter
1252 2
            ->setNullable($this->nullable)
1253 2
            ->setVisibility($this->visibility)
1254
        ;
1255
1256 2
        if ($this->typeHint) {
1257 2
            $typehint = EntityGenerator::PROPERTY_TYPE_MAP[$this->typeHint] ?? $this->typeHint;
1258 2
            $parameter->setType($typehint);
1259
        }
1260
1261 2
        if ($this->hasDefaultValue) {
1262 2
            $parameter->setDefaultValue($this->defaultValue);
1263
        }
1264
1265 2
        if ($this->varTag) {
1266 2
            $type = $this->varTag;
1267
1268 2
            if (!isset(EntityGenerator::PROPERTY_TYPE_MAP[$this->varTag]) && $namespace) {
1269 2
                $type = $this->simplifyType($type, $namespace);
1270
            }
1271
1272 2
            $parameter->addComment("\n@var $type");
1273
        }
1274
    }
1275
1276 27
    public function addInitializeLine(Method $initializeMethod, string $assignationOperator = '='): void
1277
    {
1278 27
        if ($this->initialize) {
1279 27
            $initializeMethod->addBody('$this->'.$this->name.' '.$assignationOperator.' '.$this->initialize.';');
1280
        }
1281
    }
1282
1283 33
    private function simplifyType(string $type, PhpNamespace $namespace): string
1284
    {
1285 33
        $types = explode('|', $type);
1286
1287 33
        foreach ($types as &$part) {
1288 33
            $atomicType = $part;
1289 33
            $isArray = false;
1290
1291 33
            if (str_ends_with($atomicType, '[]')) {
1292 19
                $atomicType = substr($atomicType, 0, -2);
1293 19
                $isArray = true;
1294
            }
1295
1296 33
            if (isset(EntityGenerator::PROPERTY_TYPE_MAP[$atomicType])) {
1297
                continue;
1298
            }
1299
1300 33
            $part = $namespace->simplifyName($atomicType) . ($isArray ? '[]' : '');
1301
        }
1302
1303 33
        return implode('|', $types);
1304
    }
1305
}
1306
1307
/**
1308
 * @internal
1309
 */
1310
class ConfigurableEntityPrinter extends Printer
1311
{
1312 35
    public function __construct(EntityGenerator $generator)
1313
    {
1314 35
        parent::__construct();
1315
1316 35
        $this->linesBetweenMethods = 1;
1317 35
        $this->linesBetweenProperties = 1;
1318 35
        $this->indentation = str_repeat(' ', $generator->getNumSpaces());
1319
    }
1320
1321 35
    public function printClass($class, ?PhpNamespace $namespace = null): string
1322
    {
1323 35
        $code = parent::printClass($class, $namespace);
1324
1325
        // Reformat property docblock : nette will generate property doc on single line
1326 35
        return preg_replace('#^( *)/\*\*(.*)\s+\*/$#m', "$1/**\n$1 *$2\n$1 */", $code);
1327
    }
1328
}
1329