Passed
Push — feature-FRAM-29-php-8-entity-g... ( 102654 )
by Vincent
21:47
created

EntityGenerator::getClassToExtend()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Bdf\Prime\Entity;
21
22
use Bdf\Prime\Mapper\Info\InfoInterface;
23
use Bdf\Prime\Mapper\Info\ObjectPropertyInfo;
24
use Bdf\Prime\Mapper\Info\PropertyInfo;
25
use Bdf\Prime\Mapper\Mapper;
26
use Bdf\Prime\Mapper\Info\MapperInfo;
27
use Bdf\Prime\ServiceLocator;
28
use Bdf\Prime\Types\PhpTypeInterface;
29
use Bdf\Prime\Types\TypeInterface;
30
use Doctrine\Common\Inflector\Inflector;
31
use Doctrine\Inflector\Inflector as InflectorObject;
32
use Doctrine\Inflector\InflectorFactory;
33
34
/**
35
 * Generic class used to generate PHP5 entity classes from Mapper.
36
 *
37
 *     [php]
38
 *     $mapper = $service->mappers()->build('Entity);
39
 *
40
 *     $generator = new EntityGenerator();
41
 *     $generator->setGenerateStubMethods(true);
42
 *     $generator->setRegenerateEntityIfExists(false);
43
 *     $generator->setUpdateEntityIfExists(true);
44
 *     $generator->generate($mapper, '/path/to/generate/entities');
45
 *
46
 *
47
 * @link    www.doctrine-project.org
48
 * @since   2.0
49
 * @author  Benjamin Eberlei <[email protected]>
50
 * @author  Guilherme Blanco <[email protected]>
51
 * @author  Jonathan Wage <[email protected]>
52
 * @author  Roman Borschel <[email protected]>
53
 */
54
class EntityGenerator
55
{
56
    // @todo should not be there : should be on PhpTypeInterface
57
    /**
58
     * Map prime types to php 7.4 property type
59
     */
60
    public const PROPERTY_TYPE_MAP = [
61
        PhpTypeInterface::BOOLEAN => 'bool',
62
        PhpTypeInterface::DOUBLE => 'float',
63
        PhpTypeInterface::INTEGER => 'int',
64
    ];
65
66
    /**
67
     * Specifies class fields should be protected.
68
     */
69
    public const FIELD_VISIBLE_PROTECTED = 'protected';
70
71
    /**
72
     * Specifies class fields should be private.
73
     */
74
    public const FIELD_VISIBLE_PRIVATE = 'private';
75
76
    /**
77
     * The prime service locator
78
     *
79
     * @var ServiceLocator
80
     */
81
    private $prime;
82
83
    /**
84
     * The inflector instance
85
     *
86
     * @var InflectorObject
87
     */
88
    private $inflector;
89
90
    /**
91
     * The mapper info
92
     *
93
     * @var MapperInfo
94
     */
95
    private $mapperInfo;
96
97
    /**
98
     * The extension to use for written php files.
99
     *
100
     * @var string
101
     */
102
    private $extension = '.php';
103
104
    /**
105
     * Whether or not the current Mapper instance is new or old.
106
     *
107
     * @var boolean
108
     */
109
    private $isNew = true;
110
111
    /**
112
     * @var array
113
     */
114
    private $staticReflection = [];
115
116
    /**
117
     * Number of spaces to use for indention in generated code.
118
     */
119
    private $numSpaces = 4;
120
121
    /**
122
     * The actual spaces to use for indention.
123
     *
124
     * @var string
125
     */
126
    private $spaces = '    ';
127
128
    /**
129
     * The class all generated entities should extend.
130
     *
131
     * @var string
132
     */
133
    private $classToExtend;
134
135
    /**
136
     * The interfaces all generated entities should implement.
137
     *
138
     * @var array
139
     */
140
    private $interfaces = [];
141
142
    /**
143
     * The traits
144
     *
145
     * @var array
146
     */
147
    private $traits = [];
148
149
    /**
150
     * Whether or not to generate sub methods.
151
     *
152
     * @var boolean
153
     */
154
    private $generateEntityStubMethods = true;
155
156
    /**
157
     * Whether or not to update the entity class if it exists already.
158
     *
159
     * @var boolean
160
     */
161
    private $updateEntityIfExists = false;
162
163
    /**
164
     * Whether or not to re-generate entity class if it exists already.
165
     *
166
     * @var boolean
167
     */
168
    private $regenerateEntityIfExists = false;
169
170
    /**
171
     * The name of get methods will not contains the 'get' prefix
172
     *
173
     * @var boolean
174
     */
175
    private $useGetShortcutMethod = true;
176
177
    /**
178
     * Visibility of the field
179
     *
180
     * @var string
181
     */
182
    private $fieldVisibility = self::FIELD_VISIBLE_PROTECTED;
183
184
    /**
185
     * Use type on generated properties
186
     * Note: only compatible with PHP >= 7.4
187
     *
188
     * @var bool
189
     */
190
    private $useTypedProperties = false;
191
192
    /**
193
     * Enable generation of PHP 8 constructor with promoted properties
194
     * If used, the constructor will not call import for filling the entity
195
     *
196
     * Note: only compatible with PHP >= 8.0
197
     *
198
     * @var bool
199
     */
200
    private $useConstructorPropertyPromotion = false;
201
202
    /**
203
     * @var string
204
     */
205
    private static $classTemplate =
206
'<?php
207
208
<namespace><useStatement><entityAnnotation>
209
<entityClassName>
210
{
211
<entityTraits><entityBody>
212
}
213
';
214
215
    /**
216
     * @var string
217
     */
218
    private static $getMethodTemplate =
219
'/**
220
 * <description>
221
 *
222
 * @return <variableType>
223
 */
224
public function <methodName>()
225
{
226
<spaces>return $this-><fieldName>;
227
}
228
';
229
230
    /**
231
     * @var string
232
     */
233
    private static $setMethodTemplate =
234
'/**
235
 * <description>
236
 *
237
 * @param <variableType> $<variableName>
238
 *
239
 * @return $this
240
 */
241
public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
242
{
243
<spaces>$this-><fieldName> = $<variableName>;
244
245
<spaces>return $this;
246
}
247
';
248
249
    /**
250
     * @var string
251
     */
252
    private static $addMethodTemplate =
253
'/**
254
 * <description>
255
 *
256
 * @param <variableType> $<variableName>
257
 *
258
 * @return $this
259
 */
260
public function <methodName>(<methodTypeHint>$<variableName>)
261
{
262
<spaces>$this-><fieldName>[] = $<variableName>;
263
264
<spaces>return $this;
265
}
266
';
267
268
    /**
269
     * @var string
270
     */
271
    private static $methodTemplate =
272
'/**
273
 * <description>
274
 */
275
public function <methodName>()<return>
276
{
277
<spaces><content>
278
}
279
';
280
281
    /**
282
     * @var string
283
     */
284
    private static $constructorMethodTemplate =
285
'/**
286
 * Constructor
287
 */
288
public function __construct()
289
{
290
<spaces><collections>
291
}
292
';
293
294
    /**
295
     * @var string
296
     */
297
    private static $importableConstructorMethodTemplate =
298
'/**
299
 * Constructor
300
 *
301
 * @param array $data
302
 */
303
public function __construct(array $data = [])
304
{
305
<spaces><initialize>$this->import($data);
306
}
307
';
308
309
    /**
310
     * @var string
311
     */
312
    private static $constructorWithPromotedPropertiesMethodTemplate =
313
'public function __construct(
314
<properties>
315
) {
316
<spaces><body>
317
}
318
';
319
320
    /**
321
     * Set prime service locator
322
     */
