Completed
Pull Request — master (#6849)
by Luís
42:22
created

EntityGenerator::generateEntityNamespace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 1
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
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\Util\Inflector;
24
use Doctrine\DBAL\Types\Type;
25
use Doctrine\ORM\Mapping\ClassMetadataInfo;
26
27
/**
28
 * Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances.
29
 *
30
 *     [php]
31
 *     $classes = $em->getClassMetadataFactory()->getAllMetadata();
32
 *
33
 *     $generator = new \Doctrine\ORM\Tools\EntityGenerator();
34
 *     $generator->setGenerateAnnotations(true);
35
 *     $generator->setGenerateStubMethods(true);
36
 *     $generator->setRegenerateEntityIfExists(false);
37
 *     $generator->setUpdateEntityIfExists(true);
38
 *     $generator->generate($classes, '/path/to/generate/entities');
39
 *
40
 *
41
 * @link    www.doctrine-project.org
42
 * @since   2.0
43
 * @author  Benjamin Eberlei <[email protected]>
44
 * @author  Guilherme Blanco <[email protected]>
45
 * @author  Jonathan Wage <[email protected]>
46
 * @author  Roman Borschel <[email protected]>
47
 */
48
class EntityGenerator
49
{
50
    /**
51
     * Specifies class fields should be protected.
52
     */
53
    const FIELD_VISIBLE_PROTECTED = 'protected';
54
55
    /**
56
     * Specifies class fields should be private.
57
     */
58
    const FIELD_VISIBLE_PRIVATE = 'private';
59
60
    /**
61
     * @var bool
62
     */
63
    protected $backupExisting = true;
64
65
    /**
66
     * The extension to use for written php files.
67
     *
68
     * @var string
69
     */
70
    protected $extension = '.php';
71
72
    /**
73
     * Whether or not the current ClassMetadataInfo instance is new or old.
74
     *
75
     * @var boolean
76
     */
77
    protected $isNew = true;
78
79
    /**
80
     * @var array
81
     */
82
    protected $staticReflection = [];
83
84
    /**
85
     * Number of spaces to use for indention in generated code.
86
     */
87
    protected $numSpaces = 4;
88
89
    /**
90
     * The actual spaces to use for indention.
91
     *
92
     * @var string
93
     */
94
    protected $spaces = '    ';
95
96
    /**
97
     * The class all generated entities should extend.
98
     *
99
     * @var string
100
     */
101
    protected $classToExtend;
102
103
    /**
104
     * Whether or not to generation annotations.
105
     *
106
     * @var boolean
107
     */
108
    protected $generateAnnotations = false;
109
110
    /**
111
     * @var string
112
     */
113
    protected $annotationsPrefix = '';
114
115
    /**
116
     * Whether or not to generate sub methods.
117
     *
118
     * @var boolean
119
     */
120
    protected $generateEntityStubMethods = false;
121
122
    /**
123
     * Whether or not to update the entity class if it exists already.
124
     *
125
     * @var boolean
126
     */
127
    protected $updateEntityIfExists = false;
128
129
    /**
130
     * Whether or not to re-generate entity class if it exists already.
131
     *
132
     * @var boolean
133
     */
134
    protected $regenerateEntityIfExists = false;
135
136
    /**
137
     * Visibility of the field
138
     *
139
     * @var string
140
     */
141
    protected $fieldVisibility = 'private';
142
143
    /**
144
     * Whether or not to make generated embeddables immutable.
145
     *
146
     * @var boolean.
147
     */
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...
148
    protected $embeddablesImmutable = false;
149
150
    /**
151
     * Hash-map for handle types.
152
     *
153
     * @var array
154
     */
155
    protected $typeAlias = [
156
        Type::DATETIMETZ    => '\DateTime',
157
        Type::DATETIME      => '\DateTime',
158
        Type::DATE          => '\DateTime',
159
        Type::TIME          => '\DateTime',
160
        Type::OBJECT        => '\stdClass',
161
        Type::INTEGER       => 'int',
162
        Type::BIGINT        => 'int',
163
        Type::SMALLINT      => 'int',
164
        Type::TEXT          => 'string',
165
        Type::BLOB          => 'string',
166
        Type::DECIMAL       => 'string',
167
        Type::JSON_ARRAY    => 'array',
168
        Type::SIMPLE_ARRAY  => 'array',
169
        Type::BOOLEAN       => 'bool',
170
    ];
171
172
    /**
173
     * Hash-map to handle generator types string.
174
     *
175
     * @var array
176
     */
177
    protected static $generatorStrategyMap = [
178
        ClassMetadataInfo::GENERATOR_TYPE_AUTO      => 'AUTO',
179
        ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE  => 'SEQUENCE',
180
        ClassMetadataInfo::GENERATOR_TYPE_TABLE     => 'TABLE',
181
        ClassMetadataInfo::GENERATOR_TYPE_IDENTITY  => 'IDENTITY',
182
        ClassMetadataInfo::GENERATOR_TYPE_NONE      => 'NONE',
183
        ClassMetadataInfo::GENERATOR_TYPE_UUID      => 'UUID',
184
        ClassMetadataInfo::GENERATOR_TYPE_CUSTOM    => 'CUSTOM'
185
    ];
186
187
    /**
188
     * Hash-map to handle the change tracking policy string.
189
     *
190
     * @var array
191
     */
192
    protected static $changeTrackingPolicyMap = [
193
        ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT  => 'DEFERRED_IMPLICIT',
194
        ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT  => 'DEFERRED_EXPLICIT',
195
        ClassMetadataInfo::CHANGETRACKING_NOTIFY             => 'NOTIFY',
196
    ];
197
198
    /**
199
     * Hash-map to handle the inheritance type string.
200
     *
201
     * @var array
202
     */
203
    protected static $inheritanceTypeMap = [
204
        ClassMetadataInfo::INHERITANCE_TYPE_NONE            => 'NONE',
205
        ClassMetadataInfo::INHERITANCE_TYPE_JOINED          => 'JOINED',
206
        ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE    => 'SINGLE_TABLE',
207
        ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS => 'TABLE_PER_CLASS',
208
    ];
209
210
    /**
211
     * @var string
212
     */
213
    protected static $classTemplate =
214
'<?php
215
216
<namespace>
217
<useStatement>
218
<entityAnnotation>
219
<entityClassName>
220
{
221
<entityBody>
222
}
223
';
224
225
    /**
226
     * @var string
227
     */
228
    protected static $getMethodTemplate =
229
'/**
230
 * <description>
231
 *
232
 * @return <variableType>
233
 */
234
public function <methodName>()
235
{
236
<spaces>return $this-><fieldName>;
237
}';
238
239
    /**
240
     * @var string
241
     */
242
    protected static $setMethodTemplate =
243
'/**
244
 * <description>
245
 *
246
 * @param <variableType> $<variableName>
247
 *
248
 * @return <entity>
249
 */
250
public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
251
{
252
<spaces>$this-><fieldName> = $<variableName>;
253
254
<spaces>return $this;
255
}';
256
257
    /**
258
     * @var string
259
     */
260
    protected static $addMethodTemplate =
261
'/**
262
 * <description>
263
 *
264
 * @param <variableType> $<variableName>
265
 *
266
 * @return <entity>
267
 */
268
public function <methodName>(<methodTypeHint>$<variableName>)
269
{
270
<spaces>$this-><fieldName>[] = $<variableName>;
271
272
<spaces>return $this;
273
}';
274
275
    /**
276
     * @var string
277
     */
278
    protected static $removeMethodTemplate =
279
'/**
280
 * <description>
281
 *
282
 * @param <variableType> $<variableName>
283
 *
284
 * @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
285
 */
286
public function <methodName>(<methodTypeHint>$<variableName>)
287
{
288
<spaces>return $this-><fieldName>->removeElement($<variableName>);
289
}';
290
291
    /**
292
     * @var string
293
     */
294
    protected static $lifecycleCallbackMethodTemplate =
295
'/**
296
 * @<name>
297
 */
298
public function <methodName>()
299
{
300
<spaces>// Add your code here
301
}';
302
303
    /**
304
     * @var string
305
     */
306
    protected static $constructorMethodTemplate =
307
'/**
308
 * Constructor
309
 */
310
public function __construct()
311
{
312
<spaces><collections>
313
}
314
';
315
316
    /**
317
     * @var string
318
     */
319
    protected static $embeddableConstructorMethodTemplate =
320
'/**
321
 * Constructor
322
 *
323
 * <paramTags>
324
 */
325
public function __construct(<params>)
326
{
327
<spaces><fields>
328
}
329
';
330
331
    /**
332
     * Constructor.
333
     */
334 51
    public function __construct()
335
    {
336 51
        if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
337 51
            $this->annotationsPrefix = 'ORM\\';
338
        }
339 51
    }
340
341
    /**
342
     * Generates and writes entity classes for the given array of ClassMetadataInfo instances.
343
     *
344
     * @param array  $metadatas
345
     * @param string $outputDirectory
346
     *
347
     * @return void
348
     */
349
    public function generate(array $metadatas, $outputDirectory)
350
    {
351
        foreach ($metadatas as $metadata) {
352
            $this->writeEntityClass($metadata, $outputDirectory);
353
        }
354
    }
355
356
    /**
357
     * Generates and writes entity class to disk for the given ClassMetadataInfo instance.
358
     *
359
     * @param ClassMetadataInfo $metadata
360
     * @param string            $outputDirectory
361
     *
362
     * @return void
363
     *
364
     * @throws \RuntimeException
365
     */
366 42
    public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
367
    {
368 42
        $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension;
369 42
        $dir = dirname($path);
370
371 42
        if ( ! is_dir($dir)) {
372 2
            mkdir($dir, 0775, true);
373
        }
374
375 42
        $this->isNew = ! file_exists($path) || $this->regenerateEntityIfExists;
376
377 42
        if ( ! $this->isNew) {
378 3
            $this->parseTokensInEntityFile(file_get_contents($path));
379
        } else {
380 41
            $this->staticReflection[$metadata->name] = ['properties' => [], 'methods' => []];
381
        }
382
383 42
        if ($this->backupExisting && file_exists($path)) {
384 3
            $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~";
385 3
            if (!copy($path, $backupPath)) {
386
                throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed.");
387
            }
388
        }
389
390
        // If entity doesn't exist or we're re-generating the entities entirely
391 42
        if ($this->isNew) {
392 41
            file_put_contents($path, $this->generateEntityClass($metadata));
393
        // If entity exists and we're allowed to update the entity class
394 3
        } elseif ($this->updateEntityIfExists) {
395 3
            file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path));
396
        }
