Passed
Pull Request — 2.6 (#7894)
by
unknown
08:16
created

EntityGenerator::generateEmbeddableConstructor()   B

Complexity

Conditions 10
Paths 120

Size

Total Lines 85
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 49
CRAP Score 10.0008

Importance

Changes 0
Metric Value
cc 10
eloc 51
c 0
b 0
f 0
nop 1
dl 0
loc 85
ccs 49
cts 50
cp 0.98
crap 10.0008
rs 7.069
nc 120

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 Doctrine\ORM\Tools;
21
22
use Doctrine\Common\Collections\Collection;
23
use Doctrine\Common\Inflector\Inflector;
24
use Doctrine\DBAL\Types\Type;
25
use Doctrine\ORM\Mapping\ClassMetadataInfo;
26
use function str_replace;
27
use function var_export;
28
29
/**
30
 * Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances.
31
 *
32
 *     [php]
33
 *     $classes = $em->getClassMetadataFactory()->getAllMetadata();
34
 *
35
 *     $generator = new \Doctrine\ORM\Tools\EntityGenerator();
36
 *     $generator->setGenerateAnnotations(true);
37
 *     $generator->setGenerateStubMethods(true);
38
 *     $generator->setRegenerateEntityIfExists(false);
39
 *     $generator->setUpdateEntityIfExists(true);
40
 *     $generator->generate($classes, '/path/to/generate/entities');
41
 *
42
 *
43
 * @link    www.doctrine-project.org
44
 * @since   2.0
45
 * @author  Benjamin Eberlei <[email protected]>
46
 * @author  Guilherme Blanco <[email protected]>
47
 * @author  Jonathan Wage <[email protected]>
48
 * @author  Roman Borschel <[email protected]>
49
 */
50
class EntityGenerator
51
{
52
    /**
53
     * Specifies class fields should be protected.
54
     */
55
    const FIELD_VISIBLE_PROTECTED = 'protected';
56
57
    /**
58
     * Specifies class fields should be private.
59
     */
60
    const FIELD_VISIBLE_PRIVATE = 'private';
61
62
    /**
63
     * @var bool
64
     */
65
    protected $backupExisting = true;
66
67
    /**
68
     * The extension to use for written php files.
69
     *
70
     * @var string
71
     */
72
    protected $extension = '.php';
73
74
    /**
75
     * Whether or not the current ClassMetadataInfo instance is new or old.
76
     *
77
     * @var boolean
78
     */
79
    protected $isNew = true;
80
81
    /**
82
     * @var array
83
     */
84
    protected $staticReflection = [];
85
86
    /**
87
     * Number of spaces to use for indention in generated code.
88
     */
89
    protected $numSpaces = 4;
90
91
    /**
92
     * The actual spaces to use for indention.
93
     *
94
     * @var string
95
     */
96
    protected $spaces = '    ';
97
98
    /**
99
     * The class all generated entities should extend.
100
     *
101
     * @var string
102
     */
103
    protected $classToExtend;
104
105
    /**
106
     * Whether or not to generation annotations.
107
     *
108
     * @var boolean
109
     */
110
    protected $generateAnnotations = false;
111
112
    /**
113
     * @var string
114
     */
115
    protected $annotationsPrefix = '';
116
117
    /**
118
     * Whether or not to generate sub methods.
119
     *
120
     * @var boolean
121
     */
122
    protected $generateEntityStubMethods = false;
123
124
    /**
125
     * Whether or not to update the entity class if it exists already.
126
     *
127
     * @var boolean
128
     */
129
    protected $updateEntityIfExists = false;
130
131
    /**
132
     * Whether or not to re-generate entity class if it exists already.
133
     *
134
     * @var boolean
135
     */
136
    protected $regenerateEntityIfExists = false;
137
138
    /**
139
     * Visibility of the field
140
     *
141
     * @var string
142
     */
143
    protected $fieldVisibility = 'private';
144
145
    /**
146
     * Whether or not to make generated embeddables immutable.
147
     *
148
     * @var boolean.
0 ignored issues
show
Documentation Bug introduced by
The doc comment boolean. at position 0 could not be parsed: Unknown type name 'boolean.' at position 0 in boolean..
Loading history...
149
     */
150
    protected $embeddablesImmutable = false;
151
152
    /**
153
     * Hash-map for handle types.
154
     *
155
     * @var array
156
     */
157
    protected $typeAlias = [
158
        Type::DATETIMETZ    => '\DateTime',
159
        Type::DATETIME      => '\DateTime',
160
        Type::DATE          => '\DateTime',
161
        Type::TIME          => '\DateTime',
162
        Type::OBJECT        => '\stdClass',
163
        Type::INTEGER       => 'int',
164
        Type::BIGINT        => 'int',
165
        Type::SMALLINT      => 'int',
166
        Type::TEXT          => 'string',
167
        Type::BLOB          => 'string',
168
        Type::DECIMAL       => 'string',
169
        Type::GUID          => 'string',
170
        Type::JSON_ARRAY    => 'array',
171
        Type::SIMPLE_ARRAY  => 'array',
172
        Type::BOOLEAN       => 'bool',
173
    ];
174
175
    /**
176
     * Hash-map to handle generator types string.
177
     *
178
     * @var array
179
     */
180
    protected static $generatorStrategyMap = [
181
        ClassMetadataInfo::GENERATOR_TYPE_AUTO      => 'AUTO',
182
        ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE  => 'SEQUENCE',
183
        ClassMetadataInfo::GENERATOR_TYPE_TABLE     => 'TABLE',
184
        ClassMetadataInfo::GENERATOR_TYPE_IDENTITY  => 'IDENTITY',
185
        ClassMetadataInfo::GENERATOR_TYPE_NONE      => 'NONE',
186
        ClassMetadataInfo::GENERATOR_TYPE_UUID      => 'UUID',
187
        ClassMetadataInfo::GENERATOR_TYPE_CUSTOM    => 'CUSTOM'
188
    ];
189
190
    /**
191
     * Hash-map to handle the change tracking policy string.
192
     *
193
     * @var array
194
     */
195
    protected static $changeTrackingPolicyMap = [
196
        ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT  => 'DEFERRED_IMPLICIT',
197
        ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT  => 'DEFERRED_EXPLICIT',
198
        ClassMetadataInfo::CHANGETRACKING_NOTIFY             => 'NOTIFY',
199
    ];
200
201
    /**
202
     * Hash-map to handle the inheritance type string.
203
     *
204
     * @var array
205
     */
206
    protected static $inheritanceTypeMap = [
207
        ClassMetadataInfo::INHERITANCE_TYPE_NONE            => 'NONE',
208
        ClassMetadataInfo::INHERITANCE_TYPE_JOINED          => 'JOINED',
209
        ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE    => 'SINGLE_TABLE',
210
        ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS => 'TABLE_PER_CLASS',
211
    ];
212
213
    /**
214
     * @var string
215
     */
216
    protected static $classTemplate =
217
'<?php
218
219
<namespace>
220
<useStatement>
221
<entityAnnotation>
222
<entityClassName>
223
{
224
<entityBody>
225
}
226
';
227
228
    /**
229
     * @var string
230
     */
231
    protected static $getMethodTemplate =
232
'/**
233
 * <description>
234
 *
235
 * @return <variableType>
236
 */
237
public function <methodName>()
238
{
239
<spaces>return $this-><fieldName>;
240
}';
241
242
    /**
243
     * @var string
244
     */
245
    protected static $setMethodTemplate =
246
'/**
247
 * <description>
248
 *
249
 * @param <variableType> $<variableName>
250
 *
251
 * @return <entity>
252
 */
253
public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
254
{
255
<spaces>$this-><fieldName> = $<variableName>;
256
257
<spaces>return $this;
258
}';
259
260
    /**
261
     * @var string
262
     */
263
    protected static $addMethodTemplate =
264
'/**
265
 * <description>
266
 *
267
 * @param <variableType> $<variableName>
268
 *
269
 * @return <entity>
270
 */
271
public function <methodName>(<methodTypeHint>$<variableName>)
272
{
273
<spaces>$this-><fieldName>[] = $<variableName>;
274
275
<spaces>return $this;
276
}';
277
278
    /**
279
     * @var string
280
     */
281
    protected static $removeMethodTemplate =
282
'/**
283
 * <description>
284
 *
285
 * @param <variableType> $<variableName>
286
 *
287
 * @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
288
 */
289
public function <methodName>(<methodTypeHint>$<variableName>)
290
{
291
<spaces>return $this-><fieldName>->removeElement($<variableName>);
292
}';
293
294
    /**
295
     * @var string
296
     */
297
    protected static $lifecycleCallbackMethodTemplate =
298
'/**
299
 * @<name>
300
 */
301
public function <methodName>()
302
{
303
<spaces>// Add your code here
304
}';
305
306
    /**
307
     * @var string
308
     */
309
    protected static $constructorMethodTemplate =
310
'/**
311
 * Constructor
312
 */
313
public function __construct()
314
{
315
<spaces><collections>
316
}
317
';
318
319
    /**
320
     * @var string
321
     */
322
    protected static $embeddableConstructorMethodTemplate =
323
'/**
324
 * Constructor
325
 *
326
 * <paramTags>
327
 */
328
public function __construct(<params>)
329
{
330
<spaces><fields>
331
}
332
';
333
334
    /**
335
     * Constructor.
336
     */
337 54
    public function __construct()
338
    {
339 54
        if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
340 54
            $this->annotationsPrefix = 'ORM\\';
341
        }
342 54
    }
343
344
    /**
345
     * Generates and writes entity classes for the given array of ClassMetadataInfo instances.
346
     *
347
     * @param array  $metadatas
348
     * @param string $outputDirectory
349
     *
350
     * @return void
351
     */
352
    public function generate(array $metadatas, $outputDirectory)
353
    {
354
        foreach ($metadatas as $metadata) {
355
            $this->writeEntityClass($metadata, $outputDirectory);
356
        }
357
    }
358
359
    /**
360
     * Generates and writes entity class to disk for the given ClassMetadataInfo instance.
361
     *
362
     * @param ClassMetadataInfo $metadata
363
     * @param string            $outputDirectory
364
     *
365
     * @return void
366
     *
367
     * @throws \RuntimeException
368
     */
369 45
    public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
370
    {
371 45
        $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension;
372 45
        $dir = dirname($path);
373
374 45
        if ( ! is_dir($dir)) {
375 2
            mkdir($dir, 0775, true);
376
        }
377
378 45
        $this->isNew = ! file_exists($path) || $this->regenerateEntityIfExists;
379
380 45
        if ( ! $this->isNew) {
381 4
            $this->parseTokensInEntityFile(file_get_contents($path));
382
        } else {
383 44
            $this->staticReflection[$metadata->name] = ['properties' => [], 'methods' => []];
384
        }
385
386 45
        if ($this->backupExisting && file_exists($path)) {
387 4
            $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~";
388 4
            if (!copy($path, $backupPath)) {
389
                throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed.");
390
            }
391
        }
392
393
        // If entity doesn't exist or we're re-generating the entities entirely
394 45
        if ($this->isNew) {
395 44
            file_put_contents($path, $this->generateEntityClass($metadata));
396
        // If entity exists and we're allowed to update the entity class
397 4
        } elseif ($this->updateEntityIfExists) {
398 4
            file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path));
399
        }
