Failed Conditions
Push — master ( 2ade86...13f838 )
by Jonathan
18s
created

lib/Doctrine/ORM/Tools/EntityGenerator.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
     */
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 40
    public function __construct()
335
    {
336 40
        if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
337 40
            $this->annotationsPrefix = 'ORM\\';
338
        }
339 40
    }
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 31
    public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
367
    {
368 31
        $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension;
369 31
        $dir = dirname($path);
370
371 31
        if ( ! is_dir($dir)) {
372 2
            mkdir($dir, 0775, true);
373
        }
374
375 31
        $this->isNew = ! file_exists($path) || $this->regenerateEntityIfExists;
376
377 31
        if ( ! $this->isNew) {
378 3
            $this->parseTokensInEntityFile(file_get_contents($path));
379
        } else {
380 30
            $this->staticReflection[$metadata->name] = ['properties' => [], 'methods' => []];
381
        }
382
383 31
        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 31
        if ($this->isNew) {
392 30
            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 31
        chmod($path, 0664);
398 31
    }
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 31
    public function generateEntityClass(ClassMetadataInfo $metadata)
408
    {
409
        $placeHolders = [
410 31
            '<namespace>',
411
            '<useStatement>',
412
            '<entityAnnotation>',
413
            '<entityClassName>',
414
            '<entityBody>'
415
        ];
416
417
        $replacements = [
418 31
            $this->generateEntityNamespace($metadata),
419 31
            $this->generateEntityUse(),
420 31
            $this->generateEntityDocBlock($metadata),
421 31
            $this->generateEntityClassName($metadata),
422 31
            $this->generateEntityBody($metadata)
423
        ];
424
425 31
        $code = str_replace($placeHolders, $replacements, static::$classTemplate);
426
427 31
        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 40
    public function setGenerateAnnotations($bool)
494
    {
495 40
        $this->generateAnnotations = $bool;
496 40
    }
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 39
    public function setFieldVisibility($visibility)
508
    {
509 39
        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 39
        $this->fieldVisibility = $visibility;
514 39
    }
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 40
    public function setAnnotationPrefix($prefix)
534
    {
535 40
        $this->annotationsPrefix = $prefix;
536 40
    }
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 40
    public function setUpdateEntityIfExists($bool)
546
    {
547 40
        $this->updateEntityIfExists = $bool;
548 40
    }
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 40
    public function setRegenerateEntityIfExists($bool)
558
    {
559 40
        $this->regenerateEntityIfExists = $bool;
560 40
    }
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 40
    public function setGenerateStubMethods($bool)
570
    {
571 40
        $this->generateEntityStubMethods = $bool;
572 40
    }
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 31
    protected function getType($type)
592
    {
593 31
        if (isset($this->typeAlias[$type])) {
594 30
            return $this->typeAlias[$type];
595
        }
596
597 20
        return $type;
598
    }
599
600
    /**
601
     * @param ClassMetadataInfo $metadata
602
     *
603
     * @return string
604
     */
605 31
    protected function generateEntityNamespace(ClassMetadataInfo $metadata)
606
    {
607 31
        if (! $this->hasNamespace($metadata)) {
608 2
            return '';
609
        }
610
611 31
        return 'namespace ' . $this->getNamespace($metadata) .';';
612
    }
613
614
    /**
615
     * @return string
616
     */
617 31
    protected function generateEntityUse()
618
    {
619 31
        if (! $this->generateAnnotations) {
620
            return '';
621
        }
622
623 31
        return "\n".'use Doctrine\ORM\Mapping as ORM;'."\n";
624
    }
625
626
    /**
627
     * @param ClassMetadataInfo $metadata
628
     *
629
     * @return string
630
     */
631 31
    protected function generateEntityClassName(ClassMetadataInfo $metadata)
632
    {
633 31
        return 'class ' . $this->getClassName($metadata) .
634 31
            ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
635
    }
636
637
    /**
638
     * @param ClassMetadataInfo $metadata
639
     *
640
     * @return string
641
     */
642 32
    protected function generateEntityBody(ClassMetadataInfo $metadata)
643
    {
644 32
        $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata);
645 32
        $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata);
646 32
        $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata);