397 42
        chmod($path, 0664);
398 42
    }
399
400
    /**
401
     * Generates a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance.
402
     *
403
     * @param ClassMetadataInfo $metadata
404
     *
405
     * @return string
406
     */
407 42
    public function generateEntityClass(ClassMetadataInfo $metadata)
408
    {
409
        $placeHolders = [
410 42
            '<namespace>',
411
            '<useStatement>',
412
            '<entityAnnotation>',
413
            '<entityClassName>',
414
            '<entityBody>'
415
        ];
416
417
        $replacements = [
418 42
            $this->generateEntityNamespace($metadata),
419 42
            $this->generateEntityUse(),
420 42
            $this->generateEntityDocBlock($metadata),
421 42
            $this->generateEntityClassName($metadata),
422 42
            $this->generateEntityBody($metadata)
423
        ];
424
425 42
        $code = str_replace($placeHolders, $replacements, static::$classTemplate);
426
427 42
        return str_replace('<spaces>', $this->spaces, $code);
428
    }
429
430
    /**
431
     * Generates the updated code for the given ClassMetadataInfo and entity at path.
432
     *
433
     * @param ClassMetadataInfo $metadata
434
     * @param string            $path
435
     *
436
     * @return string
437
     */
438 3
    public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
439
    {
440 3
        $currentCode = file_get_contents($path);
441
442 3
        $body = $this->generateEntityBody($metadata);
443 3
        $body = str_replace('<spaces>', $this->spaces, $body);
444 3
        $last = strrpos($currentCode, '}');
445
446 3
        return substr($currentCode, 0, $last) . $body . ($body ? "\n" : '') . "}\n";
447
    }
448
449
    /**
450
     * Sets the number of spaces the exported class should have.
451
     *
452
     * @param integer $numSpaces
453
     *
454
     * @return void
455
     */
456
    public function setNumSpaces($numSpaces)
457
    {
458
        $this->spaces = str_repeat(' ', $numSpaces);
459
        $this->numSpaces = $numSpaces;
460
    }
461
462
    /**
463
     * Sets the extension to use when writing php files to disk.
464
     *
465
     * @param string $extension
466
     *
467
     * @return void
468
     */
469
    public function setExtension($extension)
470
    {
471
        $this->extension = $extension;
472
    }
473
474
    /**
475
     * Sets the name of the class the generated classes should extend from.
476
     *
477
     * @param string $classToExtend
478
     *
479
     * @return void
480
     */
481 1
    public function setClassToExtend($classToExtend)
482
    {
483 1
        $this->classToExtend = $classToExtend;
484 1
    }
485
486
    /**
487
     * Sets whether or not to generate annotations for the entity.
488
     *
489
     * @param bool $bool
490
     *
491
     * @return void
492
     */
493 51
    public function setGenerateAnnotations($bool)
494
    {
495 51
        $this->generateAnnotations = $bool;
496 51
    }
497
498
    /**
499
     * Sets the class fields visibility for the entity (can either be private or protected).
500
     *
501
     * @param bool $visibility
502
     *
503
     * @return void
504
     *
505
     * @throws \InvalidArgumentException
506
     */
507 50
    public function setFieldVisibility($visibility)
508
    {
509 50
        if ($visibility !== static::FIELD_VISIBLE_PRIVATE && $visibility !== static::FIELD_VISIBLE_PROTECTED) {
510
            throw new \InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility);
511
        }
512
513 50
        $this->fieldVisibility = $visibility;
514 50
    }
515
516
    /**
517
     * Sets whether or not to generate immutable embeddables.
518
     *
519
     * @param boolean $embeddablesImmutable
520
     */
521 1
    public function setEmbeddablesImmutable($embeddablesImmutable)
522
    {
523 1
        $this->embeddablesImmutable = (boolean) $embeddablesImmutable;
524 1
    }