323 31
    public function __construct(ServiceLocator $prime, ?InflectorObject $inflector = null)
324
    {
325 31
        $this->prime = $prime;
326 31
        $this->inflector = $inflector ?? InflectorFactory::create()->build();
327
    }
328
329
    /**
330
     * Generates and writes entity classes
331
     *
332
     * @param Mapper $mapper
333
     * @param string $file    Entity file name
334
     *
335
     * @return string|false If no generation
336
     *
337
     * @api
338
     */
339 30
    public function generate($mapper, $file = null)
340
    {
341 30
        $this->isNew = !$file || !file_exists($file) || $this->regenerateEntityIfExists;
342
343
        // If entity doesn't exist or we're re-generating the entities entirely
344 30
        if ($this->isNew || !$file) {
345 30
            return $this->generateEntityClass($mapper);
346
        // If entity exists and we're allowed to update the entity class
347
        } elseif ($this->updateEntityIfExists && $file) {
348
            return $this->generateUpdatedEntityClass($mapper, $file);
349
        }
350
351
        return false;
352
    }
353
354
    /**
355
     * Generates a PHP5 Doctrine 2 entity class from the given Mapper instance.
356
     *
357
     * @param Mapper $mapper
358
     *
359
     * @return string
360
     */
361 30
    public function generateEntityClass(Mapper $mapper)
362
    {
363 30
        $this->mapperInfo = $mapper->info();
364
365 30
        $this->staticReflection[$this->mapperInfo->className()] = ['properties' => [], 'methods' => []];
366
367
        $placeHolders = [
368
            '<namespace>',
369
            '<useStatement>',
370
            '<entityAnnotation>',
371
            '<entityClassName>',
372
            '<entityTraits>',
373
            '<entityBody>'
374
        ];
375
376
        $replacements = [
377 30
            $this->generateEntityNamespace(),
378 30
            $this->generateEntityUse(),
379 30
            $this->generateEntityDocBlock(),
380 30
            $this->generateEntityClassName(),
381 30
            $this->generateEntityTraits(),
382 30
            $this->generateEntityBody()
383
        ];
384
385 30
        $code = str_replace($placeHolders, $replacements, static::$classTemplate);
0 ignored issues
show
Bug introduced by
Since $classTemplate is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $classTemplate to at least protected.
Loading history...
386
387 30
        return str_replace('<spaces>', $this->spaces, $code);
388
    }
389
390
    /**
391
     * Generates the updated code for the given Mapper and entity at path.
392
     *
393
     * @param Mapper $mapper
394
     * @param string $file
395
     *
396
     * @return string
397
     */
398
    public function generateUpdatedEntityClass(Mapper $mapper, $file)
399
    {
400
        $this->mapperInfo = $mapper->info();
401
402
        $currentCode = file_get_contents($file);
403
404
        $this->parseTokensInEntityFile($currentCode);
405
406
        $body = $this->generateEntityBody();
407
        $body = str_replace('<spaces>', $this->spaces, $body);
408
        $last = strrpos($currentCode, '}');
409
410
        return substr($currentCode, 0, $last) . $body . (strlen($body) > 0 ? "\n" : '') . "}\n";
411
    }
412
413
    /**
414
     * @return string
415
     */
416 30
    protected function generateEntityNamespace(): string
417
    {
418 30
        if ($this->hasNamespace($this->mapperInfo->className())) {
419 30
            return 'namespace ' . $this->getNamespace($this->mapperInfo->className()) .';' . "\n\n";
420
        }
421
422
        return '';
423
    }
424
425
    /**
426
     * Generate use part
427
     *
428
     * @return string
429
     */
430 30
    protected function generateEntityUse()
431
    {
432 30
        $use = [];
433
434 30
        if (($parentClass = $this->getClassToExtend()) && $this->hasNamespace($parentClass)) {
435 2
            $use[$parentClass] = 'use ' . $parentClass . ';';
436
        }
437
438 30
        foreach ($this->interfaces as $interface) {
439 3
            if ($this->hasNamespace($interface)) {
440 3
                $use[$interface] = 'use ' . $interface . ';';
441
            }
442
        }
443
444 30
        foreach ($this->traits as $trait) {
445 2
            if ($this->hasNamespace($trait)) {
446
                $use[$trait] = 'use ' . $trait . ';';
447
            }
448
        }
449
450 30
        foreach ($this->mapperInfo->objects() as $info) {
451 26
            $className = $info->className();
452 26
            if (!$info->belongsToRoot()) {
453 4
                continue;
454
            }
455
456 26
            if ($this->hasNamespace($className)) {
457 26
                $use[$className] = 'use '.$className.';';
458
            }
459
460 26
            if ($info->wrapper() !== null) {
461 1
                $repository = $this->prime->repository($className);
462 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

462
                $wrapperClass = $repository->collectionFactory()->wrapperClass(/** @scrutinizer ignore-type */ $info->wrapper());
Loading history...
463
464 1
                if ($this->hasNamespace($wrapperClass)) {
465 1
                    $use[$wrapperClass] = 'use '.$wrapperClass.';';
466
                }
467
            }
468
        }
469
470 30
        if (!$use) {
471 5
            return '';
472
        }
473
474 26
        sort($use);
475
476 26
        return implode("\n", $use) . "\n\n";
477
    }