647 32
        $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null;
648 32
        $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata);
649
650 32
        $code = [];
651
652 32
        if ($fieldMappingProperties) {
653 29
            $code[] = $fieldMappingProperties;
654
        }
655
656 32
        if ($embeddedProperties) {
657 10
            $code[] = $embeddedProperties;
658
        }
659
660 32
        if ($associationMappingProperties) {
661 11
            $code[] = $associationMappingProperties;
662
        }
663
664 32
        $code[] = $this->generateEntityConstructor($metadata);
665
666 32
        if ($stubMethods) {
667 30
            $code[] = $stubMethods;
668
        }
669
670 32
        if ($lifecycleCallbackMethods) {
671 10
            $code[] = $lifecycleCallbackMethods;
672
        }
673
674 32
        return implode("\n", $code);
675
    }
676
677
    /**
678
     * @param ClassMetadataInfo $metadata
679
     *
680
     * @return string
681
     */
682 32
    protected function generateEntityConstructor(ClassMetadataInfo $metadata)
683
    {
684 32
        if ($this->hasMethod('__construct', $metadata)) {
685 2
            return '';
686
        }
687
688 32
        if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) {
689 1
            return $this->generateEmbeddableConstructor($metadata);
690
        }
691
692 31
        $collections = [];
693
694 31
        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 31
        if ($collections) {
701 11
            return $this->prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->spaces, $collections), static::$constructorMethodTemplate));
702
        }
703
704 27
        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 31 View Code Duplication
    protected function hasProperty($property, ClassMetadataInfo $metadata)