400 45
        chmod($path, 0664);
401 45
    }
402
403
    /**
404
     * Generates a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance.
405
     *
406
     * @param ClassMetadataInfo $metadata
407
     *
408
     * @return string
409
     */
410 45
    public function generateEntityClass(ClassMetadataInfo $metadata)
411
    {
412
        $placeHolders = [
413 45
            '<namespace>',
414
            '<useStatement>',
415
            '<entityAnnotation>',
416
            '<entityClassName>',
417
            '<entityBody>'
418
        ];
419
420
        $replacements = [
421 45
            $this->generateEntityNamespace($metadata),
422 45
            $this->generateEntityUse(),
423 45
            $this->generateEntityDocBlock($metadata),
424 45
            $this->generateEntityClassName($metadata),
425 45
            $this->generateEntityBody($metadata)
426
        ];
427
428 45
        $code = str_replace($placeHolders, $replacements, static::$classTemplate);
429
430 45
        return str_replace('<spaces>', $this->spaces, $code);
431
    }
432
433
    /**
434
     * Generates the updated code for the given ClassMetadataInfo and entity at path.
435
     *
436
     * @param ClassMetadataInfo $metadata
437
     * @param string            $path
438
     *
439
     * @return string
440
     */
441 4
    public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
442
    {
443 4
        $currentCode = file_get_contents($path);
444
445 4
        $body = $this->generateEntityBody($metadata);
446 4
        $body = str_replace('<spaces>', $this->spaces, $body);
447 4
        $last = strrpos($currentCode, '}');
448
449 4
        return substr($currentCode, 0, $last) . $body . ($body ? "\n" : '') . "}\n";
450
    }
451
452
    /**
453
     * Sets the number of spaces the exported class should have.
454
     *
455
     * @param integer $numSpaces
456
     *
457
     * @return void
458
     */
459
    public function setNumSpaces($numSpaces)
460
    {
461
        $this->spaces = str_repeat(' ', $numSpaces);
462
        $this->numSpaces = $numSpaces;
463
    }
464
465
    /**
466
     * Sets the extension to use when writing php files to disk.
467
     *
468
     * @param string $extension
469
     *
470
     * @return void
471
     */
472
    public function setExtension($extension)
473
    {
474
        $this->extension = $extension;
475
    }
476
477
    /**
478
     * Sets the name of the class the generated classes should extend from.
479
     *
480
     * @param string $classToExtend
481
     *
482
     * @return void
483
     */
484 1
    public function setClassToExtend($classToExtend)
485
    {
486 1
        $this->classToExtend = $classToExtend;
487 1
    }
488
489
    /**
490
     * Sets whether or not to generate annotations for the entity.
491
     *
492
     * @param bool $bool
493
     *
494
     * @return void
495
     */
496 54
    public function setGenerateAnnotations($bool)
497
    {
498 54
        $this->generateAnnotations = $bool;
499 54
    }
500
501
    /**
502
     * Sets the class fields visibility for the entity (can either be private or protected).
503
     *
504
     * @param bool $visibility
505
     *
506
     * @return void
507
     *
508
     * @throws \InvalidArgumentException
509
     */
510 53
    public function setFieldVisibility($visibility)
511
    {
512 53
        if ($visibility !== static::FIELD_VISIBLE_PRIVATE && $visibility !== static::FIELD_VISIBLE_PROTECTED) {
0 ignored issues
show
introduced by
The condition $visibility !== static::FIELD_VISIBLE_PROTECTED is always true.
Loading history...
513
            throw new \InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility);
514
        }