478
479
    /**
480
     * @return string
481
     */
482 30
    protected function generateEntityClassName()
483
    {
484 30
        return 'class ' . $this->getClassName($this->mapperInfo->className()) .
485 30
            ($this->classToExtend ? ' extends ' . $this->getClassToExtendName() : null) .
486 30
            ($this->interfaces ? ' implements ' . $this->getInterfacesToImplement() : null);
487
    }
488
489
    /**
490
     * @return string
491
     */
492 30
    protected function generateEntityTraits()
493
    {
494 30
        if (!$this->traits) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->traits of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
495 28
            return '';
496
        }
497
498 2
        $traits = '';
499
500 2
        foreach ($this->traits as $trait) {
501 2
            $traits .= $this->spaces . 'use ' . $this->getRelativeClassName($trait) . ';' . "\n";
502
        }
503
504 2
        return $traits . "\n";
505
    }
506
507
    /**
508
     * @return string
509
     */
510 30
    protected function generateEntityBody()
511
    {
512 30
        $fieldMappingProperties = $this->generateEntityFieldMappingProperties($this->useConstructorPropertyPromotion);
513 30
        $embeddedProperties = $this->generateEntityEmbeddedProperties($this->useConstructorPropertyPromotion);
514 30
        $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods() : null;
515
516 30
        $code = [];
517
518 30
        if (!$this->useConstructorPropertyPromotion) {
519 28
            if ($fieldMappingProperties) {
520 28
                $code[] = $fieldMappingProperties;
521
            }
522
523 28
            if ($embeddedProperties) {
524 25
                $code[] = $embeddedProperties;
525
            }
526
        }
527
528 30
        $code[] = $this->generateEntityConstructor(
529 30
            $this->useConstructorPropertyPromotion,
530
            $fieldMappingProperties,
531
            $embeddedProperties
532
        );
533
534 30
        if ($stubMethods) {
535 29
            $code[] = $stubMethods;
536
        }
537
538 30
        return implode("\n", $code);
539
    }
540
541
    /**
542
     * @param bool $propertyPromotion Generate constructor with property promotion
543
     * @param string $fieldMappingProperties Entity properties
544
     * @param string $embeddedProperties Relations and embedded properties
545
     *
546
     * @return string
547
     */
548 30
    protected function generateEntityConstructor(bool $propertyPromotion, string $fieldMappingProperties, string $embeddedProperties)
549
    {
550 30
        $initializable = in_array(InitializableInterface::class, $this->interfaces);
551 30
        $isImportable  = in_array(ImportableInterface::class, $this->interfaces)
552 30
                    || is_subclass_of($this->classToExtend, ImportableInterface::class);
553
554 30
        $collections = [];
555
556
        // Assignment operator : use null coalesce assignment with property promotion
557
        // because assignation is performed before initializing default value
558 30
        $assign = $propertyPromotion ? '??=' : '=';
559
560 30
        foreach ($this->mapperInfo->objects() as $property) {
561 26
            if (!$property->belongsToRoot()) {
562 4
                continue;
563
            }
564
565 26
            if ($property->isRelation()) {
566 25
                if (!$property->isArray()) {
567 25
                    $collections[$property->name()] = '$this->'.$property->name().' '.$assign.' new '.$this->getRelativeClassName($property->className()).'();';
568 21
                } elseif ($property->wrapper() === 'collection') { // @todo handle other wrapper types
569 25
                    $collections[$property->name()] = '$this->'.$property->name().' '.$assign.' '.$this->getRelativeClassName($property->className()).'::collection();';
570
                }
571
            } else {
572 4
                $collections[$property->name()] = '$this->'.$property->name().' '.$assign.' new '.$this->getRelativeClassName($property->className()).'();';
573
            }
574
        }
575 30
        foreach ($this->mapperInfo->properties() as $property) {
576 30
            if ($property->isDateTime() && $property->hasDefault()) {
577 4
                $constructorArgs = '';
578
                // Add the default timezone from the property type.
579 4
                if ($timezone = $property->getTimezone()) {
580 4
                    $constructorArgs = "'now', new \DateTimeZone('$timezone')";
581
                }
582
583 4
                $collections[$property->name()] = '$this->'.$property->name().' '.$assign.' new '.$property->phpType().'('.$constructorArgs.');';
584
            }
585
        }
586
587 30
        $methods = [];
588
589 30
        if (!$this->hasMethod('__construct')) {
590 28
            if ($propertyPromotion) {
591 2
                $methods[] = $this->generateConstructorWithPromotedProperties($initializable, $collections, $fieldMappingProperties, $embeddedProperties);
592 26
            } elseif ($constructor = $this->generateClassicConstructor($isImportable, $initializable, $collections)) {
593 25
                $methods[] = $constructor;
594
            }
595
        }
596
597 30
        if (!$this->hasMethod('initialize') && $initializable) {
598 1
            $methods[] = $this->generateMethod('{@inheritdoc}', 'initialize', implode("\n".$this->spaces, $collections), 'void');
599
        }
600
601 30
        return implode("\n", $methods);
602
    }
603
604
    /**
605
     * Generate PHP 8 constructor
606
     *
607
     * @param bool $initializable Does the entity class implements InitializableInterface ?
608
     * @param string[] $collections Initialisation method instructions
609
     * @param string $fieldMappingProperties Entity properties
610
     * @param string $embeddedProperties Relations and embedded properties
611
     *
612
     * @return string
613
     */
614 2
    private function generateConstructorWithPromotedProperties(bool $initializable, array $collections, string $fieldMappingProperties, string $embeddedProperties): string
615
    {
616 2
        if ($initializable) {
617
            $buffer = '$this->initialize();'."\n".$this->spaces;
618 2
        } elseif ($collections) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $collections of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
619 2
            $buffer = implode("\n".$this->spaces, $collections)."\n".$this->spaces;
620
        } else {
621
            $buffer = '';
622
        }
623
624 2
        $properties = rtrim($fieldMappingProperties."\n".$embeddedProperties);
625 2
        $properties = str_replace(';', ',', $properties);
626
627 2
        return $this->prefixCodeWithSpaces(str_replace(
628 2
            ['<body>', '<properties>'],
629 2
            [rtrim($buffer), $properties],
630 2
            static::$constructorWithPromotedPropertiesMethodTemplate
0 ignored issues
show
Bug introduced by
Since $constructorWithPromotedPropertiesMethodTemplate is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $constructorWithPromotedPropertiesMethodTemplate to at least protected.
Loading history...
631
        ));