861
    {
862 31
        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 30
        foreach ($this->getTraits($metadata) as $trait) {
872 2
            if ($trait->hasProperty($property)) {
873 2
                return true;
874
            }
875
        }
876
877
        return (
878 30
            isset($this->staticReflection[$metadata->name]) &&
879 30
            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 32 View Code Duplication
    protected function hasMethod($method, ClassMetadataInfo $metadata)
890
    {
891 32
        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 32
        foreach ($this->getTraits($metadata) as $trait) {
902 2
            if ($trait->hasMethod($method)) {
903 2
                return true;
904
            }
905
        }
906
907
        return (
908 32
            isset($this->staticReflection[$metadata->name]) &&
909 32
            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 32
    protected function getTraits(ClassMetadataInfo $metadata)
921
    {
922 32
        if (! ($metadata->reflClass !== null || class_exists($metadata->name))) {
923 26
            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 31
    protected function hasNamespace(ClassMetadataInfo $metadata)
945
    {
946 31
        return (bool) strpos($metadata->name, '\\');
947
    }
948
949
    /**
950
     * @return bool
951
     */
952 32
    protected function extendsClass()
953
    {
954 32
        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 32
    protected function getClassName(ClassMetadataInfo $metadata)
981
    {
982 32
        return ($pos = strrpos($metadata->name, '\\'))
983 32
            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
984
    }
985
986
    /**
987
     * @param ClassMetadataInfo $metadata
988
     *
989
     * @return string
990
     */
991 31
    protected function getNamespace(ClassMetadataInfo $metadata)
992
    {
993 31
        return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
994
    }
995
996
    /**
997
     * @param ClassMetadataInfo $metadata
998
     *
999
     * @return string
1000
     */
1001 31
    protected function generateEntityDocBlock(ClassMetadataInfo $metadata)
1002
    {
1003 31
        $lines = [];
1004 31
        $lines[] = '/**';
1005 31
        $lines[] = ' * ' . $this->getClassName($metadata);
1006
1007 31
        if ($this->generateAnnotations) {
1008 31
            $lines[] = ' *';
1009
1010
            $methods = [
1011 31
                'generateTableAnnotation',
1012
                'generateInheritanceAnnotation',
1013
                'generateDiscriminatorColumnAnnotation',
1014
                'generateDiscriminatorMapAnnotation',
1015
                'generateEntityAnnotation',
1016
                'generateEntityListenerAnnotation',
1017
            ];
1018
1019 31
            foreach ($methods as $method) {
1020 31
                if ($code = $this->$method($metadata)) {
1021 31
                    $lines[] = ' * ' . $code;
1022
                }
1023
            }
1024
1025 31
            if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
1026 10
                $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks';
1027
            }
1028
        }
1029
1030 31
        $lines[] = ' */';
1031
1032 31
        return implode("\n", $lines);
1033
    }
1034
1035
    /**
1036
     * @param ClassMetadataInfo $metadata
1037
     *
1038
     * @return string
1039
     */
1040 31
    protected function generateEntityAnnotation(ClassMetadataInfo $metadata)
1041
    {
1042 31
        $prefix = '@' . $this->annotationsPrefix;
1043
1044 31
        if ($metadata->isEmbeddedClass) {
1045 11
            return $prefix . 'Embeddable';
1046
        }
1047
1048 27
        $customRepository = $metadata->customRepositoryClassName
1049 11
            ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")'
1050 27
            : '';
1051
1052 27
        return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository;
1053
    }
1054
1055
    /**
1056
     * @param ClassMetadataInfo $metadata
1057
     *
1058
     * @return string
1059
     */
1060 31
    protected function generateTableAnnotation(ClassMetadataInfo $metadata)
1061
    {
1062 31
        if ($metadata->isEmbeddedClass) {
1063 11
            return '';
1064
        }
1065
1066 27
        $table = [];
1067
1068 27 View Code Duplication
        if (isset($metadata->table['schema'])) {
1069
            $table[] = 'schema="' . $metadata->table['schema'] . '"';
1070
        }
1071
1072 27 View Code Duplication
        if (isset($metadata->table['name'])) {
1073 24
            $table[] = 'name="' . $metadata->table['name'] . '"';
1074
        }
1075
1076 27
        if (isset($metadata->table['options']) && $metadata->table['options']) {
1077 1
            $table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}';
1078
        }
1079
1080 27 View Code Duplication
        if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
1081 9
            $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
1082 9
            $table[] = 'uniqueConstraints={' . $constraints . '}';
1083
        }
1084
1085 27 View Code Duplication
        if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
1086 9
            $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']);
1087 9
            $table[] = 'indexes={' . $constraints . '}';
1088
        }
1089
1090 27
        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 31
    protected function generateInheritanceAnnotation(ClassMetadataInfo $metadata)
1119
    {
1120 31
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1121 31
            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 31
    protected function generateDiscriminatorColumnAnnotation(ClassMetadataInfo $metadata)
1133
    {
1134 31
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1135 31
            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 31
    protected function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata)
1152
    {
1153 31
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1154 31
            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 31
    protected function generateEntityStubMethods(ClassMetadataInfo $metadata)
1172
    {
1173 31
        $methods = [];
1174
1175 31
        foreach ($metadata->fieldMappings as $fieldMapping) {
1176 30
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) {
1177
                continue;
1178
            }
1179
1180 30
            $nullableField = $this->nullableFieldExpression($fieldMapping);
1181
1182 30
            if ((!$metadata->isEmbeddedClass || !$this->embeddablesImmutable)
1183 30
                && (!isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType === ClassMetadataInfo::GENERATOR_TYPE_NONE)
1184 30
                && $code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)
1185
            ) {
1186 27
                $methods[] = $code;
1187
            }
1188
1189 30
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)) {
1190 30
                $methods[] = $code;
1191
            }
1192
        }
1193
1194 31
        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 31
        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'])) {
1221 10
                    $methods[] = $code;
1222
                }
1223 10 View Code Duplication
                if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1224 10
                    $methods[] = $code;
1225
                }
1226 10 View Code Duplication
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], Collection::class)) {
1227 12
                    $methods[] = $code;
1228
                }
