Passed
Push — feature-FRAM-58-refactor-entit... ( fc48d1...1bdb0a )
by Vincent
05:54
created

EntityClassGenerator::hasProperty()   B

Complexity

Conditions 8
Paths 12

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 8.1867

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 13
c 1
b 0
f 0
dl 0
loc 27
ccs 12
cts 14
cp 0.8571
rs 8.4444
cc 8
nc 12
nop 1
crap 8.1867
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
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...
103
     */
104
    private ?string $classToExtend = null;
105
106
    /**
107
     * The interfaces all generated entities should implement.
108
     *
109
     * @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...
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());
0 ignored issues
show
Bug introduced by
It seems like $class->getName() can also be of type null; however, parameter $val of Nette\PhpGenerator\ClassLike::addComment() 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

279
        $class->addComment(/** @scrutinizer ignore-type */ $class->getName());
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $className can also be of type null; however, parameter $class of Bdf\Prime\Entity\EntityClassGenerator::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

315
            $generator->addUse(/** @scrutinizer ignore-type */ $className);
Loading history...
316
317 26
            if ($info->wrapper() !== null) {
318 1
                $repository = $this->prime->repository($className);
319 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

319
                $wrapperClass = $repository->collectionFactory()->wrapperClass(/** @scrutinizer ignore-type */ $info->wrapper());
Loading history...
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
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 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;
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...
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());
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

536
                $generator->setVarTag(/** @scrutinizer ignore-type */ $property->className());
Loading history...
537 4
                $generator->setInitialize('new '.$class->simplifyType($property->className()).'()');
0 ignored issues
show
Bug introduced by
It seems like $property->className() can also be of type null; however, parameter $type of Bdf\Prime\Entity\EntityC...nerator::simplifyType() 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

537
                $generator->setInitialize('new '.$class->simplifyType(/** @scrutinizer ignore-type */ $property->className()).'()');
Loading history...
538
539 4
                if ($this->useTypedProperties) {
540 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

540
                    $generator->setTypeHint(/** @scrutinizer ignore-type */ $property->className());
Loading history...
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() . '[]');
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

562
                    $generator->setVarTag($repository->collectionFactory()->wrapperClass(/** @scrutinizer ignore-type */ $property->wrapper()) . '|' . $property->className() . '[]');
Loading history...
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;
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...
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());
0 ignored issues
show
Bug introduced by
It seems like $propertyInfo->className() can also be of type null; however, parameter $type of Bdf\Prime\Entity\EntityC...nerator::simplifyType() 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

632
            $variableType = $generator->simplifyType(/** @scrutinizer ignore-type */ $propertyInfo->className());
Loading history...
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) {
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

644
            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...
645
                /** @var ObjectPropertyInfo $propertyInfo */
646 1
                $repository = $this->prime->repository($propertyInfo->className());
647
648 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

648
                $methodTypeHint = $repository->collectionFactory()->wrapperClass(/** @scrutinizer ignore-type */ $propertyInfo->wrapper());
Loading history...
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;
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...
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