515
516 53
        $this->fieldVisibility = $visibility;
517 53
    }
518
519
    /**
520
     * Sets whether or not to generate immutable embeddables.
521
     *
522
     * @param boolean $embeddablesImmutable
523
     */
524 1
    public function setEmbeddablesImmutable($embeddablesImmutable)
525
    {
526 1
        $this->embeddablesImmutable = (boolean) $embeddablesImmutable;
527 1
    }
528
529
    /**
530
     * Sets an annotation prefix.
531
     *
532
     * @param string $prefix
533
     *
534
     * @return void
535
     */
536 54
    public function setAnnotationPrefix($prefix)
537
    {
538 54
        $this->annotationsPrefix = $prefix;
539 54
    }
540
541
    /**
542
     * Sets whether or not to try and update the entity if it already exists.
543
     *
544
     * @param bool $bool
545
     *
546
     * @return void
547
     */
548 54
    public function setUpdateEntityIfExists($bool)
549
    {
550 54
        $this->updateEntityIfExists = $bool;
551 54
    }
552
553
    /**
554
     * Sets whether or not to regenerate the entity if it exists.
555
     *
556
     * @param bool $bool
557
     *
558
     * @return void
559
     */
560 54
    public function setRegenerateEntityIfExists($bool)
561
    {
562 54
        $this->regenerateEntityIfExists = $bool;
563 54
    }
564
565
    /**
566
     * Sets whether or not to generate stub methods for the entity.
567
     *
568
     * @param bool $bool
569
     *
570
     * @return void
571
     */
572 54
    public function setGenerateStubMethods($bool)
573
    {
574 54
        $this->generateEntityStubMethods = $bool;
575 54
    }
576
577
    /**
578
     * Should an existing entity be backed up if it already exists?
579
     *
580
     * @param bool $bool
581
     *
582
     * @return void
583
     */
584 1
    public function setBackupExisting($bool)
585
    {
586 1
        $this->backupExisting = $bool;
587 1
    }
588
589
    /**
590
     * @param string $type
591
     *
592
     * @return string
593
     */
594 45
    protected function getType($type)
595
    {
596 45
        if (isset($this->typeAlias[$type])) {
597 44
            return $this->typeAlias[$type];
598
        }
599
600 28
        return $type;
601
    }
602
603
    /**
604
     * @param ClassMetadataInfo $metadata
605
     *
606
     * @return string
607
     */
608 45
    protected function generateEntityNamespace(ClassMetadataInfo $metadata)
609
    {
610 45
        if (! $this->hasNamespace($metadata)) {
611 2
            return '';
612
        }
613
614 45
        return 'namespace ' . $this->getNamespace($metadata) .';';
615
    }
616
617
    /**
618
     * @return string
619
     */
620 45
    protected function generateEntityUse()
621
    {
622 45
        if (! $this->generateAnnotations) {
623
            return '';
624
        }
625
626 45
        return "\n".'use Doctrine\ORM\Mapping as ORM;'."\n";
627
    }
628
629
    /**
630
     * @param ClassMetadataInfo $metadata
631
     *
632
     * @return string
633
     */
634 45
    protected function generateEntityClassName(ClassMetadataInfo $metadata)
635
    {
636 45
        return 'class ' . $this->getClassName($metadata) .
637 45
            ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
638
    }
639
640
    /**
641
     * @param ClassMetadataInfo $metadata
642
     *
643
     * @return string
644
     */
645 46
    protected function generateEntityBody(ClassMetadataInfo $metadata)
646
    {
647 46
        $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata);
648 46
        $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata);
649 46
        $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata);
650 46
        $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null;
651 46
        $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata);
652
653 46
        $code = [];
654
655 46
        if ($fieldMappingProperties) {
656 43
            $code[] = $fieldMappingProperties;
657
        }
658
659 46
        if ($embeddedProperties) {
660 11
            $code[] = $embeddedProperties;
661
        }
662
663 46
        if ($associationMappingProperties) {
664 12
            $code[] = $associationMappingProperties;
665
        }
666
667 46
        $code[] = $this->generateEntityConstructor($metadata);
668
669 46
        if ($stubMethods) {
670 44
            $code[] = $stubMethods;
671
        }
672
673 46
        if ($lifecycleCallbackMethods) {
674 11
            $code[] = $lifecycleCallbackMethods;
675
        }
676
677 46
        return implode("\n", $code);
678
    }
679
680
    /**
681
     * @param ClassMetadataInfo $metadata
682
     *
683
     * @return string
684
     */
685 46
    protected function generateEntityConstructor(ClassMetadataInfo $metadata)
686
    {
687 46
        if ($this->hasMethod('__construct', $metadata)) {
688 3
            return '';
689
        }
690
691 46
        if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) {
692 1
            return $this->generateEmbeddableConstructor($metadata);
693
        }
694
695 45
        $collections = [];
696
697 45
        foreach ($metadata->associationMappings as $mapping) {
698 14
            if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
699 14
                $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
700
            }
701
        }
702
703 45
        if ($collections) {
704 12
            return $this->prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->spaces, $collections), static::$constructorMethodTemplate));
705
        }
706
707 41
        return '';
708
    }
709
710
    /**
711
     * @param ClassMetadataInfo $metadata
712
     *
713
     * @return string
714
     */
715 1
    private function generateEmbeddableConstructor(ClassMetadataInfo $metadata)
716
    {
717 1
        $paramTypes = [];
718 1
        $paramVariables = [];
719 1
        $params = [];
720 1
        $fields = [];
721
722
        // Resort fields to put optional fields at the end of the method signature.
723 1
        $requiredFields = [];
724 1
        $optionalFields = [];
725
726 1
        foreach ($metadata->fieldMappings as $fieldMapping) {
727 1
            if (empty($fieldMapping['nullable'])) {
728 1
                $requiredFields[] = $fieldMapping;
729
730 1
                continue;
731
            }
732
733 1
            $optionalFields[] = $fieldMapping;
734
        }
735
736 1
        $fieldMappings = array_merge($requiredFields, $optionalFields);
737
738 1
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
739 1
            $paramType = '\\' . ltrim($embeddedClass['class'], '\\');
740 1
            $paramVariable = '$' . $fieldName;
741
742 1
            $paramTypes[] = $paramType;
743 1
            $paramVariables[] = $paramVariable;
744 1
            $params[] = $paramType . ' ' . $paramVariable;
745 1
            $fields[] = '$this->' . $fieldName . ' = ' . $paramVariable . ';';
746
        }
747
748 1
        foreach ($fieldMappings as $fieldMapping) {
749 1
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) {
750
                continue;
751
            }
752
753 1
            $paramTypes[] = $this->getType($fieldMapping['type']) . (!empty($fieldMapping['nullable']) ? '|null' : '');
754 1
            $param = '$' . $fieldMapping['fieldName'];
755 1
            $paramVariables[] = $param;
756
757 1
            if ($fieldMapping['type'] === 'datetime') {
758 1
                $param = $this->getType($fieldMapping['type']) . ' ' . $param;
759
            }
760
761 1
            if (!empty($fieldMapping['nullable'])) {
762 1
                $param .= ' = null';
763
            }
764
765 1
            $params[] = $param;