1229
            }
1230
        }
1231
1232 31
        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 32
    protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
1268
    {
1269 32
        if (empty($metadata->lifecycleCallbacks)) {
1270 29
            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 32
    protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
1290
    {
1291 32
        $lines = [];
1292
1293 32
        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 32
        return implode("\n", $lines);
1304
    }
1305
1306
    /**
1307
     * @param ClassMetadataInfo $metadata
1308
     *
1309
     * @return string
1310
     */
1311 32
    protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata)
1312
    {
1313 32
        $lines = [];
1314
1315 32
        foreach ($metadata->fieldMappings as $fieldMapping) {
1316 31
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']]) ||
1317 31
                $this->hasProperty($fieldMapping['fieldName'], $metadata) ||
1318 31
                $metadata->isInheritedField($fieldMapping['fieldName'])
1319
            ) {
1320 4
                continue;
1321
            }
1322
1323 29
            $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
1324 29
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName']
1325 29
                     . (isset($fieldMapping['options']['default']) ? ' = ' . var_export($fieldMapping['options']['default'], true) : null) . ";\n";
1326
        }
1327
1328 32
        return implode("\n", $lines);
1329
    }
1330
1331
    /**
1332
     * @param ClassMetadataInfo $metadata
1333
     *
1334
     * @return string
1335
     */
1336 32
    protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata)
1337
    {
1338 32
        $lines = [];
1339
1340 32
        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 32
        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 30
    protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
1362
    {
1363 30
        $methodName = $type . Inflector::classify($fieldName);
1364 30
        $variableName = Inflector::camelize($fieldName);
1365 30
        if (in_array($type, ["add", "remove"])) {
1366 10
            $methodName = Inflector::singularize($methodName);
1367 10
            $variableName = Inflector::singularize($variableName);
1368
        }
1369
1370 30
        if ($this->hasMethod($methodName, $metadata)) {
1371 5
            return '';
1372
        }
1373 30
        $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName);
1374
1375 30
        $var = sprintf('%sMethodTemplate', $type);
1376 30
        $template = static::$$var;
1377
1378 30
        $methodTypeHint = null;
1379 30
        $types          = Type::getTypesMap();
1380 30
        $variableType   = $typeHint ? $this->getType($typeHint) : null;
1381
1382 30
        if ($typeHint && ! isset($types[$typeHint])) {
1383 14
            $variableType   =  '\\' . ltrim($variableType, '\\');
1384 14
            $methodTypeHint =  '\\' . $typeHint . ' ';
1385
        }
1386
1387
        $replacements = [
1388 30
          '<description>'       => ucfirst($type) . ' ' . $variableName . '.',
1389 30
          '<methodTypeHint>'    => $methodTypeHint,
1390 30
          '<variableType>'      => $variableType . (null !== $defaultValue ? ('|' . $defaultValue) : ''),
1391 30
          '<variableName>'      => $variableName,
1392 30
          '<methodName>'        => $methodName,
1393 30
          '<fieldName>'         => $fieldName,
1394 30
          '<variableDefault>'   => ($defaultValue !== null ) ? (' = ' . $defaultValue) : '',
1395 30
          '<entity>'            => $this->getClassName($metadata)
1396
        ];
1397
1398 30
        $method = str_replace(
1399 30
            array_keys($replacements),
1400 30
            array_values($replacements),
1401 30
            $template
1402
        );
1403
1404 30
        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)) {
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
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
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
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
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 29
    protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
1631
    {
1632 29
        $lines = [];
1633 29
        $lines[] = $this->spaces . '/**';
1634 29
        $lines[] = $this->spaces . ' * @var '
1635 29
            . $this->getType($fieldMapping['type'])
1636 29
            . ($this->nullableFieldExpression($fieldMapping) ? '|null' : '');
1637
1638 29
        if ($this->generateAnnotations) {
1639 29
            $lines[] = $this->spaces . ' *';
1640
1641 29
            $column = [];
1642 29
            if (isset($fieldMapping['columnName'])) {
1643 29
                $column[] = 'name="' . $fieldMapping['columnName'] . '"';
1644
            }
1645
1646 29
            if (isset($fieldMapping['type'])) {
1647 29
                $column[] = 'type="' . $fieldMapping['type'] . '"';
1648
            }
1649
1650 29
            if (isset($fieldMapping['length'])) {
1651 4
                $column[] = 'length=' . $fieldMapping['length'];
1652
            }
1653
1654 29
            if (isset($fieldMapping['precision'])) {
1655 4
                $column[] = 'precision=' .  $fieldMapping['precision'];
1656
            }
1657
1658 29
            if (isset($fieldMapping['scale'])) {
1659 4
                $column[] = 'scale=' . $fieldMapping['scale'];
1660
            }
1661
1662 29 View Code Duplication
            if (isset($fieldMapping['nullable'])) {
1663 10
                $column[] = 'nullable=' .  var_export($fieldMapping['nullable'], true);
1664
            }
1665
1666 29
            $options = [];
1667
1668 29
            if (isset($fieldMapping['options']['unsigned']) && $fieldMapping['options']['unsigned']) {
1669 1
                $options[] = '"unsigned"=true';
1670
            }
1671
1672 29
            if ($options) {
1673 1
                $column[] = 'options={'.implode(',', $options).'}';
1674
            }
1675
1676 29
            if (isset($fieldMapping['columnDefinition'])) {
1677 1
                $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
1678
            }
1679
1680 29 View Code Duplication
            if (isset($fieldMapping['unique'])) {
1681 4
                $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
1682
            }
1683
1684 29
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
1685
1686 29
            if (isset($fieldMapping['id']) && $fieldMapping['id']) {
1687 25
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1688
1689 25 View Code Duplication
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1690 25
                    $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1691
                }
1692
1693 25
                if ($metadata->sequenceGeneratorDefinition) {
1694 1
                    $sequenceGenerator = [];
1695
1696 1
                    if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) {
1697 1
                        $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"';
1698
                    }
1699
1700 1
                    if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
1701 1
                        $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize'];
1702
                    }
1703
1704 1
                    if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
1705 1
                        $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue'];
1706
                    }
1707
1708 1
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
1709
                }