632
    }
633
634
    /**
635
     * Generate classic constructor
636
     *
637
     * @param bool $isImportable Does the entity class implements InitializableInterface ?
638
     * @param bool $initializable Does the entity class implements ImportableInterface ?
639
     * @param string[] $collections Initialisation method instructions
640
     *
641
     * @return string|null
642
     */
643 26
    private function generateClassicConstructor(bool $isImportable, bool $initializable, array $collections): ?string
644
    {
645 26
        if ($isImportable) {
646 2
            $buffer = '';
647
648 2
            if ($initializable) {
649 1
                $buffer = '$this->initialize();'."\n".$this->spaces;
650 1
            } elseif ($collections) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $collections of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
651 1
                $buffer = implode("\n".$this->spaces, $collections)."\n".$this->spaces;
652
            }
653
654 2
            return $this->prefixCodeWithSpaces(str_replace("<initialize>", $buffer, static::$importableConstructorMethodTemplate));
0 ignored issues
show
Bug introduced by
Since $importableConstructorMethodTemplate is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $importableConstructorMethodTemplate to at least protected.
Loading history...
655 24
        } elseif ($collections && !$initializable) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $collections of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
656 23
            return $this->prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->spaces, $collections), static::$constructorMethodTemplate));
0 ignored issues
show
Bug introduced by
Since $constructorMethodTemplate is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $constructorMethodTemplate to at least protected.
Loading history...
657
        }
658
659 2
        return null;
660
    }
661
662
    /**
663
     * @param Mapper $mapper
664
     *
665
     * @return string
666
     */
667 30
    protected function generateEntityDocBlock()
668
    {
669 30
        $lines = [];
670 30
        $lines[] = '/**';
671 30
        $lines[] = ' * ' . $this->getClassName($this->mapperInfo->className());
672 30
        $lines[] = ' */';
673
674 30
        return implode("\n", $lines);
675
    }
676
677
    /**
678
     * @return string
679
     */
680 29
    protected function generateEntityStubMethods()
681
    {
682 29
        $methods = [];
683
684 29
        foreach ($this->mapperInfo->properties() as $property) {
685 29
            if ($code = $this->generateEntityStubMethod('set', $property)) {
686 29
                $methods[] = $code;
687
            }
688
689 29
            if ($code = $this->generateEntityStubMethod('get', $property)) {
690 29
                $methods[] = $code;
691
            }
692
        }
693
694 29
        foreach ($this->mapperInfo->objects() as $property) {
695 25
            if (!$property->belongsToRoot()) {
696 4
                continue;
697
            }
698
699 25
            if (!$property->isArray() || $property->wrapper() !== null) {
700 25
                if ($code = $this->generateEntityStubMethod('set', $property)) {
701 25
                    $methods[] = $code;
702
                }
703 25
                if ($code = $this->generateEntityStubMethod('get', $property)) {
704 25
                    $methods[] = $code;
705
                }
706
            } else {
707 19
                if ($code = $this->generateEntityStubMethod('add', $property)) {
708 19
                    $methods[] = $code;
709
                }
710 19
                if ($code = $this->generateEntityStubMethod('set', $property)) {
711 19
                    $methods[] = $code;
712
                }
713 19
                if ($code = $this->generateEntityStubMethod('get', $property)) {
714 19
                    $methods[] = $code;
715
                }
716
            }
717
        }
718
719 29
        return implode("\n", $methods);
720
    }
721
722
    /**
723
     * @return string
724
     */
725 30
    protected function generateEntityFieldMappingProperties(bool $forceNullable = false)
726
    {
727 30
        $lines = [];
728
729 30
        foreach ($this->mapperInfo->properties() as  $property) {
730 30
            if ($this->hasProperty($property->name())) {
731 2
                continue;
732
            }
733
734 30
            $default = '';
735
736 30
            if ($property->hasDefault() && !$property->isDateTime()) {
737 5
                $default = ' = '.$this->stringfyValue(
738 5
                    $property->convert($property->getDefault())
739
                );
740 30
            } elseif ($property->isArray()) {
741 17
                $default = ' = []';
742
            }
743
744
            // A nullable property should be defined as null by default
745
            // A property is considered as nullable if it's explicitly defined on mapper or if the field is auto-generated
746 30
            if (!$default && ($forceNullable || ($this->useTypedProperties && $property->isNullable()))) {
747 13
                $default = ' = null';
748
            }
749
750 30
            $lines[] = $this->generateFieldMappingPropertyDocBlock($property);
751 30
            $lines[] = $this->spaces.$this->fieldVisibility.$this->getPropertyTypeHintForSimpleProperty($property, $forceNullable).' $'.$property->name().$default.";\n";
752
        }
753
754 30
        return implode("\n", $lines);
755
    }
756
757
    /**
758
     * @param bool $forceNullable Force typehint to be nullable. Useful property promotion
759
     * @return string
760
     */
761 30
    protected function generateEntityEmbeddedProperties(bool $forceNullable = false)
762
    {
763 30
        $lines = [];
764
765 30
        foreach ($this->mapperInfo->objects() as $property) {
766 26
            if (!$property->belongsToRoot() || $this->hasProperty($property->name())) {
767 6
                continue;
768
            }
769
770 26
            if (!$property->isRelation()) {
771 4
                $lines[] = $this->generateEmbeddedPropertyDocBlock($property);
772 4
                $lines[] = $this->spaces . $this->fieldVisibility . $this->getPropertyTypeHintForObject($property, $forceNullable) . ' $'.$property->name().";\n";
773
            } else {
774 25
                $name = $property->name();
775 25
                $default = '';
776
777
                // Do not initialize the property if it's a wrapper
778 25
                if ($property->isArray() && $property->wrapper() === null) {
779 18
                    $default = ' = []';
780
                }
781
782
                // If property is typed, always define a default value
783 25
                if (($forceNullable || $this->useTypedProperties) && !$default) {
784 12
                    $default = ' = null';
785
                }
786
787 25
                $lines[] = $this->generateEmbeddedPropertyDocBlock($property);
788 25
                $lines[] = $this->spaces . $this->fieldVisibility . $this->getPropertyTypeHintForObject($property, $forceNullable) . ' $' . $name . $default .";\n";
789
            }
790
        }
791
792 30
        return implode("\n", $lines);
793
    }