525
526
    /**
527
     * Sets an annotation prefix.
528
     *
529
     * @param string $prefix
530
     *
531
     * @return void
532
     */
533 51
    public function setAnnotationPrefix($prefix)
534
    {
535 51
        $this->annotationsPrefix = $prefix;
536 51
    }
537
538
    /**
539
     * Sets whether or not to try and update the entity if it already exists.
540
     *
541
     * @param bool $bool
542
     *
543
     * @return void
544
     */
545 51
    public function setUpdateEntityIfExists($bool)
546
    {
547 51
        $this->updateEntityIfExists = $bool;
548 51
    }
549
550
    /**
551
     * Sets whether or not to regenerate the entity if it exists.
552
     *
553
     * @param bool $bool
554
     *
555
     * @return void
556
     */
557 51
    public function setRegenerateEntityIfExists($bool)
558
    {
559 51
        $this->regenerateEntityIfExists = $bool;
560 51
    }
561
562
    /**
563
     * Sets whether or not to generate stub methods for the entity.
564
     *
565
     * @param bool $bool
566
     *
567
     * @return void
568
     */
569 51
    public function setGenerateStubMethods($bool)
570
    {
571 51
        $this->generateEntityStubMethods = $bool;
572 51
    }
573
574
    /**
575
     * Should an existing entity be backed up if it already exists?
576
     *
577
     * @param bool $bool
578
     *
579
     * @return void
580
     */
581 1
    public function setBackupExisting($bool)
582
    {
583 1
        $this->backupExisting = $bool;
584 1
    }
585
586
    /**
587
     * @param string $type
588
     *
589
     * @return string
590
     */
591 42
    protected function getType($type)
592
    {
593 42
        if (isset($this->typeAlias[$type])) {
594 41
            return $this->typeAlias[$type];
595
        }
596
597 26
        return $type;
598
    }
599
600
    /**
601
     * @param ClassMetadataInfo $metadata
602
     *
603
     * @return string
604
     */
605 42
    protected function generateEntityNamespace(ClassMetadataInfo $metadata)
606
    {
607 42
        if (! $this->hasNamespace($metadata)) {
608 2
            return '';
609
        }
610
611 42
        return 'namespace ' . $this->getNamespace($metadata) .';';
612
    }
613
614
    /**
615
     * @return string
616
     */
617 42
    protected function generateEntityUse()
618
    {
619 42
        if (! $this->generateAnnotations) {
620
            return '';
621
        }
622
623 42
        return "\n".'use Doctrine\ORM\Mapping as ORM;'."\n";
624
    }
625
626
    /**
627
     * @param ClassMetadataInfo $metadata
628
     *
629
     * @return string
630
     */
631 42
    protected function generateEntityClassName(ClassMetadataInfo $metadata)
632
    {
633 42
        return 'class ' . $this->getClassName($metadata) .
634 42
            ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
635
    }
636
637
    /**
638
     * @param ClassMetadataInfo $metadata
639
     *
640
     * @return string
641
     */
642 43
    protected function generateEntityBody(ClassMetadataInfo $metadata)
643
    {
644 43
        $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata);
645 43
        $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata);
646 43
        $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata);
647 43
        $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null;
648 43
        $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata);
649
650 43
        $code = [];
651
652 43
        if ($fieldMappingProperties) {
653 40
            $code[] = $fieldMappingProperties;
654
        }
655
656 43
        if ($embeddedProperties) {
657 10
            $code[] = $embeddedProperties;
658
        }
659
660 43
        if ($associationMappingProperties) {
661 11
            $code[] = $associationMappingProperties;
662
        }
663
664 43
        $code[] = $this->generateEntityConstructor($metadata);
665
666 43
        if ($stubMethods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stubMethods of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
667 41
            $code[] = $stubMethods;
668
        }
669
670 43
        if ($lifecycleCallbackMethods) {
671 10
            $code[] = $lifecycleCallbackMethods;
672
        }
673
674 43
        return implode("\n", $code);
675
    }
676
677
    /**
678
     * @param ClassMetadataInfo $metadata
679
     *
680
     * @return string
681
     */
682 43
    protected function generateEntityConstructor(ClassMetadataInfo $metadata)
683
    {
684 43
        if ($this->hasMethod('__construct', $metadata)) {
685 2
            return '';
686
        }
687
688 43
        if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) {
689 1
            return $this->generateEmbeddableConstructor($metadata);
690
        }
691
692 42
        $collections = [];
693
694 42
        foreach ($metadata->associationMappings as $mapping) {
695 13
            if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
696 13
                $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
697
            }
698
        }
699
700 42
        if ($collections) {
701 11
            return $this->prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->spaces, $collections), static::$constructorMethodTemplate));
702
        }
703
704 38
        return '';
705
    }
706
707
    /**
708
     * @param ClassMetadataInfo $metadata
709
     *
710
     * @return string
711
     */
712 1
    private function generateEmbeddableConstructor(ClassMetadataInfo $metadata)
713
    {
714 1
        $paramTypes = [];
715 1
        $paramVariables = [];
716 1
        $params = [];
717 1
        $fields = [];
718
719
        // Resort fields to put optional fields at the end of the method signature.
720 1
        $requiredFields = [];
721 1
        $optionalFields = [];
722
723 1
        foreach ($metadata->fieldMappings as $fieldMapping) {
724 1
            if (empty($fieldMapping['nullable'])) {
725 1
                $requiredFields[] = $fieldMapping;
726
727 1
                continue;
728
            }
729
730 1
            $optionalFields[] = $fieldMapping;
731
        }
732
733 1
        $fieldMappings = array_merge($requiredFields, $optionalFields);
734
735 1
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
736 1
            $paramType = '\\' . ltrim($embeddedClass['class'], '\\');
737 1
            $paramVariable = '$' . $fieldName;
738
739 1
            $paramTypes[] = $paramType;
740 1
            $paramVariables[] = $paramVariable;
741 1
            $params[] = $paramType . ' ' . $paramVariable;
742 1
            $fields[] = '$this->' . $fieldName . ' = ' . $paramVariable . ';';
743
        }
744
745 1
        foreach ($fieldMappings as $fieldMapping) {
746 1
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) {
747
                continue;
748
            }
749
750 1
            $paramTypes[] = $this->getType($fieldMapping['type']) . (!empty($fieldMapping['nullable']) ? '|null' : '');
751 1
            $param = '$' . $fieldMapping['fieldName'];
752 1
            $paramVariables[] = $param;
753
754 1
            if ($fieldMapping['type'] === 'datetime') {
755 1
                $param = $this->getType($fieldMapping['type']) . ' ' . $param;
756
            }
757
758 1
            if (!empty($fieldMapping['nullable'])) {
759 1
                $param .= ' = null';
760
            }
761
762 1
            $params[] = $param;
763
764 1
            $fields[] = '$this->' . $fieldMapping['fieldName'] . ' = $' . $fieldMapping['fieldName'] . ';';
765
        }
766
767 1
        $maxParamTypeLength = max(array_map('strlen', $paramTypes));
768 1
        $paramTags = array_map(
769 1
            function ($type, $variable) use ($maxParamTypeLength) {
770 1
                return '@param ' . $type . str_repeat(' ', $maxParamTypeLength - strlen($type) + 1) . $variable;
771 1
            },
772 1
            $paramTypes,
773 1
            $paramVariables
774
        );