766
767 1
            $fields[] = '$this->' . $fieldMapping['fieldName'] . ' = $' . $fieldMapping['fieldName'] . ';';
768
        }
769
770 1
        $maxParamTypeLength = max(array_map('strlen', $paramTypes));
771 1
        $paramTags = array_map(
772
            function ($type, $variable) use ($maxParamTypeLength) {
773 1
                return '@param ' . $type . str_repeat(' ', $maxParamTypeLength - strlen($type) + 1) . $variable;
774 1
            },
775 1
            $paramTypes,
776 1
            $paramVariables
777
        );
778
779
        // Generate multi line constructor if the signature exceeds 120 characters.
780 1
        if (array_sum(array_map('strlen', $params)) + count($params) * 2 + 29 > 120) {
781 1
            $delimiter = "\n" . $this->spaces;
782 1
            $params = $delimiter . implode(',' . $delimiter, $params) . "\n";
783
        } else {
784 1
            $params = implode(', ', $params);
785
        }
786
787
        $replacements = [
788 1
            '<paramTags>' => implode("\n * ", $paramTags),
789 1
            '<params>'    => $params,
790 1
            '<fields>'    => implode("\n" . $this->spaces, $fields),
791
        ];
792
793 1
        $constructor = str_replace(
794 1
            array_keys($replacements),
795 1
            array_values($replacements),
796 1
            static::$embeddableConstructorMethodTemplate
797
        );
798
799 1
        return $this->prefixCodeWithSpaces($constructor);
800
    }
801
802
    /**
803
     * @todo this won't work if there is a namespace in brackets and a class outside of it.
804
     *
805
     * @param string $src
806
     *
807
     * @return void
808
     */
809 9
    protected function parseTokensInEntityFile($src)
810
    {
811 9
        $tokens = token_get_all($src);
812 9
        $tokensCount = count($tokens);
813 9
        $lastSeenNamespace = '';
814 9
        $lastSeenClass = false;
815
816 9
        $inNamespace = false;
817 9
        $inClass = false;
818
819 9
        for ($i = 0; $i < $tokensCount; $i++) {
820 9
            $token = $tokens[$i];
821 9
            if (in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
822 9
                continue;
823
            }
824
825 9
            if ($inNamespace) {
826 9
                if (in_array($token[0], [T_NS_SEPARATOR, T_STRING], true)) {
827 9
                    $lastSeenNamespace .= $token[1];
828 9
                } elseif (is_string($token) && in_array($token, [';', '{'], true)) {
829 9
                    $inNamespace = false;
830
                }
831
            }
832
833 9
            if ($inClass) {
834 9
                $inClass = false;
835 9
                $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
836 9
                $this->staticReflection[$lastSeenClass]['properties'] = [];
837 9
                $this->staticReflection[$lastSeenClass]['methods'] = [];
838
            }
839
840 9
            if (T_NAMESPACE === $token[0]) {
841 9
                $lastSeenNamespace = '';
842 9
                $inNamespace = true;
843 9
            } elseif (T_CLASS === $token[0] && T_DOUBLE_COLON !== $tokens[$i-1][0]) {
844 9
                $inClass = true;
845 9
            } elseif (T_FUNCTION === $token[0]) {
846 4
                if (T_STRING === $tokens[$i+2][0]) {
847 4
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+2][1]);
848
                } elseif ($tokens[$i+2] == '&' && T_STRING === $tokens[$i+3][0]) {
849 4
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+3][1]);
850
                }
851 9
            } elseif (in_array($token[0], [T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED], true) && T_FUNCTION !== $tokens[$i+2][0]) {
852 5
                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
853
            }
854
        }
855 9
    }
856
857
    /**
858
     * @param string            $property
859
     * @param ClassMetadataInfo $metadata
860
     *
861
     * @return bool
862
     */
863 45
    protected function hasProperty($property, ClassMetadataInfo $metadata)
864
    {
865 45
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
866
            // don't generate property if its already on the base class.
867 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
868 2
            if ($reflClass->hasProperty($property)) {
869 1
                return true;
870
            }
871
        }
872
873
        // check traits for existing property
874 44
        foreach ($this->getTraits($metadata) as $trait) {
875 2
            if ($trait->hasProperty($property)) {
876 2
                return true;
877
            }
878
        }
879
880
        return (
881 44
            isset($this->staticReflection[$metadata->name]) &&
882 44
            in_array($property, $this->staticReflection[$metadata->name]['properties'], true)
883
        );
884
    }
885
886
    /**
887
     * @param string            $method
888
     * @param ClassMetadataInfo $metadata
889
     *
890
     * @return bool
891
     */
892 46
    protected function hasMethod($method, ClassMetadataInfo $metadata)
893
    {
894 46
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
895
            // don't generate method if its already on the base class.
896 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
897
898 2
            if ($reflClass->hasMethod($method)) {
899 1
                return true;
900
            }
901
        }
902
903
        // check traits for existing method
904 46
        foreach ($this->getTraits($metadata) as $trait) {
905 2
            if ($trait->hasMethod($method)) {
906 2
                return true;
907
            }
908
        }
909
910
        return (
911 46
            isset($this->staticReflection[$metadata->name]) &&
912 46
            in_array(strtolower($method), $this->staticReflection[$metadata->name]['methods'], true)
913
        );
914
    }
915
916
    /**
917
     * @param ClassMetadataInfo $metadata
918
     *
919
     * @return array
920
     *
921
     * @throws \ReflectionException
922
     */
923 46
    protected function getTraits(ClassMetadataInfo $metadata)
924
    {
925 46
        if (! ($metadata->reflClass !== null || class_exists($metadata->name))) {
926 40
            return [];
927
        }
928
929 7
        $reflClass = $metadata->reflClass ?? new \ReflectionClass($metadata->name);
930
931 7
        $traits = [];
932
933 7
        while ($reflClass !== false) {
934 7
            $traits = array_merge($traits, $reflClass->getTraits());
935
936 7
            $reflClass = $reflClass->getParentClass();
937
        }
938
939 7
        return $traits;
940
    }
941
942
    /**
943
     * @param ClassMetadataInfo $metadata
944
     *
945
     * @return bool
946
     */
947 45
    protected function hasNamespace(ClassMetadataInfo $metadata)
948
    {
949 45
        return (bool) strpos($metadata->name, '\\');
950
    }
951
952
    /**
953
     * @return bool
954
     */
955 46
    protected function extendsClass()
956
    {
957 46
        return (bool) $this->classToExtend;
958
    }
959
960
    /**
961
     * @return string
962
     */
963 2
    protected function getClassToExtend()
964
    {
965 2
        return $this->classToExtend;
966
    }
967
968
    /**
969
     * @return string
970
     */
971 1
    protected function getClassToExtendName()
972
    {
973 1
        $refl = new \ReflectionClass($this->getClassToExtend());
974
975 1
        return '\\' . $refl->getName();
976
    }
977
978
    /**
979
     * @param ClassMetadataInfo $metadata
980
     *
981
     * @return string
982
     */
983 46
    protected function getClassName(ClassMetadataInfo $metadata)
984
    {
985 46
        return ($pos = strrpos($metadata->name, '\\'))
986 46
            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
987
    }
988
989
    /**
990
     * @param ClassMetadataInfo $metadata
991
     *
992
     * @return string
993
     */
994 45
    protected function getNamespace(ClassMetadataInfo $metadata)
995
    {
996 45
        return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
997
    }