1710
            }
1711
1712 29
            if (isset($fieldMapping['version']) && $fieldMapping['version']) {
1713
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version';
1714
            }
1715
        }
1716
1717 29
        $lines[] = $this->spaces . ' */';
1718
1719 29
        return implode("\n", $lines);
1720
    }
1721
1722
    /**
1723
     * @param array $embeddedClass
1724
     *
1725
     * @return string
1726
     */
1727 10
    protected function generateEmbeddedPropertyDocBlock(array $embeddedClass)
1728
    {
1729 10
        $lines = [];
1730 10
        $lines[] = $this->spaces . '/**';
1731 10
        $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\');
1732
1733 10
        if ($this->generateAnnotations) {
1734 10
            $lines[] = $this->spaces . ' *';
1735
1736 10
            $embedded = ['class="' . $embeddedClass['class'] . '"'];
1737
1738 10
            if (isset($embeddedClass['columnPrefix'])) {
1739 8
                if (is_string($embeddedClass['columnPrefix'])) {
1740 1
                    $embedded[] = 'columnPrefix="' . $embeddedClass['columnPrefix'] . '"';
1741
                } else {
1742 7
                    $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true);
1743
                }
1744
            }
1745
1746 10
            $lines[] = $this->spaces . ' * @' .
1747 10
                $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')';
1748
        }
1749
1750 10
        $lines[] = $this->spaces . ' */';
1751
1752 10
        return implode("\n", $lines);
1753
    }
1754
1755 31
    private function generateEntityListenerAnnotation(ClassMetadataInfo $metadata): string