775
776
        // Generate multi line constructor if the signature exceeds 120 characters.
777 1
        if (array_sum(array_map('strlen', $params)) + count($params) * 2 + 29 > 120) {
778 1
            $delimiter = "\n" . $this->spaces;
779 1
            $params = $delimiter . implode(',' . $delimiter, $params) . "\n";
780
        } else {
781 1
            $params = implode(', ', $params);
782
        }
783
784
        $replacements = [
785 1
            '<paramTags>' => implode("\n * ", $paramTags),
786 1
            '<params>'    => $params,
787 1
            '<fields>'    => implode("\n" . $this->spaces, $fields),
788
        ];
789
790 1
        $constructor = str_replace(
791 1
            array_keys($replacements),
792 1
            array_values($replacements),
793 1
            static::$embeddableConstructorMethodTemplate
794
        );
795
796 1
        return $this->prefixCodeWithSpaces($constructor);
797
    }
798
799
    /**
800
     * @todo this won't work if there is a namespace in brackets and a class outside of it.
801
     *
802
     * @param string $src
803
     *
804
     * @return void
805
     */
806 8
    protected function parseTokensInEntityFile($src)
807
    {
808 8
        $tokens = token_get_all($src);
809 8
        $tokensCount = count($tokens);
810 8
        $lastSeenNamespace = '';
811 8
        $lastSeenClass = false;
812
813 8
        $inNamespace = false;
814 8
        $inClass = false;
815
816 8
        for ($i = 0; $i < $tokensCount; $i++) {
817 8
            $token = $tokens[$i];
818 8
            if (in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
819 8
                continue;
820
            }
821
822 8
            if ($inNamespace) {
823 8
                if (in_array($token[0], [T_NS_SEPARATOR, T_STRING], true)) {
824 8
                    $lastSeenNamespace .= $token[1];
825 8
                } elseif (is_string($token) && in_array($token, [';', '{'], true)) {
826 8
                    $inNamespace = false;
827
                }
828
            }
829
830 8
            if ($inClass) {
831 8
                $inClass = false;
832 8
                $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
833 8
                $this->staticReflection[$lastSeenClass]['properties'] = [];
834 8
                $this->staticReflection[$lastSeenClass]['methods'] = [];
835
            }
836
837 8
            if (T_NAMESPACE === $token[0]) {
838 8
                $lastSeenNamespace = '';
839 8
                $inNamespace = true;
840 8
            } elseif (T_CLASS === $token[0] && T_DOUBLE_COLON !== $tokens[$i-1][0]) {
841 8
                $inClass = true;
842 8
            } elseif (T_FUNCTION === $token[0]) {
843 3
                if (T_STRING === $tokens[$i+2][0]) {
844 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+2][1]);
845
                } elseif ($tokens[$i+2] == '&' && T_STRING === $tokens[$i+3][0]) {
846 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+3][1]);
847
                }
848 8
            } elseif (in_array($token[0], [T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED], true) && T_FUNCTION !== $tokens[$i+2][0]) {
849 4
                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
850
            }
851
        }
852 8
    }
853
854
    /**
855
     * @param string            $property
856
     * @param ClassMetadataInfo $metadata
857
     *
858
     * @return bool
859
     */
860 42 View Code Duplication
    protected function hasProperty($property, ClassMetadataInfo $metadata)
861
    {
862 42
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
863
            // don't generate property if its already on the base class.
864 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
865 2
            if ($reflClass->hasProperty($property)) {
866 1
                return true;
867
            }
868
        }
869
870
        // check traits for existing property
871 41
        foreach ($this->getTraits($metadata) as $trait) {
872 2
            if ($trait->hasProperty($property)) {
873 2
                return true;
874
            }
875
        }
876
877
        return (
878 41
            isset($this->staticReflection[$metadata->name]) &&
879 41
            in_array($property, $this->staticReflection[$metadata->name]['properties'], true)
880
        );
881
    }
882
883
    /**
884
     * @param string            $method
885
     * @param ClassMetadataInfo $metadata
886
     *
887
     * @return bool
888
     */
889 43 View Code Duplication
    protected function hasMethod($method, ClassMetadataInfo $metadata)
890
    {
891 43
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
892
            // don't generate method if its already on the base class.
893 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
894
895 2
            if ($reflClass->hasMethod($method)) {
896 1
                return true;
897
            }
898
        }
899
900
        // check traits for existing method
901 43
        foreach ($this->getTraits($metadata) as $trait) {
902 2
            if ($trait->hasMethod($method)) {
903 2
                return true;
904
            }
905
        }
906
907
        return (
908 43
            isset($this->staticReflection[$metadata->name]) &&
909 43
            in_array(strtolower($method), $this->staticReflection[$metadata->name]['methods'], true)
910
        );
911
    }
912
913
    /**
914
     * @param ClassMetadataInfo $metadata
915
     *
916
     * @return array
917
     *
918
     * @throws \ReflectionException
919
     */
920 43
    protected function getTraits(ClassMetadataInfo $metadata)
921
    {
922 43
        if (! ($metadata->reflClass !== null || class_exists($metadata->name))) {
923 37
            return [];
924
        }
925
926 7
        $reflClass = $metadata->reflClass ?? new \ReflectionClass($metadata->name);
927
928 7
        $traits = [];
929
930 7
        while ($reflClass !== false) {
931 7
            $traits = array_merge($traits, $reflClass->getTraits());
932
933 7
            $reflClass = $reflClass->getParentClass();
934
        }
935
936 7
        return $traits;
937
    }
938
939
    /**
940
     * @param ClassMetadataInfo $metadata
941
     *
942
     * @return bool
943
     */
944 42
    protected function hasNamespace(ClassMetadataInfo $metadata)
945
    {
946 42
        return (bool) strpos($metadata->name, '\\');
947
    }
948
949
    /**
950
     * @return bool
951
     */
952 43
    protected function extendsClass()
953
    {
954 43
        return (bool) $this->classToExtend;
955
    }
956
957
    /**
958
     * @return string
959
     */
960 2
    protected function getClassToExtend()
961
    {
962 2
        return $this->classToExtend;
963
    }
964
965
    /**
966
     * @return string
967
     */
968 1
    protected function getClassToExtendName()
969
    {
970 1
        $refl = new \ReflectionClass($this->getClassToExtend());
971
972 1
        return '\\' . $refl->getName();
973
    }
974
975
    /**
976
     * @param ClassMetadataInfo $metadata
977
     *
978
     * @return string
979
     */
980 43
    protected function getClassName(ClassMetadataInfo $metadata)
981
    {
982 43
        return ($pos = strrpos($metadata->name, '\\'))
983 43
            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
984
    }
985
986
    /**
987
     * @param ClassMetadataInfo $metadata
988
     *
989
     * @return string
990
     */
991 42
    protected function getNamespace(ClassMetadataInfo $metadata)
992
    {
993 42
        return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
994
    }
995
996
    /**
997
     * @param ClassMetadataInfo $metadata
998
     *
999
     * @return string
1000
     */
1001 42
    protected function generateEntityDocBlock(ClassMetadataInfo $metadata)