998
999
    /**
1000
     * @param ClassMetadataInfo $metadata
1001
     *
1002
     * @return string
1003
     */
1004 45
    protected function generateEntityDocBlock(ClassMetadataInfo $metadata)
1005
    {
1006 45
        $lines = [];
1007 45
        $lines[] = '/**';
1008 45
        $lines[] = ' * ' . $this->getClassName($metadata);
1009
1010 45
        if ($this->generateAnnotations) {
1011 45
            $lines[] = ' *';
1012
1013
            $methods = [
1014 45
                'generateTableAnnotation',
1015
                'generateInheritanceAnnotation',
1016
                'generateDiscriminatorColumnAnnotation',
1017
                'generateDiscriminatorMapAnnotation',
1018
                'generateEntityAnnotation',
1019
                'generateEntityListenerAnnotation',
1020
            ];
1021
1022 45
            foreach ($methods as $method) {
1023 45
                if ($code = $this->$method($metadata)) {
1024 45
                    $lines[] = ' * ' . $code;
1025
                }
1026
            }
1027
1028 45
            if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata->lifecycleCallbacks of type array<mixed,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...
1029 11
                $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks';
1030
            }
1031
        }
1032
1033 45
        $lines[] = ' */';
1034
1035 45
        return implode("\n", $lines);
1036
    }
1037
1038
    /**
1039
     * @param ClassMetadataInfo $metadata
1040
     *
1041
     * @return string
1042
     */
1043 45
    protected function generateEntityAnnotation(ClassMetadataInfo $metadata)
1044
    {
1045 45
        $prefix = '@' . $this->annotationsPrefix;
1046
1047 45
        if ($metadata->isEmbeddedClass) {
1048 12
            return $prefix . 'Embeddable';
1049
        }
1050
1051 41
        $customRepository = $metadata->customRepositoryClassName
1052 12
            ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")'
1053 41
            : '';
1054
1055 41
        return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository;
1056
    }
1057
1058
    /**
1059
     * @param ClassMetadataInfo $metadata
1060
     *
1061
     * @return string
1062
     */
1063 45
    protected function generateTableAnnotation(ClassMetadataInfo $metadata)
1064
    {
1065 45
        if ($metadata->isEmbeddedClass) {
1066 12
            return '';
1067
        }
1068
1069 41
        $table = [];
1070
1071 41
        if (isset($metadata->table['schema'])) {
1072
            $table[] = 'schema="' . $metadata->table['schema'] . '"';
1073
        }
1074
1075 41
        if (isset($metadata->table['name'])) {
1076 26
            $table[] = 'name="' . $metadata->table['name'] . '"';
1077
        }
1078
1079 41
        if (isset($metadata->table['options']) && $metadata->table['options']) {
1080 1
            $table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}';
1081
        }
1082
1083 41
        if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
1084 10
            $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
1085 10
            $table[] = 'uniqueConstraints={' . $constraints . '}';
1086
        }
1087
1088 41
        if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
1089 10
            $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']);
1090 10
            $table[] = 'indexes={' . $constraints . '}';
1091
        }
1092
1093 41
        return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
1094
    }
1095
1096
    /**
1097
     * @param string $constraintName
1098
     * @param array  $constraints
1099
     *
1100
     * @return string
1101
     */
1102 10
    protected function generateTableConstraints($constraintName, array $constraints)
1103
    {
1104 10
        $annotations = [];
1105 10
        foreach ($constraints as $name => $constraint) {
1106 10
            $columns = [];
1107 10
            foreach ($constraint['columns'] as $column) {
1108 10
                $columns[] = '"' . $column . '"';
1109
            }
1110 10
            $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})';
1111
        }
1112
1113 10
        return implode(', ', $annotations);
1114
    }
1115
1116
    /**
1117
     * @param ClassMetadataInfo $metadata
1118
     *
1119
     * @return string
1120
     */
1121 45
    protected function generateInheritanceAnnotation(ClassMetadataInfo $metadata)
1122
    {
1123 45
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1124 45
            return '';
1125
        }
1126
1127
        return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")';
1128
    }
1129
1130
    /**
1131
     * @param ClassMetadataInfo $metadata
1132
     *
1133
     * @return string
1134
     */
1135 45
    protected function generateDiscriminatorColumnAnnotation(ClassMetadataInfo $metadata)
1136
    {
1137 45
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1138 45
            return '';
1139
        }
1140
1141
        $discrColumn = $metadata->discriminatorColumn;
1142
        $columnDefinition = 'name="' . $discrColumn['name']
1143
            . '", type="' . $discrColumn['type']
1144
            . '", length=' . $discrColumn['length'];
1145
1146
        return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
1147
    }
1148
1149
    /**
1150
     * @param ClassMetadataInfo $metadata
1151
     *
1152
     * @return string
1153
     */
1154 45
    protected function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata)
1155
    {
1156 45
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1157 45
            return null;
1158
        }
1159
1160
        $inheritanceClassMap = [];
1161
1162
        foreach ($metadata->discriminatorMap as $type => $class) {
1163
            $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
1164
        }
1165
1166
        return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
1167
    }
1168
1169
    /**
1170
     * @param ClassMetadataInfo $metadata
1171
     *
1172
     * @return string
1173
     */
1174 45
    protected function generateEntityStubMethods(ClassMetadataInfo $metadata)
1175
    {
1176 45
        $methods = [];
1177
1178 45
        foreach ($metadata->fieldMappings as $fieldMapping) {
1179 44
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) {
1180
                continue;
1181
            }
1182
1183 44
            $nullableField = $this->nullableFieldExpression($fieldMapping);
1184
1185 44
            if ((!$metadata->isEmbeddedClass || !$this->embeddablesImmutable)
1186 44
                && (!isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType === ClassMetadataInfo::GENERATOR_TYPE_NONE)
1187 44
                && $code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)
1188
            ) {
1189 41
                $methods[] = $code;
1190
            }
1191
1192 44
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)) {
1193 44
                $methods[] = $code;
1194
            }
1195
        }
1196
1197 45
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1198 11
            if (isset($embeddedClass['declaredField'])) {
1199 1
                continue;
1200
            }
1201
1202 11
            if ( ! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) {
1203 10
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldName, $embeddedClass['class'])) {
1204 10
                    $methods[] = $code;
1205
                }
1206
            }
1207
1208 11
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldName, $embeddedClass['class'])) {
1209 11
                $methods[] = $code;
1210
            }
1211
        }
1212
1213 45
        foreach ($metadata->associationMappings as $associationMapping) {
1214 13
            if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
1215 12
                $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null;
1216 12
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1217 10
                    $methods[] = $code;
1218
                }
1219 12
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1220 12
                    $methods[] = $code;
1221
                }
1222 11
            } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1223 11
                if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1224 11
                    $methods[] = $code;
1225
                }
1226 11
                if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1227 11
                    $methods[] = $code;
1228
                }
1229 11
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], Collection::class)) {
1230 13
                    $methods[] = $code;
1231
                }
1232
            }
1233
        }
1234
1235 45
        return implode("\n\n", $methods);
1236
    }
1237
1238
    /**
1239
     * @param array $associationMapping
1240
     *
1241
     * @return bool
1242
     */
1243 12
    protected function isAssociationIsNullable(array $associationMapping)