794
795
    /**
796
     * @param string            $type
797
     * @param InfoInterface     $propertyInfo
798
     * @param string|null       $defaultValue
799
     *
800
     * @return string
801
     */
802 29
    protected function generateEntityStubMethod($type, InfoInterface $propertyInfo, $defaultValue = null)
803
    {
804 29
        $fieldName = $propertyInfo->name();
805
806
        // The hint flag help algorythm to determine the hint info for object parameter.
807
        // It should be 'array' for collection but the add method need the object hint.
808
        // setItems(array $items)
809
        // addItem(Item $item)
810 29
        $hintOne = false;
811
812 29
        if ($type === 'get' && $this->useGetShortcutMethod === true) {
813 28
            $variableName = $this->inflector->camelize($fieldName);
814 28
            $methodName = $variableName;
815
        } else {
816 29
            $methodName = $type . $this->inflector->classify($fieldName);
817 29
            $variableName = $this->inflector->camelize($fieldName);
818
        }
819
820 29
        if ($type === 'add') {
821 19
            $methodName = $this->inflector->singularize($methodName);
822 19
            $variableName = $this->inflector->singularize($variableName);
823 19
            $hintOne = true;
824
        }
825
826 29
        if ($this->hasMethod($methodName)) {
827
            return '';
828
        }
829 29
        $this->staticReflection[$this->mapperInfo->className()]['methods'][] = strtolower($methodName);
830
831 29
        if ($propertyInfo->isObject()) {
832
            /** @var ObjectPropertyInfo $propertyInfo */
833 25
            $variableType = $this->getRelativeClassName($propertyInfo->className());
834 25
            $methodTypeHint =  $variableType.' ';
835
        } else {
836
            /** @var PropertyInfo $propertyInfo */
837 29
            $variableType = $propertyInfo->phpType();
838 29
            $methodTypeHint = '';
839
        }
840
841 29
        if ($propertyInfo->isArray() && $hintOne === false) {
842 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

842
            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...
843
                /** @var ObjectPropertyInfo $propertyInfo */
844 1
                $repository = $this->prime->repository($propertyInfo->className());
845 1
                $wrapperClass = $this->getRelativeClassName($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

845
                $wrapperClass = $this->getRelativeClassName($repository->collectionFactory()->wrapperClass(/** @scrutinizer ignore-type */ $propertyInfo->wrapper()));
Loading history...
846
847 1
                $methodTypeHint = $wrapperClass.' ';
848 1
                $variableType .= '[]|'.$wrapperClass;
849
            } else {
850 19
                $methodTypeHint = 'array ';
851
852 19
                if ($variableType !== 'array') {
853 19
                    $variableType .= '[]';
854
                }
855
            }
856
        }
857
858
        $replacements = [
859 29
          '<description>'       => ucfirst($type).' '.$variableName,
860
          '<methodTypeHint>'    => $methodTypeHint,
861
          '<variableType>'      => $variableType,
862
          '<variableName>'      => $variableName,
863
          '<methodName>'        => $methodName,
864
          '<fieldName>'         => $fieldName,
865 29
          '<variableDefault>'   => ($defaultValue !== null) ? (' = '.$defaultValue) : ''
866
        ];
867
868 29
        $method = str_replace(
869 29
            array_keys($replacements),
870 29
            array_values($replacements),
871 29
            $this->getMethodTemplate($type)
872
        );
873
874 29
        return $this->prefixCodeWithSpaces($method);
875
    }
876
877
    /**
878
     * Get the template of the method
879
     *
880
     * @param string $prefix
881
     *
882
     * @return string
883
     *
884
     * @throws \LogicException
885
     */
886 29
    private function getMethodTemplate($prefix)
887
    {
888 29
        switch ($prefix) {
889 29
            case 'get':
890 29
                return static::$getMethodTemplate;
0 ignored issues
show
Bug introduced by
Since $getMethodTemplate is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $getMethodTemplate to at least protected.
Loading history...
891
892 29
            case 'add':
893 19
                return static::$addMethodTemplate;
0 ignored issues
show
Bug introduced by
Since $addMethodTemplate is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $addMethodTemplate to at least protected.
Loading history...
894
895 29
            case 'set':
896 29
                return static::$setMethodTemplate;
0 ignored issues
show
Bug introduced by
Since $setMethodTemplate is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $setMethodTemplate to at least protected.
Loading history...
897
        }
898
899
        throw new \LogicException('No template found for method "'.$prefix.'"');
900
    }
901
902
    /**
903
     * @param string $description
904
     * @param string $methodName
905
     * @param string $content
906
     *
907
     * @return string
908
     */
909 1
    protected function generateMethod(string $description, string $methodName, string $content, ?string $return = null)
910
    {
911 1
        if ($this->hasMethod($methodName)) {
912
            return '';
913
        }
914
915 1
        $this->staticReflection[$this->mapperInfo->className()]['methods'][] = $methodName;
916
917
        $replacements = [
918
            '<description>' => $description,
919
            '<methodName>'  => $methodName,
920
            '<content>'     => $content,
921 1
            '<return>'      => $return ? ": $return" : '',
922
        ];
923
924 1
        $method = str_replace(
925 1
            array_keys($replacements),
926 1
            array_values($replacements),
927 1
            static::$methodTemplate
0 ignored issues
show
Bug introduced by
Since $methodTemplate is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $methodTemplate to at least protected.
Loading history...
928
        );
929
930 1
        return $this->prefixCodeWithSpaces($method);
931
    }
932
933
    /**
934
     * @param PropertyInfo $property
935
     *
936
     * @return string
937
     */
938 30
    protected function generateFieldMappingPropertyDocBlock($property)
939
    {
940 30
        $lines = [];
941 30
        $lines[] = $this->spaces . '/**';
942 30
        $lines[] = $this->spaces . ' * @var '.$property->phpType();
943 30
        $lines[] = $this->spaces . ' */';
944
945 30
        return implode("\n", $lines);
946
    }
947
948
    /**
949
     * @param ObjectPropertyInfo $property
950
     *
951
     * @return string
952
     */
953 26
    protected function generateEmbeddedPropertyDocBlock($property)