1002
    {
1003 42
        $lines = [];
1004 42
        $lines[] = '/**';
1005 42
        $lines[] = ' * ' . $this->getClassName($metadata);
1006
1007 42
        if ($this->generateAnnotations) {
1008 42
            $lines[] = ' *';
1009
1010
            $methods = [
1011 42
                'generateTableAnnotation',
1012
                'generateInheritanceAnnotation',
1013
                'generateDiscriminatorColumnAnnotation',
1014
                'generateDiscriminatorMapAnnotation',
1015
                'generateEntityAnnotation',
1016
                'generateEntityListenerAnnotation',
1017
            ];
1018
1019 42
            foreach ($methods as $method) {
1020 42
                if ($code = $this->$method($metadata)) {
1021 42
                    $lines[] = ' * ' . $code;
1022
                }
1023
            }
1024
1025 42
            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...
1026 10
                $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks';
1027
            }
1028
        }
1029
1030 42
        $lines[] = ' */';
1031
1032 42
        return implode("\n", $lines);
1033
    }
1034
1035
    /**
1036
     * @param ClassMetadataInfo $metadata
1037
     *
1038
     * @return string
1039
     */
1040 42
    protected function generateEntityAnnotation(ClassMetadataInfo $metadata)
1041
    {
1042 42
        $prefix = '@' . $this->annotationsPrefix;
1043
1044 42
        if ($metadata->isEmbeddedClass) {
1045 11
            return $prefix . 'Embeddable';
1046
        }
1047
1048 38
        $customRepository = $metadata->customRepositoryClassName
1049 11
            ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")'
1050 38
            : '';
1051
1052 38
        return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository;
1053
    }
1054
1055
    /**
1056
     * @param ClassMetadataInfo $metadata
1057
     *
1058
     * @return string
1059
     */
1060 42
    protected function generateTableAnnotation(ClassMetadataInfo $metadata)
1061
    {
1062 42
        if ($metadata->isEmbeddedClass) {
1063 11
            return '';
1064
        }
1065
1066 38
        $table = [];
1067
1068 38 View Code Duplication
        if (isset($metadata->table['schema'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1069
            $table[] = 'schema="' . $metadata->table['schema'] . '"';
1070
        }
1071
1072 38 View Code Duplication
        if (isset($metadata->table['name'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1073 24
            $table[] = 'name="' . $metadata->table['name'] . '"';
1074
        }
1075
1076 38
        if (isset($metadata->table['options']) && $metadata->table['options']) {
1077 1
            $table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}';
1078
        }
1079
1080 38 View Code Duplication
        if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1081 9
            $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
1082 9
            $table[] = 'uniqueConstraints={' . $constraints . '}';
1083
        }
1084
1085 38 View Code Duplication
        if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1086 9
            $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']);
1087 9
            $table[] = 'indexes={' . $constraints . '}';
1088
        }
1089
1090 38
        return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
1091
    }
1092
1093
    /**
1094
     * @param string $constraintName
1095
     * @param array  $constraints
1096
     *
1097
     * @return string
1098
     */
1099 9
    protected function generateTableConstraints($constraintName, array $constraints)
1100
    {
1101 9
        $annotations = [];
1102 9
        foreach ($constraints as $name => $constraint) {
1103 9
            $columns = [];
1104 9
            foreach ($constraint['columns'] as $column) {
1105 9
                $columns[] = '"' . $column . '"';
1106
            }
1107 9
            $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})';
1108
        }
1109
1110 9
        return implode(', ', $annotations);
1111
    }
1112
1113
    /**
1114
     * @param ClassMetadataInfo $metadata
1115
     *
1116
     * @return string
1117
     */
1118 42
    protected function generateInheritanceAnnotation(ClassMetadataInfo $metadata)
1119
    {
1120 42
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1121 42
            return '';
1122
        }
1123
1124
        return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")';
1125
    }
1126
1127
    /**
1128
     * @param ClassMetadataInfo $metadata
1129
     *
1130
     * @return string
1131
     */
1132 42
    protected function generateDiscriminatorColumnAnnotation(ClassMetadataInfo $metadata)
1133
    {
1134 42
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1135 42
            return '';
1136
        }
1137
1138
        $discrColumn = $metadata->discriminatorColumn;
1139
        $columnDefinition = 'name="' . $discrColumn['name']
1140
            . '", type="' . $discrColumn['type']
1141
            . '", length=' . $discrColumn['length'];
1142
1143
        return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
1144
    }
1145
1146
    /**
1147
     * @param ClassMetadataInfo $metadata
1148
     *
1149
     * @return string
1150
     */
1151 42
    protected function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata)
1152
    {
1153 42
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1154 42
            return null;
1155
        }
1156
1157
        $inheritanceClassMap = [];
1158
1159
        foreach ($metadata->discriminatorMap as $type => $class) {
1160
            $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
1161
        }
1162
1163
        return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
1164
    }
1165
1166
    /**
1167
     * @param ClassMetadataInfo $metadata
1168
     *
1169
     * @return string
1170
     */
1171 42
    protected function generateEntityStubMethods(ClassMetadataInfo $metadata)
1172
    {
1173 42
        $methods = [];
1174
1175 42
        foreach ($metadata->fieldMappings as $fieldMapping) {
1176 41
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) {
1177
                continue;
1178
            }
1179
1180 41
            $nullableField = $this->nullableFieldExpression($fieldMapping);
1181
1182 41
            if ((!$metadata->isEmbeddedClass || !$this->embeddablesImmutable)
1183 41
                && (!isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType === ClassMetadataInfo::GENERATOR_TYPE_NONE)
1184 41
                && $code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)
1185
            ) {
1186 38
                $methods[] = $code;
1187
            }
1188
1189 41
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)) {
1190 41
                $methods[] = $code;
1191
            }
1192
        }
1193
1194 42
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1195 10
            if (isset($embeddedClass['declaredField'])) {
1196 1
                continue;
1197
            }
1198
1199 10
            if ( ! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) {
1200 9
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldName, $embeddedClass['class'])) {
1201 9
                    $methods[] = $code;
1202
                }
1203
            }
1204
1205 10
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldName, $embeddedClass['class'])) {
1206 10
                $methods[] = $code;
1207
            }
1208
        }
1209
1210 42
        foreach ($metadata->associationMappings as $associationMapping) {
1211 12
            if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
1212 11
                $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null;
1213 11
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1214 9
                    $methods[] = $code;
1215
                }
1216 11
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1217 11
                    $methods[] = $code;
1218
                }
1219 10
            } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1220 10 View Code Duplication
                if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1221 10
                    $methods[] = $code;
1222
                }
1223 10 View Code Duplication
                if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1224 10
                    $methods[] = $code;
1225
                }
1226 10 View Code Duplication
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], Collection::class)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1227 12
                    $methods[] = $code;
1228
                }
1229
            }
1230
        }
1231
1232 42
        return implode("\n\n", $methods);
1233
    }
1234
1235
    /**
1236
     * @param array $associationMapping
1237
     *
1238
     * @return bool
1239
     */
1240 11
    protected function isAssociationIsNullable(array $associationMapping)