1244
    {
1245 12
        if (isset($associationMapping['id']) && $associationMapping['id']) {
1246
            return false;
1247
        }
1248
1249 12
        if (isset($associationMapping['joinColumns'])) {
1250 2
            $joinColumns = $associationMapping['joinColumns'];
1251
        } else {
1252
            //@todo there is no way to retrieve targetEntity metadata
1253 10
            $joinColumns = [];
1254
        }
1255
1256 12
        foreach ($joinColumns as $joinColumn) {
1257 2
            if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
1258 2
                return false;
1259
            }
1260
        }
1261
1262 12
        return true;
1263
    }
1264
1265
    /**
1266
     * @param ClassMetadataInfo $metadata
1267
     *
1268
     * @return string
1269
     */
1270 46
    protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
1271
    {
1272 46
        if (empty($metadata->lifecycleCallbacks)) {
1273 43
            return '';
1274
        }
1275
1276 11
        $methods = [];
1277
1278 11
        foreach ($metadata->lifecycleCallbacks as $name => $callbacks) {
1279 11
            foreach ($callbacks as $callback) {
1280 11
                $methods[] = $this->generateLifecycleCallbackMethod($name, $callback, $metadata);
1281
            }
1282
        }
1283
1284 11
        return implode("\n\n", array_filter($methods));
1285
    }
1286
1287
    /**
1288
     * @param ClassMetadataInfo $metadata
1289
     *
1290
     * @return string
1291
     */
1292 46
    protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
1293
    {
1294 46
        $lines = [];
1295
1296 46
        foreach ($metadata->associationMappings as $associationMapping) {
1297 14
            if ($this->hasProperty($associationMapping['fieldName'], $metadata)) {
1298 5
                continue;
1299
            }
1300
1301 12
            $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata);
1302 12
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName']
1303 12
                     . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n";
1304
        }
1305
1306 46
        return implode("\n", $lines);
1307
    }
1308
1309
    /**
1310
     * @param ClassMetadataInfo $metadata
1311
     *
1312
     * @return string
1313
     */
1314 46
    protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata)
1315
    {
1316 46
        $lines = [];
1317
1318 46
        foreach ($metadata->fieldMappings as $fieldMapping) {
1319 45
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']]) ||
1320 45
                $this->hasProperty($fieldMapping['fieldName'], $metadata) ||
1321 45
                $metadata->isInheritedField($fieldMapping['fieldName'])
1322
            ) {
1323 5
                continue;
1324
            }
1325
1326 43
            $defaultValue = '';
1327 43
            if (isset($fieldMapping['options']['default'])) {
1328 14
                if ($fieldMapping['type'] === 'boolean' && $fieldMapping['options']['default'] === '1') {
1329 1
                    $defaultValue = ' = true';
1330
                } else {
1331 14
                    $defaultValue = ' = ' . var_export($fieldMapping['options']['default'], true);
1332
                }
1333
            }
1334
1335 43
            $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
1336 43
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName'] . $defaultValue . ";\n";
1337
        }
1338
1339 46
        return implode("\n", $lines);
1340
    }
1341
1342
    /**
1343
     * @param ClassMetadataInfo $metadata
1344
     *
1345
     * @return string
1346
     */
1347 46
    protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata)
1348
    {
1349 46
        $lines = [];
1350
1351 46
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1352 11
            if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) {
1353 3
                continue;
1354
            }
1355
1356 11
            $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass);
1357 11
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n";
1358
        }
1359
1360 46
        return implode("\n", $lines);
1361
    }
1362
1363
    /**
1364
     * @param ClassMetadataInfo $metadata
1365
     * @param string            $type
1366
     * @param string            $fieldName
1367
     * @param string|null       $typeHint
1368
     * @param string|null       $defaultValue
1369
     *
1370
     * @return string
1371
     */
1372 44
    protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
1373
    {
1374 44
        $methodName = $type . Inflector::classify($fieldName);
1375 44
        $variableName = Inflector::camelize($fieldName);
1376 44
        if (in_array($type, ["add", "remove"])) {
1377 11
            $methodName = Inflector::singularize($methodName);
1378 11
            $variableName = Inflector::singularize($variableName);
1379
        }
1380
1381 44
        if ($this->hasMethod($methodName, $metadata)) {
1382 6
            return '';
1383
        }
1384 44
        $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName);
1385
1386 44
        $var = sprintf('%sMethodTemplate', $type);
1387 44
        $template = static::$$var;
1388
1389 44
        $methodTypeHint = null;
1390 44
        $types          = Type::getTypesMap();
1391 44
        $variableType   = $typeHint ? $this->getType($typeHint) : null;
1392
1393 44
        if ($typeHint && ! isset($types[$typeHint])) {
1394 15
            $variableType   =  '\\' . ltrim($variableType, '\\');
1395 15
            $methodTypeHint =  '\\' . $typeHint . ' ';
1396
        }
1397
1398
        $replacements = [
1399 44
          '<description>'       => ucfirst($type) . ' ' . $variableName . '.',
1400 44
          '<methodTypeHint>'    => $methodTypeHint,
1401 44
          '<variableType>'      => $variableType . (null !== $defaultValue ? ('|' . $defaultValue) : ''),
1402 44
          '<variableName>'      => $variableName,
1403 44
          '<methodName>'        => $methodName,
1404 44
          '<fieldName>'         => $fieldName,
1405 44
          '<variableDefault>'   => ($defaultValue !== null ) ? (' = ' . $defaultValue) : '',
1406 44
          '<entity>'            => $this->getClassName($metadata)
1407
        ];
1408
1409 44
        $method = str_replace(
1410 44
            array_keys($replacements),
1411 44
            array_values($replacements),
1412 44
            $template
1413
        );
1414
1415 44
        return $this->prefixCodeWithSpaces($method);
1416
    }
1417
1418
    /**
1419
     * @param string            $name
1420
     * @param string            $methodName
1421
     * @param ClassMetadataInfo $metadata
1422
     *
1423
     * @return string
1424
     */
1425 11
    protected function generateLifecycleCallbackMethod($name, $methodName, ClassMetadataInfo $metadata)
1426
    {
1427 11
        if ($this->hasMethod($methodName, $metadata)) {
1428 3
            return '';
1429
        }
1430
1431 11
        $this->staticReflection[$metadata->name]['methods'][] = $methodName;
1432
1433
        $replacements = [
1434 11
            '<name>'        => $this->annotationsPrefix . ucfirst($name),
1435 11
            '<methodName>'  => $methodName,
1436
        ];
1437
1438 11
        $method = str_replace(
1439 11
            array_keys($replacements),
1440 11
            array_values($replacements),
1441 11
            static::$lifecycleCallbackMethodTemplate
1442
        );
1443
1444 11
        return $this->prefixCodeWithSpaces($method);
1445
    }
1446
1447
    /**
1448
     * @param array $joinColumn
1449
     *
1450
     * @return string
1451
     */
1452 12
    protected function generateJoinColumnAnnotation(array $joinColumn)
1453
    {
1454 12
        $joinColumnAnnot = [];
1455
1456 12
        if (isset($joinColumn['name'])) {
1457 12
            $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"';
1458
        }
1459
1460 12
        if (isset($joinColumn['referencedColumnName'])) {
1461 12
            $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"';
1462
        }
1463
1464 12
        if (isset($joinColumn['unique']) && $joinColumn['unique']) {
1465 1
            $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false');
1466
        }
1467
1468 12
        if (isset($joinColumn['nullable'])) {
1469 1
            $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false');
1470
        }
1471
1472 12
        if (isset($joinColumn['onDelete'])) {
1473 1
            $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
1474
        }
1475
1476 12
        if (isset($joinColumn['columnDefinition'])) {
1477 1
            $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
1478
        }
1479
1480 12
        return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
1481
    }
