Passed
Pull Request — 2.6 (#7894)
by
unknown
07:25
created

EntityGenerator::generateEntityClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

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