954
    {
955 26
        $className = $property->className();
956 26
        if ($className) {
957 26
            $className = $this->getRelativeClassName($className);
958
959 26
            if ($property->isArray()) {
960 19
                if ($property->wrapper() !== null) {
961 1
                    $repository = $this->prime->repository($property->className());
962 1
                    $className = $this->getRelativeClassName($repository->collectionFactory()->wrapperClass($property->wrapper())).'|'.$className.'[]';
0 ignored issues
show
Bug introduced by
It seems like $property->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

962
                    $className = $this->getRelativeClassName($repository->collectionFactory()->wrapperClass(/** @scrutinizer ignore-type */ $property->wrapper())).'|'.$className.'[]';
Loading history...
963
                } else {
964 26
                    $className .= '[]';
965
                }
966
            }
967
        } else {
968
            $className = '{type}';
969
        }
970
971 26
        $lines = [];
972 26
        $lines[] = $this->spaces . '/**';
973 26
        $lines[] = $this->spaces . ' * @var '.$className;
974 26
        $lines[] = $this->spaces . ' */';
975
976 26
        return implode("\n", $lines);
977
    }
978
979
    //
980
    //---------- tools methods
981
    //
982
983
    /**
984
     * @todo this won't work if there is a namespace in brackets and a class outside of it.
985
     *
986
     * @param string $src
987
     *
988
     * @return void
989
     */
990
    protected function parseTokensInEntityFile($src)
991
    {
992
        $tokens = token_get_all($src);
993
        $lastSeenNamespace = "";
994
        $lastSeenClass = false;
995
996
        $inNamespace = false;
997
        $inClass = false;
998
999
        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...
1000
            $token = $tokens[$i];
1001
            if (in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT])) {
1002
                continue;
1003
            }
1004
1005
            if ($inNamespace) {
1006
                if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) {
1007
                    $lastSeenNamespace .= $token[1];
1008
                } elseif (is_string($token) && in_array($token, [';', '{'])) {
1009
                    $inNamespace = false;
1010
                }
1011
            }
1012
1013
            if ($inClass) {
1014
                $inClass = false;
1015
                $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
1016
                $this->staticReflection[$lastSeenClass]['properties'] = [];
1017
                $this->staticReflection[$lastSeenClass]['methods'] = [];
1018
            }
1019
1020
            if ($token[0] == T_NAMESPACE) {
1021
                $lastSeenNamespace = "";
1022
                $inNamespace = true;
1023
            } elseif ($token[0] == T_CLASS && $tokens[$i-1][0] != T_DOUBLE_COLON) {
1024
                $inClass = true;
1025
            } elseif ($token[0] == T_FUNCTION) {
1026
                if ($tokens[$i+2][0] == T_STRING) {
1027
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+2][1]);
1028
                } elseif ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) {
1029
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+3][1]);
1030
                }
1031
            } elseif (in_array($token[0], [T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED]) && $tokens[$i+2][0] != T_FUNCTION) {
1032
                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
1033
            }
1034
        }
1035
    }
1036
1037
    /**
1038
     * @param string $property
1039
     *
1040
     * @return bool
1041
     */
1042 30
    protected function hasProperty($property)
1043
    {
1044 30
        if ($this->classToExtend || (!$this->isNew && class_exists($this->mapperInfo->className()))) {
1045
            // don't generate property if its already on the base class.
1046 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $this->mapperInfo->className());
1047 2
            if ($reflClass->hasProperty($property)) {
1048 2
                return true;
1049
            }
1050
        }
1051
1052
        // check traits for existing property
1053 30
        foreach ($this->getTraitsReflections() as $trait) {
1054
            if ($trait->hasProperty($property)) {
1055
                return true;
1056
            }
1057
        }
1058
1059
        return (
1060 30
            isset($this->staticReflection[$this->mapperInfo->className()]) &&
1061 30
            in_array($property, $this->staticReflection[$this->mapperInfo->className()]['properties'])
1062
        );
1063
    }
1064
1065
    /**
1066
     * @param string $method
1067
     *
1068
     * @return bool
1069
     */
1070 30
    protected function hasMethod($method)
1071
    {
1072 30
        if ($this->classToExtend || (!$this->isNew && class_exists($this->mapperInfo->className()))) {
1073
            // don't generate method if its already on the base class.
1074 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $this->mapperInfo->className());
1075
1076 2
            if ($reflClass->hasMethod($method)) {
1077 2
                return true;
1078
            }
1079
        }
1080
1081
        // check traits for existing method
1082 30
        foreach ($this->getTraitsReflections() as $trait) {
1083
            if ($trait->hasMethod($method)) {
1084
                return true;
1085
            }
1086
        }
1087
1088
        return (
1089 30
            isset($this->staticReflection[$this->mapperInfo->className()]) &&
1090 30
            in_array(strtolower($method), $this->staticReflection[$this->mapperInfo->className()]['methods'])
1091
        );
1092
    }
1093
1094
    /**
1095
     * Get class name relative to use
1096
     *
1097
     * @param string $className
1098
     * @return string
1099
     */
1100 28
    protected function getRelativeClassName($className)
1101
    {
1102 28
        $className = ltrim($className, '\\');
1103
1104 28
        if ($this->hasNamespace($className)) {
1105 26
            return $this->getClassName($className);
1106
        } else {
1107 6
            return '\\' . $className;
1108
        }
1109
    }
1110
1111
    /**
1112
     * Get the class short name
1113
     *
1114
     * @param string $className
1115
     *
1116
     * @return string
1117
     */
1118 30
    protected function getClassName($className)
1119
    {
1120 30
        $parts = explode('\\', $className);
1121 30
        return array_pop($parts);
1122
    }
1123
1124
    /**
1125
     * @param string $className
1126
     *
1127
     * @return string
1128
     */
1129 30
    protected function getNamespace($className)
1130
    {
1131 30
        $parts = explode('\\', $className);
1132 30
        array_pop($parts);
1133
1134 30
        return implode('\\', $parts);
1135
    }
1136
1137
    /**
1138
     * @param string $className
1139
     *
1140
     * @return bool
1141
     */
1142 30
    protected function hasNamespace($className)
1143
    {
1144 30
        return strrpos($className, '\\') != 0;
1145
    }
1146
1147
    /**
1148
     * @return string
1149
     */
1150 2
    protected function getClassToExtendName()
1151
    {
1152 2
        $refl = new \ReflectionClass($this->getClassToExtend());
1153
1154 2
        return $this->getRelativeClassName($refl->getName());
1155
    }
1156
1157
    /**
1158
     * @return string
1159
     */
1160 3
    protected function getInterfacesToImplement()
1161
    {
1162 3
        $interfaces = [];
1163
1164 3
        foreach ($this->interfaces as $interface) {
1165 3
            $refl = new \ReflectionClass($interface);
1166
1167 3
            $interfaces[] = $this->getRelativeClassName($refl->getName());
1168
        }
1169
1170 3
        return implode(', ', $interfaces);
1171
    }