1241
    {
1242 11
        if (isset($associationMapping['id']) && $associationMapping['id']) {
1243
            return false;
1244
        }
1245
1246 11
        if (isset($associationMapping['joinColumns'])) {
1247 2
            $joinColumns = $associationMapping['joinColumns'];
1248
        } else {
1249
            //@todo there is no way to retrieve targetEntity metadata
1250 9
            $joinColumns = [];
1251
        }
1252
1253 11
        foreach ($joinColumns as $joinColumn) {
1254 2
            if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
1255 2
                return false;
1256
            }
1257
        }
1258
1259 11
        return true;
1260
    }
1261
1262
    /**
1263
     * @param ClassMetadataInfo $metadata
1264
     *
1265
     * @return string
1266
     */
1267 43
    protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
1268
    {
1269 43
        if (empty($metadata->lifecycleCallbacks)) {
1270 40
            return '';
1271
        }
1272
1273 10
        $methods = [];
1274
1275 10
        foreach ($metadata->lifecycleCallbacks as $name => $callbacks) {
1276 10
            foreach ($callbacks as $callback) {
1277 10
                $methods[] = $this->generateLifecycleCallbackMethod($name, $callback, $metadata);
1278
            }
1279
        }
1280
1281 10
        return implode("\n\n", array_filter($methods));
1282
    }
1283
1284
    /**
1285
     * @param ClassMetadataInfo $metadata
1286
     *
1287
     * @return string
1288
     */
1289 43
    protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
1290
    {
1291 43
        $lines = [];
1292
1293 43
        foreach ($metadata->associationMappings as $associationMapping) {
1294 13
            if ($this->hasProperty($associationMapping['fieldName'], $metadata)) {
1295 4
                continue;
1296
            }
1297
1298 11
            $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata);
1299 11
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName']
1300 11
                     . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n";
1301
        }
1302
1303 43
        return implode("\n", $lines);
1304
    }
1305
1306
    /**
1307
     * @param ClassMetadataInfo $metadata
1308
     *
1309
     * @return string
1310
     */
1311 43
    protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata)
1312
    {
1313 43
        $lines = [];
1314
1315 43
        foreach ($metadata->fieldMappings as $fieldMapping) {
1316 42
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']]) ||
1317 42
                $this->hasProperty($fieldMapping['fieldName'], $metadata) ||
1318 42
                $metadata->isInheritedField($fieldMapping['fieldName'])
1319
            ) {
1320 4
                continue;
1321
            }
1322
1323 40
            $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
1324 40
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName']
1325 40
                     . (isset($fieldMapping['options']['default']) ? ' = ' . var_export($fieldMapping['options']['default'], true) : null) . ";\n";
1326
        }
1327
1328 43
        return implode("\n", $lines);
1329
    }
1330
1331
    /**
1332
     * @param ClassMetadataInfo $metadata
1333
     *
1334
     * @return string
1335
     */
1336 43
    protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata)
1337
    {
1338 43
        $lines = [];
1339
1340 43
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1341 10
            if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) {
1342 2
                continue;
1343
            }
1344
1345 10
            $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass);
1346 10
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n";
1347
        }
1348
1349 43
        return implode("\n", $lines);
1350
    }
1351
1352
    /**
1353
     * @param ClassMetadataInfo $metadata
1354
     * @param string            $type
1355
     * @param string            $fieldName
1356
     * @param string|null       $typeHint
1357
     * @param string|null       $defaultValue
1358
     *
1359
     * @return string
1360
     */
1361 41
    protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
1362
    {
1363 41
        $methodName = $type . Inflector::classify($fieldName);
1364 41
        $variableName = Inflector::camelize($fieldName);
1365 41
        if (in_array($type, ["add", "remove"])) {
1366 10
            $methodName = Inflector::singularize($methodName);
1367 10
            $variableName = Inflector::singularize($variableName);
1368
        }
1369
1370 41
        if ($this->hasMethod($methodName, $metadata)) {
1371 5
            return '';
1372
        }
1373 41
        $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName);
1374
1375 41
        $var = sprintf('%sMethodTemplate', $type);
1376 41
        $template = static::$$var;
1377
1378 41
        $methodTypeHint = null;
1379 41
        $types          = Type::getTypesMap();
1380 41
        $variableType   = $typeHint ? $this->getType($typeHint) : null;
1381
1382 41
        if ($typeHint && ! isset($types[$typeHint])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $typeHint of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1383 14
            $variableType   =  '\\' . ltrim($variableType, '\\');
1384 14
            $methodTypeHint =  '\\' . $typeHint . ' ';
1385
        }
1386
1387
        $replacements = [
1388 41
          '<description>'       => ucfirst($type) . ' ' . $variableName . '.',
1389 41
          '<methodTypeHint>'    => $methodTypeHint,
1390 41
          '<variableType>'      => $variableType . (null !== $defaultValue ? ('|' . $defaultValue) : ''),
1391 41
          '<variableName>'      => $variableName,
1392 41
          '<methodName>'        => $methodName,
1393 41
          '<fieldName>'         => $fieldName,
1394 41
          '<variableDefault>'   => ($defaultValue !== null ) ? (' = ' . $defaultValue) : '',
1395 41
          '<entity>'            => $this->getClassName($metadata)
1396
        ];
1397
1398 41
        $method = str_replace(
1399 41
            array_keys($replacements),
1400 41
            array_values($replacements),
1401 41
            $template
1402
        );
1403
1404 41
        return $this->prefixCodeWithSpaces($method);
1405
    }
1406
1407
    /**
1408
     * @param string            $name
1409
     * @param string            $methodName
1410
     * @param ClassMetadataInfo $metadata
1411
     *
1412
     * @return string
1413
     */
1414 10
    protected function generateLifecycleCallbackMethod($name, $methodName, ClassMetadataInfo $metadata)
1415
    {
1416 10
        if ($this->hasMethod($methodName, $metadata)) {
1417 2
            return '';
1418
        }
1419
1420 10
        $this->staticReflection[$metadata->name]['methods'][] = $methodName;
1421
1422
        $replacements = [
1423 10
            '<name>'        => $this->annotationsPrefix . ucfirst($name),
1424 10
            '<methodName>'  => $methodName,
1425
        ];
1426
1427 10
        $method = str_replace(
1428 10
            array_keys($replacements),
1429 10
            array_values($replacements),
1430 10
            static::$lifecycleCallbackMethodTemplate
1431
        );
1432
1433 10
        return $this->prefixCodeWithSpaces($method);
1434
    }
1435
1436
    /**
1437
     * @param array $joinColumn
1438
     *
1439
     * @return string
1440
     */
1441 11
    protected function generateJoinColumnAnnotation(array $joinColumn)
1442
    {
1443 11
        $joinColumnAnnot = [];
1444
1445 11
        if (isset($joinColumn['name'])) {
1446 11
            $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"';
1447
        }
1448
1449 11
        if (isset($joinColumn['referencedColumnName'])) {
1450 11
            $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"';
1451
        }
1452
1453 11
        if (isset($joinColumn['unique']) && $joinColumn['unique']) {
1454 1
            $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false');
1455
        }
1456
1457 11
        if (isset($joinColumn['nullable'])) {
1458 1
            $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false');
1459
        }
1460
1461 11
        if (isset($joinColumn['onDelete'])) {
1462 1
            $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
1463
        }
1464
1465 11
        if (isset($joinColumn['columnDefinition'])) {
1466 1
            $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
1467
        }
1468
1469 11
        return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
1470
    }
