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

generateFieldMappingPropertyDocBlock()   F

Complexity

Conditions 33
Paths > 20000

Size

Total Lines 110
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 57
CRAP Score 33.0055

Importance

Changes 0
Metric Value
cc 33
eloc 57
nc 2490370
nop 2
dl 0
loc 110
rs 0
c 0
b 0
f 0
ccs 57
cts 58
cp 0.9828
crap 33.0055

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