1756
    {
1757 31
        if (0 === \count($metadata->entityListeners)) {
1758 30
            return '';
1759
        }
1760
1761 1
        $processedClasses = [];
1762 1
        foreach ($metadata->entityListeners as $event => $eventListeners) {
1763 1
            foreach ($eventListeners as $eventListener) {
1764 1
                $processedClasses[] = '"' . $eventListener['class'] . '"';
1765
            }
1766
        }
1767
1768 1
        return \sprintf(
1769 1
            '%s%s({%s})',
1770 1
            '@' . $this->annotationsPrefix,
1771 1
            'EntityListeners',
1772 1
            \implode(',', \array_unique($processedClasses))
1773
        );
1774
    }
1775
1776
    /**
1777
     * @param string $code
1778
     * @param int    $num
1779
     *
1780
     * @return string
1781
     */
1782 31
    protected function prefixCodeWithSpaces($code, $num = 1)
1783
    {
1784 31
        $lines = explode("\n", $code);
1785
1786 31
        foreach ($lines as $key => $value) {
1787 31
            if ( ! empty($value)) {
1788 31
                $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
1789
            }
1790
        }
1791
1792 31
        return implode("\n", $lines);
1793
    }
1794
1795
    /**
1796
     * @param integer $type The inheritance type used by the class and its subclasses.
1797
     *
1798
     * @return string The literal string for the inheritance type.
1799
     *
1800
     * @throws \InvalidArgumentException When the inheritance type does not exist.
1801
     */
1802 1
    protected function getInheritanceTypeString($type)
1803
    {
1804 1
        if ( ! isset(static::$inheritanceTypeMap[$type])) {
1805 1
            throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type));
1806
        }
1807
1808 1
        return static::$inheritanceTypeMap[$type];
1809
    }
1810
1811
    /**
1812
     * @param integer $type The policy used for change-tracking for the mapped class.
1813
     *
1814
     * @return string The literal string for the change-tracking type.
1815
     *
1816
     * @throws \InvalidArgumentException When the change-tracking type does not exist.
1817
     */
1818 1
    protected function getChangeTrackingPolicyString($type)
1819
    {
1820 1
        if ( ! isset(static::$changeTrackingPolicyMap[$type])) {
1821 1
            throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type));
1822
        }
1823
1824 1
        return static::$changeTrackingPolicyMap[$type];
1825
    }
1826
1827
    /**
1828
     * @param integer $type The generator to use for the mapped class.
1829
     *
1830
     * @return string The literal string for the generator type.
1831
     *
1832
     * @throws \InvalidArgumentException    When the generator type does not exist.
1833
     */
1834 26
    protected function getIdGeneratorTypeString($type)
1835
    {
1836 26
        if ( ! isset(static::$generatorStrategyMap[$type])) {
1837 1
            throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type));
1838
        }
1839
1840 26
        return static::$generatorStrategyMap[$type];
1841
    }
1842
1843
    /**
1844
     * @param array $fieldMapping
1845
     *
1846
     * @return string|null
1847
     */
1848 31
    private function nullableFieldExpression(array $fieldMapping)
1849
    {
1850 31
        if (isset($fieldMapping['nullable']) && true === $fieldMapping['nullable']) {
1851 7
            return 'null';
1852
        }
1853
1854 31
        return null;
1855
    }
1856
1857
    /**
1858
     * Exports (nested) option elements.
1859
     *
1860
     * @param array $options
1861
     *
1862
     * @return string
1863
     */
1864 1
    private function exportTableOptions(array $options)
1865
    {
1866 1
        $optionsStr = [];
1867
1868 1
        foreach ($options as $name => $option) {
1869 1
            if (is_array($option)) {
1870 1
                $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}';
1871
            } else {
1872 1
                $optionsStr[] = '"' . $name . '"="' . (string) $option . '"';
1873
            }
1874
        }
1875
1876 1
        return implode(',', $optionsStr);
1877
    }
1878
}
1879