1471
1472
    /**
1473
     * @param array             $associationMapping
1474
     * @param ClassMetadataInfo $metadata
1475
     *
1476
     * @return string
1477
     */
1478 11
    protected function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
1479
    {
1480 11
        $lines = [];
1481 11
        $lines[] = $this->spaces . '/**';
1482
1483 11
        if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1484 11
            $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection';
1485
        } else {
1486 10
            $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\');
1487
        }
1488
1489 11
        if ($this->generateAnnotations) {
1490 11
            $lines[] = $this->spaces . ' *';
1491
1492 11
            if (isset($associationMapping['id']) && $associationMapping['id']) {
1493
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1494
1495 View Code Duplication
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1496
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1497
                }
1498
            }
1499
1500 11
            $type = null;
1501 11
            switch ($associationMapping['type']) {
1502 11
                case ClassMetadataInfo::ONE_TO_ONE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1503 10
                    $type = 'OneToOne';
1504 10
                    break;
1505 11
                case ClassMetadataInfo::MANY_TO_ONE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1506 1
                    $type = 'ManyToOne';
1507 1
                    break;
1508 11
                case ClassMetadataInfo::ONE_TO_MANY:
1509 1
                    $type = 'OneToMany';
1510 1
                    break;
1511 11
                case ClassMetadataInfo::MANY_TO_MANY:
1512 11
                    $type = 'ManyToMany';
1513 11
                    break;
1514
            }
1515 11
            $typeOptions = [];
1516
1517 11
            if (isset($associationMapping['targetEntity'])) {
1518 11
                $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"';
1519
            }
1520
1521 11
            if (isset($associationMapping['inversedBy'])) {
1522 1
                $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"';
1523
            }
1524
1525 11
            if (isset($associationMapping['mappedBy'])) {
1526 10
                $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"';
1527
            }
1528
1529 11
            if ($associationMapping['cascade']) {
1530 1
                $cascades = [];
1531
1532 1
                if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"';
1533 1
                if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"';
1534 1
                if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"';
1535 1
                if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
1536 1
                if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
1537
1538 1
                if (count($cascades) === 5) {
1539 1
                    $cascades = ['"all"'];
1540
                }
1541
1542 1
                $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
1543
            }
1544
1545 11
            if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
1546 1
                $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
1547
            }
1548
1549 11
            if (isset($associationMapping['fetch']) && $associationMapping['fetch'] !== ClassMetadataInfo::FETCH_LAZY) {
1550
                $fetchMap = [
1551 10
                    ClassMetadataInfo::FETCH_EXTRA_LAZY => 'EXTRA_LAZY',
1552
                    ClassMetadataInfo::FETCH_EAGER      => 'EAGER',
1553
                ];
1554
1555 10
                $typeOptions[] = 'fetch="' . $fetchMap[$associationMapping['fetch']] . '"';
1556
            }
1557
1558 11
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
1559
1560 11
            if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
1561 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({';
1562
1563 1
                $joinColumnsLines = [];
1564
1565 1 View Code Duplication
                foreach ($associationMapping['joinColumns'] as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1566 1
                    if ($joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn)) {
1567 1
                        $joinColumnsLines[] = $this->spaces . ' *   ' . $joinColumnAnnot;
1568
                    }
1569
                }
1570
1571 1
                $lines[] = implode(",\n", $joinColumnsLines);
1572 1
                $lines[] = $this->spaces . ' * })';
1573
            }
1574
1575 11
            if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) {
1576 11
                $joinTable = [];
1577 11
                $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"';
1578
1579 11
                if (isset($associationMapping['joinTable']['schema'])) {
1580
                    $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
1581
                }
1582
1583 11
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
1584 11
                $lines[] = $this->spaces . ' *   joinColumns={';
1585
1586 11
                $joinColumnsLines = [];
1587
1588 11 View Code Duplication
                foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1589 11
                    $joinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1590
                }
1591
1592 11
                $lines[] = implode(",". PHP_EOL, $joinColumnsLines);
1593 11
                $lines[] = $this->spaces . ' *   },';
1594 11
                $lines[] = $this->spaces . ' *   inverseJoinColumns={';
1595
1596 11
                $inverseJoinColumnsLines = [];
1597
1598 11
                foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
1599 11
                    $inverseJoinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1600
                }
1601
1602 11
                $lines[] = implode(",". PHP_EOL, $inverseJoinColumnsLines);
1603 11
                $lines[] = $this->spaces . ' *   }';
1604 11
                $lines[] = $this->spaces . ' * )';
1605
            }
1606
1607 11
            if (isset($associationMapping['orderBy'])) {
1608 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({';
1609
1610 1
                foreach ($associationMapping['orderBy'] as $name => $direction) {
1611 1
                    $lines[] = $this->spaces . ' *     "' . $name . '"="' . $direction . '",';
1612
                }
1613
1614 1
                $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
1615 1
                $lines[] = $this->spaces . ' * })';
1616
            }
1617
        }
1618
1619 11
        $lines[] = $this->spaces . ' */';
1620
1621 11
        return implode("\n", $lines);
1622
    }
1623
1624
    /**
1625
     * @param array             $fieldMapping
1626
     * @param ClassMetadataInfo $metadata
1627
     *
1628
     * @return string
1629
     */
1630 40
    protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
1631
    {
1632 40
        $lines = [];
1633 40
        $lines[] = $this->spaces . '/**';
1634 40
        $lines[] = $this->spaces . ' * @var '
1635 40
            . $this->getType($fieldMapping['type'])
1636 40
            . ($this->nullableFieldExpression($fieldMapping) ? '|null' : '');
1637
1638 40
        if ($this->generateAnnotations) {
1639 40
            $lines[] = $this->spaces . ' *';
1640
1641 40
            $column = [];
1642 40
            if (isset($fieldMapping['columnName'])) {
1643 40
                $column[] = 'name="' . $fieldMapping['columnName'] . '"';
1644
            }
1645
1646 40
            if (isset($fieldMapping['type'])) {
1647 40
                $column[] = 'type="' . $fieldMapping['type'] . '"';
1648
            }
1649
1650 40
            if (isset($fieldMapping['length'])) {
1651 10
                $column[] = 'length=' . $fieldMapping['length'];
1652
            }
1653
1654 40
            if (isset($fieldMapping['precision'])) {
1655 4
                $column[] = 'precision=' .  $fieldMapping['precision'];
1656
            }
1657
1658 40
            if (isset($fieldMapping['scale'])) {
1659 4
                $column[] = 'scale=' . $fieldMapping['scale'];
1660
            }
1661
1662 40 View Code Duplication
            if (isset($fieldMapping['nullable'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1663 10
                $column[] = 'nullable=' .  var_export($fieldMapping['nullable'], true);
1664
            }
1665
1666 40
            $options = [];
1667
1668 40 View Code Duplication
            if (isset($fieldMapping['options']['default']) && $fieldMapping['options']['default']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1669 13
                $options[] = '"default"="' . $fieldMapping['options']['default'] .'"';
1670
            }
1671
1672 40
            if (isset($fieldMapping['options']['unsigned']) && $fieldMapping['options']['unsigned']) {
1673 3
                $options[] = '"unsigned"=true';
1674
            }
1675
1676 40
            if (isset($fieldMapping['options']['fixed']) && $fieldMapping['options']['fixed']) {
1677 2
                $options[] = '"fixed"=true';
1678
            }
1679
1680 40 View Code Duplication
            if (isset($fieldMapping['options']['comment']) && $fieldMapping['options']['comment']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1681 4
                $options[] = '"comment"="' . $fieldMapping['options']['comment'] .'"';
1682
            }
1683
1684 40 View Code Duplication
            if (isset($fieldMapping['options']['collation']) && $fieldMapping['options']['collation']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1685 2
                $options[] = '"collation"="' . $fieldMapping['options']['collation'] .'"';
1686
            }
1687
1688 40 View Code Duplication
            if (isset($fieldMapping['options']['check']) && $fieldMapping['options']['check']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1689 4
                $options[] = '"check"="' . $fieldMapping['options']['check'] .'"';
1690
            }
1691
1692 40
            if ($options) {
1693 21
                $column[] = 'options={'.implode(',', $options).'}';
1694
            }
1695
1696 40
            if (isset($fieldMapping['columnDefinition'])) {
1697 1
                $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
1698
            }
1699
1700 40 View Code Duplication
            if (isset($fieldMapping['unique'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1701 4
                $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
1702
            }
1703
1704 40
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
1705
1706 40
            if (isset($fieldMapping['id']) && $fieldMapping['id']) {
1707 36
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1708
1709 36 View Code Duplication
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1710 36
                    $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1711
                }
1712
1713 36
                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...
1714 1
                    $sequenceGenerator = [];
1715
1716 1
                    if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) {
1717 1
                        $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"';
1718
                    }
1719
1720 1
                    if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
1721 1
                        $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize'];
1722
                    }
1723
1724 1
                    if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
1725 1
                        $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue'];
1726
                    }
1727
1728 1
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
1729
                }
1730
            }
1731
1732 40
            if (isset($fieldMapping['version']) && $fieldMapping['version']) {
1733
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version';
1734
            }
1735
        }