1482
1483
    /**
1484
     * @param array             $associationMapping
1485
     * @param ClassMetadataInfo $metadata
1486
     *
1487
     * @return string
1488
     */
1489 12
    protected function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
1490
    {
1491 12
        $lines = [];
1492 12
        $lines[] = $this->spaces . '/**';
1493
1494 12
        if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1495 12
            $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection';
1496
        } else {
1497 11
            $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\');
1498
        }
1499
1500 12
        if ($this->generateAnnotations) {
1501 12
            $lines[] = $this->spaces . ' *';
1502
1503 12
            if (isset($associationMapping['id']) && $associationMapping['id']) {
1504
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1505
1506
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1507
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1508
                }
1509
            }
1510
1511 12
            $type = null;
1512 12
            switch ($associationMapping['type']) {
1513 12
                case ClassMetadataInfo::ONE_TO_ONE:
1514 11
                    $type = 'OneToOne';
1515 11
                    break;
1516 12
                case ClassMetadataInfo::MANY_TO_ONE:
1517 1
                    $type = 'ManyToOne';
1518 1
                    break;
1519 12
                case ClassMetadataInfo::ONE_TO_MANY:
1520 1
                    $type = 'OneToMany';
1521 1
                    break;
1522 12
                case ClassMetadataInfo::MANY_TO_MANY:
1523 12
                    $type = 'ManyToMany';
1524 12
                    break;
1525
            }
1526 12
            $typeOptions = [];
1527
1528 12
            if (isset($associationMapping['targetEntity'])) {
1529 12
                $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"';
1530
            }
1531
1532 12
            if (isset($associationMapping['inversedBy'])) {
1533 1
                $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"';
1534
            }
1535
1536 12
            if (isset($associationMapping['mappedBy'])) {
1537 11
                $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"';
1538
            }
1539
1540 12
            if ($associationMapping['cascade']) {
1541 1
                $cascades = [];
1542
1543 1
                if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"';
1544 1
                if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"';
1545 1
                if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"';
1546 1
                if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
1547 1
                if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
1548
1549 1
                if (count($cascades) === 5) {
1550 1
                    $cascades = ['"all"'];
1551
                }
1552
1553 1
                $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
1554
            }
1555
1556 12
            if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
1557 1
                $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
1558
            }
1559
1560 12
            if (isset($associationMapping['fetch']) && $associationMapping['fetch'] !== ClassMetadataInfo::FETCH_LAZY) {
1561
                $fetchMap = [
1562 11
                    ClassMetadataInfo::FETCH_EXTRA_LAZY => 'EXTRA_LAZY',
1563
                    ClassMetadataInfo::FETCH_EAGER      => 'EAGER',
1564
                ];
1565
1566 11
                $typeOptions[] = 'fetch="' . $fetchMap[$associationMapping['fetch']] . '"';
1567
            }
1568
1569 12
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
1570
1571 12
            if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
1572 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({';
1573
1574 1
                $joinColumnsLines = [];
1575
1576 1
                foreach ($associationMapping['joinColumns'] as $joinColumn) {
1577 1
                    if ($joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn)) {
1578 1
                        $joinColumnsLines[] = $this->spaces . ' *   ' . $joinColumnAnnot;
1579
                    }
1580
                }
1581
1582 1
                $lines[] = implode(",\n", $joinColumnsLines);
1583 1
                $lines[] = $this->spaces . ' * })';
1584
            }
1585
1586 12
            if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) {
1587 12
                $joinTable = [];
1588 12
                $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"';
1589
1590 12
                if (isset($associationMapping['joinTable']['schema'])) {
1591
                    $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
1592
                }
1593
1594 12
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
1595 12
                $lines[] = $this->spaces . ' *   joinColumns={';
1596
1597 12
                $joinColumnsLines = [];
1598
1599 12
                foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
1600 12
                    $joinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1601
                }
1602
1603 12
                $lines[] = implode(",". PHP_EOL, $joinColumnsLines);
1604 12
                $lines[] = $this->spaces . ' *   },';
1605 12
                $lines[] = $this->spaces . ' *   inverseJoinColumns={';
1606
1607 12
                $inverseJoinColumnsLines = [];
1608
1609 12
                foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
1610 12
                    $inverseJoinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1611
                }
1612
1613 12
                $lines[] = implode(",". PHP_EOL, $inverseJoinColumnsLines);
1614 12
                $lines[] = $this->spaces . ' *   }';
1615 12
                $lines[] = $this->spaces . ' * )';
1616
            }
1617
1618 12
            if (isset($associationMapping['orderBy'])) {
1619 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({';
1620
1621 1
                foreach ($associationMapping['orderBy'] as $name => $direction) {
1622 1
                    $lines[] = $this->spaces . ' *     "' . $name . '"="' . $direction . '",';
1623
                }
1624
1625 1
                $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
1626 1
                $lines[] = $this->spaces . ' * })';
1627
            }
1628
        }
1629
1630 12
        $lines[] = $this->spaces . ' */';
1631
1632 12
        return implode("\n", $lines);
1633
    }
1634
1635
    /**
1636
     * @param array             $fieldMapping
1637
     * @param ClassMetadataInfo $metadata
1638
     *
1639
     * @return string
1640
     */
1641 43
    protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
1642
    {
1643 43
        $lines = [];
1644 43
        $lines[] = $this->spaces . '/**';
1645 43
        $lines[] = $this->spaces . ' * @var '
1646 43
            . $this->getType($fieldMapping['type'])
1647 43
            . ($this->nullableFieldExpression($fieldMapping) ? '|null' : '');
1648
1649 43
        if ($this->generateAnnotations) {
1650 43
            $lines[] = $this->spaces . ' *';
1651
1652 43
            $column = [];
1653 43
            if (isset($fieldMapping['columnName'])) {
1654 43
                $column[] = 'name="' . $fieldMapping['columnName'] . '"';
1655
            }
1656
1657 43
            if (isset($fieldMapping['type'])) {
1658 43
                $column[] = 'type="' . $fieldMapping['type'] . '"';
1659
            }
1660
1661 43
            if (isset($fieldMapping['length'])) {
1662 11
                $column[] = 'length=' . $fieldMapping['length'];
1663
            }
1664
1665 43
            if (isset($fieldMapping['precision'])) {
1666 4
                $column[] = 'precision=' .  $fieldMapping['precision'];
1667
            }
1668
1669 43
            if (isset($fieldMapping['scale'])) {
1670 4
                $column[] = 'scale=' . $fieldMapping['scale'];
1671
            }
1672
1673 43
            if (isset($fieldMapping['nullable'])) {
1674 11
                $column[] = 'nullable=' .  var_export($fieldMapping['nullable'], true);
1675
            }
1676
1677 43
            $options = [];
1678
1679 43
            if (isset($fieldMapping['options']['default']) && $fieldMapping['options']['default']) {
1680 14
                $options[] = '"default"="' . $fieldMapping['options']['default'] .'"';
1681
            }
1682
1683 43
            if (isset($fieldMapping['options']['unsigned']) && $fieldMapping['options']['unsigned']) {
1684 3
                $options[] = '"unsigned"=true';
1685
            }
1686
1687 43
            if (isset($fieldMapping['options']['fixed']) && $fieldMapping['options']['fixed']) {
1688 2
                $options[] = '"fixed"=true';
1689
            }
1690
1691 43
            if (isset($fieldMapping['options']['comment']) && $fieldMapping['options']['comment']) {
1692 5
                $options[] = '"comment"="' . str_replace('"', '""', $fieldMapping['options']['comment']) . '"';
1693
            }
1694
1695 43
            if (isset($fieldMapping['options']['collation']) && $fieldMapping['options']['collation']) {
1696 2
                $options[] = '"collation"="' . $fieldMapping['options']['collation'] .'"';
1697
            }
1698
1699 43
            if (isset($fieldMapping['options']['check']) && $fieldMapping['options']['check']) {
1700 4
                $options[] = '"check"="' . $fieldMapping['options']['check'] .'"';
1701
            }
1702
1703 43
            if ($options) {
1704 23
                $column[] = 'options={'.implode(',', $options).'}';
1705
            }
1706
1707 43
            if (isset($fieldMapping['columnDefinition'])) {
1708 1
                $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
1709
            }
1710
1711 43
            if (isset($fieldMapping['unique'])) {
1712 4
                $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
1713
            }
1714
1715 43
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
1716
1717 43
            if (isset($fieldMapping['id']) && $fieldMapping['id']) {
1718 39
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1719
1720 39
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1721 39
                    $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1722
                }
1723
1724 39
                if ($metadata->sequenceGeneratorDefinition) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata->sequenceGeneratorDefinition 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...
1725 1
                    $sequenceGenerator = [];
1726
1727 1
                    if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) {
1728 1
                        $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"';
1729
                    }
1730
1731 1
                    if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
1732 1
                        $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize'];
1733
                    }