1172
1173
    /**
1174
     * @param Mapper $mapper
1175
     *
1176
     * @return \ReflectionClass[]
1177
     */
1178 30
    protected function getTraitsReflections()
1179
    {
1180 30
        if ($this->isNew) {
1181 30
            return [];
1182
        }
1183
1184
        $reflClass = new \ReflectionClass($this->mapperInfo->className());
1185
1186
        $traits = [];
1187
1188
        while ($reflClass !== false) {
1189
            $traits = array_merge($traits, $reflClass->getTraits());
1190
1191
            $reflClass = $reflClass->getParentClass();
1192
        }
1193
1194
        return $traits;
1195
    }
1196
1197
    /**
1198
     * @param string $code
1199
     * @param int    $num
1200
     *
1201
     * @return string
1202
     */
1203 30
    protected function prefixCodeWithSpaces($code, $num = 1)
1204
    {
1205 30
        $lines = explode("\n", $code);
1206
1207 30
        foreach ($lines as $key => $value) {
1208 30
            if (! empty($value)) {
1209 30
                $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
1210
            }
1211
        }
1212
1213 30
        return implode("\n", $lines);
1214
    }
1215
1216
    /**
1217
     * Get string representation of a value
1218
     *
1219
     * @param mixed $value
1220
     *
1221
     * @return string
1222
     */
1223 5
    protected function stringfyValue($value)
1224
    {
1225 5
        if (is_array($value)) {
1226
            if (empty($value)) {
1227
                return '[]';
1228
            }
1229
1230
            return var_export($value, true);
1231
        }
1232
1233 5
        if (null === $value) {
1234
            return 'null';
1235
        }
1236
1237 5
        if (is_string($value)) {
1238 4
            return "'" . $value . "'";
1239
        }
1240
1241 2
        if (is_bool($value)) {
1242 2
            return $value ? 'true' : 'false';
1243
        }
1244
1245
        return $value;
1246
    }
1247
1248
    /**
1249
     * Get the php 7.4 property type hint
1250
     *
1251
     * This method will map invalid type hint to value one (ex: double -> float)
1252
     * It will also resolve the relative class name if the type is a class
1253
     *
1254
     * @param string $type The property type declared by field phpType()
1255
     * @param bool $nullable Does the property should be nullable
1256
     *
1257
     * @return string
1258
     *
1259
     * @see TypeInterface::phpType()
1260
     *
1261
     * @todo use directly the property info object ?
1262
     */
1263 15
    protected function getPropertyTypeHint(string $type, bool $nullable): string
1264
    {
1265 15
        if (class_exists($type)) {
1266 14
            $type = $this->getRelativeClassName($type);
1267
        } else {
1268 15
            $type = self::PROPERTY_TYPE_MAP[$type] ?? $type;
1269
        }
1270
1271 15
        return ($nullable ? '?' : '') . $type;
1272
    }
1273
1274
    /**
1275
     * Get the php 7.4 property type hint for a simple property
1276
     *
1277
     * If typed properties are disabled, this method will return an empty string
1278
     * The returned type hint will be prefixed by a single space
1279
     *
1280
     * @param PropertyInfo $property
1281
     * @param bool $forceNullable Force typehint to be nullable. Useful property promotion
1282
     *
1283
     * @return string
1284
     */
1285 30
    protected function getPropertyTypeHintForSimpleProperty(PropertyInfo $property, bool $forceNullable = false): string
1286
    {
1287 30
        if (!$this->useTypedProperties) {
1288 26
            return '';
1289
        }
1290
1291 15
        return ' ' . $this->getPropertyTypeHint($property->phpType(), $forceNullable || $property->isNullable());
1292
    }
1293
1294
    /**
1295
     * Get the php 7.4 property type hint for a object property (i.e. relation or embedded)
1296
     *
1297
     * If typed properties are disabled, this method will return an empty string
1298
     * The returned type hint will be prefixed by a single space
1299
     *
1300
     * - Embedded properties will not be marked as nullable
1301
     * - Relations will always be nullable
1302
     * - This method will also resolve collection relations and the wrapper class if provided
1303
     *
1304
     * @param ObjectPropertyInfo $property
1305
     * @param bool $forceNullable Force typehint to be nullable. Useful property promotion
1306
     *
1307
     * @return string
1308
     */
1309 26
    protected function getPropertyTypeHintForObject(ObjectPropertyInfo $property, bool $forceNullable = false): string
1310
    {
1311 26
        if (!$this->useTypedProperties) {
1312 23
            return '';
1313
        }
1314
1315 12
        $type = $property->className();
1316
1317 12
        if ($property->isArray()) {
1318 8
            if ($property->wrapper() === null) {
1319 7
                $type = 'array';
1320
            } else {
1321 1
                $repository = $this->prime->repository($type);
1322 1
                $type = $repository->collectionFactory()->wrapperClass($property->wrapper());
0 ignored issues
show
Bug introduced by
It seems like $property->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

1322
                $type = $repository->collectionFactory()->wrapperClass(/** @scrutinizer ignore-type */ $property->wrapper());
Loading history...
1323
            }
1324
        }
1325
1326 12
        return ' ' . $this->getPropertyTypeHint($type, $forceNullable || $property->isRelation());
0 ignored issues
show
Bug introduced by
It seems like $type can also be of type null; however, parameter $type of Bdf\Prime\Entity\EntityG...::getPropertyTypeHint() 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

1326
        return ' ' . $this->getPropertyTypeHint(/** @scrutinizer ignore-type */ $type, $forceNullable || $property->isRelation());
Loading history...
1327
    }
1328
1329
    //---------------------- mutators
1330
1331
    /**
1332
     * Sets the number of spaces the exported class should have.
1333
     *
1334
     * @api
1335
     */
1336 1
    public function setNumSpaces(int $numSpaces): void
1337
    {
1338 1
        $this->spaces = str_repeat(' ', $numSpaces);
1339 1
        $this->numSpaces = $numSpaces;
1340
    }
1341
1342
    /**
1343
     * Gets the indentation spaces
1344
     */
1345 1
    public function getNumSpaces(): int
1346
    {
1347 1
        return $this->numSpaces;
1348
    }
1349
1350
    /**
1351
     * Sets the extension to use when writing php files to disk.
1352
     *
1353
     * @api
1354
     */