1736
1737 40
        $lines[] = $this->spaces . ' */';
1738
1739 40
        return implode("\n", $lines);
1740
    }
1741
1742
    /**
1743
     * @param array $embeddedClass
1744
     *
1745
     * @return string
1746
     */
1747 10
    protected function generateEmbeddedPropertyDocBlock(array $embeddedClass)
1748
    {
1749 10
        $lines = [];
1750 10
        $lines[] = $this->spaces . '/**';
1751 10
        $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\');
1752
1753 10
        if ($this->generateAnnotations) {
1754 10
            $lines[] = $this->spaces . ' *';
1755
1756 10
            $embedded = ['class="' . $embeddedClass['class'] . '"'];
1757
1758 10
            if (isset($embeddedClass['columnPrefix'])) {
1759 8
                if (is_string($embeddedClass['columnPrefix'])) {
1760 1
                    $embedded[] = 'columnPrefix="' . $embeddedClass['columnPrefix'] . '"';
1761
                } else {
1762 7
                    $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true);
1763
                }
1764
            }
1765
1766 10
            $lines[] = $this->spaces . ' * @' .
1767 10
                $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')';
1768
        }
1769
1770 10
        $lines[] = $this->spaces . ' */';
1771
1772 10
        return implode("\n", $lines);
1773
    }
1774
1775 42
    private function generateEntityListenerAnnotation(ClassMetadataInfo $metadata): string
1776
    {
1777 42
        if (0 === \count($metadata->entityListeners)) {
1778 41
            return '';
1779
        }
1780
1781 1
        $processedClasses = [];
1782 1
        foreach ($metadata->entityListeners as $event => $eventListeners) {
1783 1
            foreach ($eventListeners as $eventListener) {
1784 1
                $processedClasses[] = '"' . $eventListener['class'] . '"';
1785
            }
1786
        }
1787
1788 1
        return \sprintf(
1789 1
            '%s%s({%s})',
1790 1
            '@' . $this->annotationsPrefix,
1791 1
            'EntityListeners',
1792 1
            \implode(',', \array_unique($processedClasses))
1793
        );
1794
    }
1795
1796
    /**
1797
     * @param string $code
1798
     * @param int    $num
1799
     *
1800
     * @return string
1801
     */
1802 42
    protected function prefixCodeWithSpaces($code, $num = 1)
1803
    {
1804 42
        $lines = explode("\n", $code);
1805
1806 42
        foreach ($lines as $key => $value) {
1807 42
            if ( ! empty($value)) {
1808 42
                $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
1809
            }
1810
        }
1811
1812 42
        return implode("\n", $lines);
1813
    }
1814
1815
    /**
1816
     * @param integer $type The inheritance type used by the class and its subclasses.
1817
     *
1818
     * @return string The literal string for the inheritance type.
1819
     *
1820
     * @throws \InvalidArgumentException When the inheritance type does not exist.
1821
     */
1822 1
    protected function getInheritanceTypeString($type)
1823
    {
1824 1
        if ( ! isset(static::$inheritanceTypeMap[$type])) {
1825 1
            throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type));
1826
        }
1827
1828 1
        return static::$inheritanceTypeMap[$type];
1829
    }
1830
1831
    /**
1832
     * @param integer $type The policy used for change-tracking for the mapped class.
1833
     *
1834
     * @return string The literal string for the change-tracking type.
1835
     *
1836
     * @throws \InvalidArgumentException When the change-tracking type does not exist.
1837
     */
1838 1
    protected function getChangeTrackingPolicyString($type)
1839
    {
1840 1
        if ( ! isset(static::$changeTrackingPolicyMap[$type])) {
1841 1
            throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type));
1842
        }
1843
1844 1
        return static::$changeTrackingPolicyMap[$type];
1845
    }
1846
1847
    /**
1848
     * @param integer $type The generator to use for the mapped class.
1849
     *
1850
     * @return string The literal string for the generator type.
1851
     *
1852
     * @throws \InvalidArgumentException    When the generator type does not exist.
1853
     */
1854 37
    protected function getIdGeneratorTypeString($type)
1855
    {
1856 37
        if ( ! isset(static::$generatorStrategyMap[$type])) {
1857 1
            throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type));
1858
        }
1859
1860 37
        return static::$generatorStrategyMap[$type];
1861
    }
1862
1863
    /**
1864
     * @param array $fieldMapping
1865
     *
1866
     * @return string|null
1867
     */
1868 42
    private function nullableFieldExpression(array $fieldMapping)
1869
    {
1870 42
        if (isset($fieldMapping['nullable']) && true === $fieldMapping['nullable']) {
1871 7
            return 'null';
1872
        }
1873
1874 42
        return null;
1875
    }
1876
1877
    /**
1878
     * Exports (nested) option elements.
1879
     *
1880
     * @param array $options
1881
     *
1882
     * @return string
1883
     */
1884 1
    private function exportTableOptions(array $options)
1885
    {
1886 1
        $optionsStr = [];
1887
1888 1
        foreach ($options as $name => $option) {
1889 1
            if (is_array($option)) {
1890 1
                $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}';
1891
            } else {
1892 1
                $optionsStr[] = '"' . $name . '"="' . (string) $option . '"';
1893
            }
1894
        }
1895
1896 1
        return implode(',', $optionsStr);
1897
    }
1898
}
1899