1734
1735 1
                    if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
1736 1
                        $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue'];
1737
                    }
1738
1739 1
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
1740
                }
1741
            }
1742
1743 43
            if (isset($fieldMapping['version']) && $fieldMapping['version']) {
1744
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version';
1745
            }
1746
        }
1747
1748 43
        $lines[] = $this->spaces . ' */';
1749
1750 43
        return implode("\n", $lines);
1751
    }
1752
1753
    /**
1754
     * @param array $embeddedClass
1755
     *
1756
     * @return string
1757
     */
1758 11
    protected function generateEmbeddedPropertyDocBlock(array $embeddedClass)
1759
    {
1760 11
        $lines = [];
1761 11
        $lines[] = $this->spaces . '/**';
1762 11
        $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\');
1763
1764 11
        if ($this->generateAnnotations) {
1765 11
            $lines[] = $this->spaces . ' *';
1766
1767 11
            $embedded = ['class="' . $embeddedClass['class'] . '"'];
1768
1769 11
            if (isset($embeddedClass['columnPrefix'])) {
1770 9
                if (is_string($embeddedClass['columnPrefix'])) {
1771 1
                    $embedded[] = 'columnPrefix="' . $embeddedClass['columnPrefix'] . '"';
1772
                } else {
1773 8
                    $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true);
1774
                }
1775
            }
1776
1777 11
            $lines[] = $this->spaces . ' * @' .
1778 11
                $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')';
1779
        }
1780
1781 11
        $lines[] = $this->spaces . ' */';
1782
1783 11
        return implode("\n", $lines);
1784
    }
1785
1786 45
    private function generateEntityListenerAnnotation(ClassMetadataInfo $metadata): string
1787
    {
1788 45
        if (0 === \count($metadata->entityListeners)) {
1789 44
            return '';
1790
        }
1791
1792 1
        $processedClasses = [];
1793 1
        foreach ($metadata->entityListeners as $event => $eventListeners) {
1794 1
            foreach ($eventListeners as $eventListener) {
1795 1
                $processedClasses[] = '"' . $eventListener['class'] . '"';
1796
            }
1797
        }
1798
1799 1
        return \sprintf(
1800 1
            '%s%s({%s})',
1801 1
            '@' . $this->annotationsPrefix,
1802 1
            'EntityListeners',
1803 1
            \implode(',', \array_unique($processedClasses))
1804
        );
1805
    }
1806
1807
    /**
1808
     * @param string $code
1809
     * @param int    $num
1810
     *
1811
     * @return string
1812
     */
1813 45
    protected function prefixCodeWithSpaces($code, $num = 1)
1814
    {
1815 45
        $lines = explode("\n", $code);
1816
1817 45
        foreach ($lines as $key => $value) {
1818 45
            if ( ! empty($value)) {
1819 45
                $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
1820
            }
1821
        }
1822
1823 45
        return implode("\n", $lines);
1824
    }
1825
1826
    /**
1827
     * @param integer $type The inheritance type used by the class and its subclasses.
1828
     *
1829
     * @return string The literal string for the inheritance type.
1830
     *
1831
     * @throws \InvalidArgumentException When the inheritance type does not exist.
1832
     */
1833 1
    protected function getInheritanceTypeString($type)
1834
    {
1835 1
        if ( ! isset(static::$inheritanceTypeMap[$type])) {
1836 1
            throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type));
1837
        }
1838
1839 1
        return static::$inheritanceTypeMap[$type];
1840
    }
1841
1842
    /**
1843
     * @param integer $type The policy used for change-tracking for the mapped class.
1844
     *
1845
     * @return string The literal string for the change-tracking type.
1846
     *
1847
     * @throws \InvalidArgumentException When the change-tracking type does not exist.
1848
     */
1849 1
    protected function getChangeTrackingPolicyString($type)
1850
    {
1851 1
        if ( ! isset(static::$changeTrackingPolicyMap[$type])) {
1852 1
            throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type));
1853
        }
1854
1855 1
        return static::$changeTrackingPolicyMap[$type];
1856
    }
1857
1858
    /**
1859
     * @param integer $type The generator to use for the mapped class.
1860
     *
1861
     * @return string The literal string for the generator type.
1862
     *
1863
     * @throws \InvalidArgumentException    When the generator type does not exist.
1864
     */
1865 40
    protected function getIdGeneratorTypeString($type)
1866
    {
1867 40
        if ( ! isset(static::$generatorStrategyMap[$type])) {
1868 1
            throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type));
1869
        }
1870
1871 40
        return static::$generatorStrategyMap[$type];
1872
    }
1873
1874
    /**
1875
     * @param array $fieldMapping
1876
     *
1877
     * @return string|null
1878
     */
1879 45
    private function nullableFieldExpression(array $fieldMapping)
1880
    {
1881 45
        if (isset($fieldMapping['nullable']) && true === $fieldMapping['nullable']) {
1882 8
            return 'null';
1883
        }
1884
1885 45
        return null;
1886
    }
1887
1888
    /**
1889
     * Exports (nested) option elements.
1890
     *
1891
     * @param array $options
1892
     *
1893
     * @return string
1894
     */
1895 1
    private function exportTableOptions(array $options)
1896
    {
1897 1
        $optionsStr = [];
1898
1899 1
        foreach ($options as $name => $option) {
1900 1
            if (is_array($option)) {
1901 1
                $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}';
1902
            } else {
1903 1
                $optionsStr[] = '"' . $name . '"="' . (string) $option . '"';
1904
            }
1905
        }
1906
1907 1
        return implode(',', $optionsStr);
1908
    }
1909
}
1910