1355 1
    public function setExtension(string $extension): void
1356
    {
1357 1
        $this->extension = $extension;
1358
    }
1359
1360
    /**
1361
     * Get the file extension
1362
     */
1363 1
    public function getExtension(): string
1364
    {
1365 1
        return $this->extension;
1366
    }
1367
1368
    /**
1369
     * Sets the name of the class the generated classes should extend from.
1370
     *
1371
     * @api
1372
     */
1373 3
    public function setClassToExtend(string $classToExtend): void
1374
    {
1375 3
        $this->classToExtend = $classToExtend;
1376
    }
1377
1378
    /**
1379
     * Get the class to extend
1380
     */
1381 31
    public function getClassToExtend(): ?string
1382
    {
1383 31
        return $this->classToExtend;
1384
    }
1385
1386
    /**
1387
     * Add interface to implement
1388
     *
1389
     * @api
1390
     *
1391
     * @return void
1392
     */
1393 2
    public function addInterface(string $interface): void
1394
    {
1395 2
        $this->interfaces[$interface] = $interface;
1396
    }
1397
1398
    /**
1399
     * Sets the interfaces
1400
     *
1401
     * @param string[] $interfaces
1402
     *
1403
     * @api
1404
     */
1405 2
    public function setInterfaces(array $interfaces): void
1406
    {
1407 2
        $this->interfaces = $interfaces;
1408
    }
1409
1410
    /**
1411
     * Get the registered interfaces
1412
     */
1413 1
    public function getInterfaces(): array
1414
    {
1415 1
        return $this->interfaces;
1416
    }
1417
1418
    /**
1419
     * Add trait
1420
     *
1421
     * @api
1422
     *
1423
     * @return void
1424
     */
1425 2
    public function addTrait(string $trait): void
1426
    {
1427 2
        $this->traits[$trait] = $trait;
1428
    }
1429
1430
    /**
1431
     * Sets the traits
1432
     *
1433
     * @param string[] $traits
1434
     *
1435
     * @api
1436
     */
1437 1
    public function setTraits(array $traits): void
1438
    {
1439 1
        $this->traits = $traits;
1440
    }
1441
1442
    /**
1443
     * Get the registered traits
1444
     */
1445 1
    public function getTraits(): array
1446
    {
1447 1
        return $this->traits;
1448
    }
1449
1450
    /**
1451
     * Sets the class fields visibility for the entity (can either be private or protected).
1452
     *
1453
     * @throws \InvalidArgumentException
1454
     *
1455
     * @api
1456
     */
1457 2
    public function setFieldVisibility(string $visibility): void
1458
    {
1459 2
        if ($visibility !== static::FIELD_VISIBLE_PRIVATE && $visibility !== static::FIELD_VISIBLE_PROTECTED) {
1460
            throw new \InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility);
1461
        }
1462
1463 2
        $this->fieldVisibility = $visibility;
1464
    }
1465
1466
    /**
1467
     * Get the field visibility
1468
     */
1469 1
    public function getFieldVisibility(): string
1470
    {
1471 1
        return $this->fieldVisibility;
1472
    }
1473
1474
    /**
1475
     * Sets whether or not to try and update the entity if it already exists.
1476
     *
1477
     * @api
1478
     */
1479 1
    public function setUpdateEntityIfExists(bool $bool): void
1480
    {
1481 1
        $this->updateEntityIfExists = $bool;
1482
    }
1483
1484
    /**
1485
     * Get the flag for updating the entity
1486
     */
1487 1
    public function getUpdateEntityIfExists(): bool
1488
    {
1489 1
        return $this->updateEntityIfExists;
1490
    }
1491
1492
    /**
1493
     * Sets whether or not to regenerate the entity if it exists.
1494
     *
1495
     * @api
1496
     */
1497 1
    public function setRegenerateEntityIfExists(bool $bool): void
1498
    {
1499 1
        $this->regenerateEntityIfExists = $bool;
1500
    }
1501
1502
    /**
1503
     * Get the flag for regenerating entity
1504
     */
1505 1
    public function getRegenerateEntityIfExists(): bool
1506
    {
1507 1
        return $this->regenerateEntityIfExists;
1508
    }
1509
1510
    /**
1511
     * Sets whether or not to generate stub methods for the entity.
1512
     *
1513
     * @api
1514
     */
1515 2
    public function setGenerateStubMethods(bool $bool): void
1516
    {
1517 2
        $this->generateEntityStubMethods = $bool;
1518
    }
1519
1520
    /**
1521
     * Get the flag for generating stub methods
1522
     */
1523 1
    public function getGenerateStubMethods(): bool
1524
    {
1525 1
        return $this->generateEntityStubMethods;
1526
    }
1527
1528
    /**
1529
     * Sets whether or not the get mehtod will be suffixed by 'get'.
1530
     *
1531
     * @param bool $bool
1532
     *
1533
     * @return void
1534
     *
1535
     * @api
1536
     */
1537 2
    public function useGetShortcutMethod($bool = true)
1538
    {
1539 2
        $this->useGetShortcutMethod = $bool;
1540
    }
1541
1542
    /**
1543
     * Get the flag for get mehtod name.
1544
     */
1545 1
    public function getUseGetShortcutMethod(): bool
1546
    {
1547 1
        return $this->useGetShortcutMethod;
1548
    }
1549
1550
    /**
1551
     * @return bool
1552
     */
1553
    public function getUseTypedProperties(): bool
1554
    {
1555
        return $this->useTypedProperties;
1556
    }
1557
1558
    /**
1559
     * Enable usage of php 7.4 type properties
1560
     *
1561
     * @param bool $useTypedProperties
1562
     */
1563 15
    public function useTypedProperties(bool $useTypedProperties = true): void
1564
    {
1565 15
        $this->useTypedProperties = $useTypedProperties;
1566
    }
1567
1568
    /**
1569
     * @return bool
1570
     */
1571
    public function getUseConstructorPropertyPromotion(): bool
1572
    {
1573
        return $this->useConstructorPropertyPromotion;
1574
    }
1575
1576
    /**
1577
     * Enable usage of PHP 8 promoted properties on constructor instead of array import
1578
     *
1579
     * @param bool $useConstructorPropertyPromotion
1580
     */
1581 2
    public function useConstructorPropertyPromotion(bool $useConstructorPropertyPromotion = true): void
1582
    {
1583 2
        $this->useConstructorPropertyPromotion = $useConstructorPropertyPromotion;
1584
    }
1585
}
1586