Failed Conditions
Pull Request — master (#6593)
by Thomas
16:12
created

EntityGenerator::getClassToExtendName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
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\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): void
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): void
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): string
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): string
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): void
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): void
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): void
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): void
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): void
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): void
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): void
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): void
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): void
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): void
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): void
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): string
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): ?string
606
    {
607 31
        if (!$this->hasNamespace($metadata)) {
608 2
            return null;
609
        }
610
611 31
        return 'namespace ' . $this->getNamespace($metadata) .';';
612
    }
613
614 31
    protected function generateEntityUse(): ?string
615
    {
616 31
        if (!$this->generateAnnotations) {
617
            return null;
618
        }
619
620 31
        return "\n".'use Doctrine\ORM\Mapping as ORM;'."\n";
621
    }
622
623
    /**
624
     * @param ClassMetadataInfo $metadata
625
     *
626
     * @return string
627
     */
628 31
    protected function generateEntityClassName(ClassMetadataInfo $metadata): string
629
    {
630 31
        return 'class ' . $this->getClassName($metadata) .
631 31
            ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
632
    }
633
634
    /**
635
     * @param ClassMetadataInfo $metadata
636
     *
637
     * @return string
638
     */
639 32
    protected function generateEntityBody(ClassMetadataInfo $metadata): string
640
    {
641 32
        $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata);
642 32
        $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata);
643 32
        $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata);
644 32
        $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null;
645 32
        $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata);
646
647 32
        $code = [];
648
649 32
        if ($fieldMappingProperties) {
650 29
            $code[] = $fieldMappingProperties;
651
        }
652
653 32
        if ($embeddedProperties) {
654 10
            $code[] = $embeddedProperties;
655
        }
656
657 32
        if ($associationMappingProperties) {
658 11
            $code[] = $associationMappingProperties;
659
        }
660
661 32
        $code[] = $this->generateEntityConstructor($metadata);
662
663 32
        if ($stubMethods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stubMethods of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
664 30
            $code[] = $stubMethods;
665
        }
666
667 32
        if ($lifecycleCallbackMethods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lifecycleCallbackMethods of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
668 10
            $code[] = $lifecycleCallbackMethods;
669
        }
670
671 32
        return implode("\n", $code);
672
    }
673
674
    /**
675
     * @param ClassMetadataInfo $metadata
676
     *
677
     * @return string
678
     */
679 32
    protected function generateEntityConstructor(ClassMetadataInfo $metadata): string
680
    {
681 32
        if ($this->hasMethod('__construct', $metadata)) {
682 2
            return '';
683
        }
684
685 32
        if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) {
686 1
            return $this->generateEmbeddableConstructor($metadata);
687
        }
688
689 31
        $collections = [];
690
691 31
        foreach ($metadata->associationMappings as $mapping) {
692 13
            if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
693 13
                $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
694
            }
695
        }
696
697 31
        if ($collections) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $collections 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...
698 11
            return $this->prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->spaces, $collections), static::$constructorMethodTemplate));
699
        }
700
701 27
        return '';
702
    }
703
704
    /**
705
     * @param ClassMetadataInfo $metadata
706
     *
707
     * @return string
708
     */
709 1
    private function generateEmbeddableConstructor(ClassMetadataInfo $metadata): string
710
    {
711 1
        $paramTypes = [];
712 1
        $paramVariables = [];
713 1
        $params = [];
714 1
        $fields = [];
715
716
        // Resort fields to put optional fields at the end of the method signature.
717 1
        $requiredFields = [];
718 1
        $optionalFields = [];
719
720 1
        foreach ($metadata->fieldMappings as $fieldMapping) {
721 1
            if (empty($fieldMapping['nullable'])) {
722 1
                $requiredFields[] = $fieldMapping;
723
724 1
                continue;
725
            }
726
727 1
            $optionalFields[] = $fieldMapping;
728
        }
729
730 1
        $fieldMappings = array_merge($requiredFields, $optionalFields);
731
732 1
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
733 1
            $paramType = '\\' . ltrim($embeddedClass['class'], '\\');
734 1
            $paramVariable = '$' . $fieldName;
735
736 1
            $paramTypes[] = $paramType;
737 1
            $paramVariables[] = $paramVariable;
738 1
            $params[] = $paramType . ' ' . $paramVariable;
739 1
            $fields[] = '$this->' . $fieldName . ' = ' . $paramVariable . ';';
740
        }
741
742 1
        foreach ($fieldMappings as $fieldMapping) {
743 1
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) {
744
                continue;
745
            }
746
747 1
            $paramTypes[] = $this->getType($fieldMapping['type']) . (!empty($fieldMapping['nullable']) ? '|null' : '');
748 1
            $param = '$' . $fieldMapping['fieldName'];
749 1
            $paramVariables[] = $param;
750
751 1
            if ($fieldMapping['type'] === 'datetime') {
752 1
                $param = $this->getType($fieldMapping['type']) . ' ' . $param;
753
            }
754
755 1
            if (!empty($fieldMapping['nullable'])) {
756 1
                $param .= ' = null';
757
            }
758
759 1
            $params[] = $param;
760
761 1
            $fields[] = '$this->' . $fieldMapping['fieldName'] . ' = $' . $fieldMapping['fieldName'] . ';';
762
        }
763
764 1
        $maxParamTypeLength = max(array_map('strlen', $paramTypes));
765 1
        $paramTags = array_map(
766 1
            function ($type, $variable) use ($maxParamTypeLength) {
767 1
                return '@param ' . $type . str_repeat(' ', $maxParamTypeLength - strlen($type) + 1) . $variable;
768 1
            },
769 1
            $paramTypes,
770 1
            $paramVariables
771
        );
772
773
        // Generate multi line constructor if the signature exceeds 120 characters.
774 1
        if (array_sum(array_map('strlen', $params)) + count($params) * 2 + 29 > 120) {
775 1
            $delimiter = "\n" . $this->spaces;
776 1
            $params = $delimiter . implode(',' . $delimiter, $params) . "\n";
777
        } else {
778 1
            $params = implode(', ', $params);
779
        }
780
781
        $replacements = [
782 1
            '<paramTags>' => implode("\n * ", $paramTags),
783 1
            '<params>'    => $params,
784 1
            '<fields>'    => implode("\n" . $this->spaces, $fields),
785
        ];
786
787 1
        $constructor = str_replace(
788 1
            array_keys($replacements),
789 1
            array_values($replacements),
790 1
            static::$embeddableConstructorMethodTemplate
791
        );
792
793 1
        return $this->prefixCodeWithSpaces($constructor);
794
    }
795
796
    /**
797
     * @todo this won't work if there is a namespace in brackets and a class outside of it.
798
     *
799
     * @param string $src
800
     *
801
     * @return void
802
     */
803 8
    protected function parseTokensInEntityFile($src): void
804
    {
805 8
        $tokens = token_get_all($src);
806 8
        $tokensCount = count($tokens);
807 8
        $lastSeenNamespace = '';
808 8
        $lastSeenClass = false;
809
810 8
        $inNamespace = false;
811 8
        $inClass = false;
812
813 8
        for ($i = 0; $i < $tokensCount; $i++) {
814 8
            $token = $tokens[$i];
815 8
            if (in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
816 8
                continue;
817
            }
818
819 8
            if ($inNamespace) {
820 8
                if (in_array($token[0], [T_NS_SEPARATOR, T_STRING], true)) {
821 8
                    $lastSeenNamespace .= $token[1];
822 8
                } elseif (is_string($token) && in_array($token, [';', '{'], true)) {
823 8
                    $inNamespace = false;
824
                }
825
            }
826
827 8
            if ($inClass) {
828 8
                $inClass = false;
829 8
                $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
830 8
                $this->staticReflection[$lastSeenClass]['properties'] = [];
831 8
                $this->staticReflection[$lastSeenClass]['methods'] = [];
832
            }
833
834 8
            if (T_NAMESPACE === $token[0]) {
835 8
                $lastSeenNamespace = '';
836 8
                $inNamespace = true;
837 8
            } elseif (T_CLASS === $token[0] && T_DOUBLE_COLON !== $tokens[$i-1][0]) {
838 8
                $inClass = true;
839 8
            } elseif (T_FUNCTION === $token[0]) {
840 3
                if (T_STRING === $tokens[$i+2][0]) {
841 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+2][1]);
842
                } elseif ($tokens[$i+2] == '&' && T_STRING === $tokens[$i+3][0]) {
843 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+3][1]);
844
                }
845 8
            } elseif (in_array($token[0], [T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED], true) && T_FUNCTION !== $tokens[$i+2][0]) {
846 4
                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
847
            }
848
        }
849 8
    }
850
851 31
    protected function hasProperty(string $property, ClassMetadataInfo $metadata): bool
852
    {
853 31
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
854
            // don't generate property if its already on the base class.
855 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
856 2
            if ($reflClass->hasProperty($property)) {
857 1
                return true;
858
            }
859
        }
860
861
        // check traits for existing property
862 30
        foreach ($this->getTraits($metadata) as $trait) {
863 2
            if ($trait->hasProperty($property)) {
864 2
                return true;
865
            }
866
        }
867
868
        return (
869 30
            isset($this->staticReflection[$metadata->name]) &&
870 30
            in_array($property, $this->staticReflection[$metadata->name]['properties'], true)
871
        );
872
    }
873
874 32
    protected function hasMethod(string $method, ClassMetadataInfo $metadata): bool
875
    {
876 32
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
877
            // don't generate method if its already on the base class.
878 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
879
880 2
            if ($reflClass->hasMethod($method)) {
881 1
                return true;
882
            }
883
        }
884
885
        // check traits for existing method
886 32
        foreach ($this->getTraits($metadata) as $trait) {
887 2
            if ($trait->hasMethod($method)) {
888 2
                return true;
889
            }
890
        }
891
892
        return (
893 32
            isset($this->staticReflection[$metadata->name]) &&
894 32
            in_array(strtolower($method), $this->staticReflection[$metadata->name]['methods'], true)
895
        );
896
    }
897
898 32
    protected function getTraits(ClassMetadataInfo $metadata): array
899
    {
900 32
        if (! ($metadata->reflClass !== null || class_exists($metadata->name))) {
901 26
            return [];
902
        }
903
904 7
        $reflClass = $metadata->reflClass ?? new \ReflectionClass($metadata->name);
905
906 7
        $traits = [];
907
908 7
        while ($reflClass !== false) {
909 7
            $traits = array_merge($traits, $reflClass->getTraits());
910
911 7
            $reflClass = $reflClass->getParentClass();
912
        }
913
914 7
        return $traits;
915
    }
916
917 31
    protected function hasNamespace(ClassMetadataInfo $metadata): bool
918
    {
919 31
        return (bool) strpos($metadata->name, '\\');
920
    }
921
922 32
    protected function extendsClass(): bool
923
    {
924 32
        return (bool) $this->classToExtend;
925
    }
926
927 2
    protected function getClassToExtend(): ?string
928
    {
929 2
        return $this->classToExtend;
930
    }
931
932 1
    protected function getClassToExtendName(): string
933
    {
934 1
        $refl = new \ReflectionClass($this->getClassToExtend());
935
936 1
        return '\\' . $refl->getName();
0 ignored issues
show
Bug introduced by
Consider using $refl->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
937
    }
938
939 32
    protected function getClassName(ClassMetadataInfo $metadata): string
940
    {
941 32
        return ($pos = strrpos($metadata->name, '\\'))
942 32
            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
943
    }
944
945 31
    protected function getNamespace(ClassMetadataInfo $metadata): string
946
    {
947 31
        return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
948
    }
949
950 31
    protected function generateEntityDocBlock(ClassMetadataInfo $metadata): string
951
    {
952 31
        $lines = [];
953 31
        $lines[] = '/**';
954 31
        $lines[] = ' * ' . $this->getClassName($metadata);
955
956 31
        if ($this->generateAnnotations) {
957 31
            $lines[] = ' *';
958
959
            $methods = [
960 31
                'generateTableAnnotation',
961
                'generateInheritanceAnnotation',
962
                'generateDiscriminatorColumnAnnotation',
963
                'generateDiscriminatorMapAnnotation',
964
                'generateEntityAnnotation',
965
                'generateEntityListenerAnnotation',
966
            ];
967
968 31
            foreach ($methods as $method) {
969 31
                if ($code = $this->$method($metadata)) {
970 31
                    $lines[] = ' * ' . $code;
971
                }
972
            }
973
974 31
            if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata->lifecycleCallbacks 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...
975 10
                $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks';
976
            }
977
        }
978
979 31
        $lines[] = ' */';
980
981 31
        return implode("\n", $lines);
982
    }
983
984 31
    protected function generateEntityAnnotation(ClassMetadataInfo $metadata): string
985
    {
986 31
        $prefix = '@' . $this->annotationsPrefix;
987
988 31
        if ($metadata->isEmbeddedClass) {
989 11
            return $prefix . 'Embeddable';
990
        }
991
992 27
        $customRepository = $metadata->customRepositoryClassName
993 11
            ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")'
994 27
            : '';
995
996 27
        return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository;
997
    }
998
999 31
    protected function generateTableAnnotation(ClassMetadataInfo $metadata): string
1000
    {
1001 31
        if ($metadata->isEmbeddedClass) {
1002 11
            return '';
1003
        }
1004
1005 27
        $table = [];
1006
1007 27
        if (isset($metadata->table['schema'])) {
1008
            $table[] = 'schema="' . $metadata->table['schema'] . '"';
1009
        }
1010
1011 27
        if (isset($metadata->table['name'])) {
1012 24
            $table[] = 'name="' . $metadata->table['name'] . '"';
1013
        }
1014
1015 27
        if (isset($metadata->table['options']) && $metadata->table['options']) {
1016 1
            $table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}';
1017
        }
1018
1019 27
        if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
1020 9
            $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
1021 9
            $table[] = 'uniqueConstraints={' . $constraints . '}';
1022
        }
1023
1024 27
        if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
1025 9
            $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']);
1026 9
            $table[] = 'indexes={' . $constraints . '}';
1027
        }
1028
1029 27
        return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
1030
    }
1031
1032 9
    protected function generateTableConstraints(string $constraintName, array $constraints): string
1033
    {
1034 9
        $annotations = [];
1035 9
        foreach ($constraints as $name => $constraint) {
1036 9
            $columns = [];
1037 9
            foreach ($constraint['columns'] as $column) {
1038 9
                $columns[] = '"' . $column . '"';
1039
            }
1040 9
            $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})';
1041
        }
1042
1043 9
        return implode(', ', $annotations);
1044
    }
1045
1046 31
    protected function generateInheritanceAnnotation(ClassMetadataInfo $metadata): ?string
1047
    {
1048 31
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1049 31
            return null;
1050
        }
1051
1052
        return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")';
1053
    }
1054
1055 31
    protected function generateDiscriminatorColumnAnnotation(ClassMetadataInfo $metadata): ?string
1056
    {
1057 31
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1058 31
            return null;
1059
        }
1060
1061
        $discrColumn = $metadata->discriminatorColumn;
1062
        $columnDefinition = 'name="' . $discrColumn['name']
1063
            . '", type="' . $discrColumn['type']
1064
            . '", length=' . $discrColumn['length'];
1065
1066
        return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
1067
    }
1068
1069 31
    protected function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata): ?string
1070
    {
1071 31
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1072 31
            return null;
1073
        }
1074
1075
        $inheritanceClassMap = [];
1076
1077
        foreach ($metadata->discriminatorMap as $type => $class) {
1078
            $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
1079
        }
1080
1081
        return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
1082
    }
1083
1084 31
    protected function generateEntityStubMethods(ClassMetadataInfo $metadata): string
1085
    {
1086 31
        $methods = [];
1087
1088 31
        foreach ($metadata->fieldMappings as $fieldMapping) {
1089 30
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) {
1090
                continue;
1091
            }
1092
1093 30
            $nullableField = $this->nullableFieldExpression($fieldMapping);
1094
1095 30
            if ((!$metadata->isEmbeddedClass || !$this->embeddablesImmutable)
1096 30
                && (!isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType === ClassMetadataInfo::GENERATOR_TYPE_NONE)
1097 30
                && $code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)
1098
            ) {
1099 27
                $methods[] = $code;
1100
            }
1101
1102 30
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)) {
1103 30
                $methods[] = $code;
1104
            }
1105
        }
1106
1107 31
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1108 10
            if (isset($embeddedClass['declaredField'])) {
1109 1
                continue;
1110
            }
1111
1112 10
            if ( ! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) {
1113 9
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldName, $embeddedClass['class'])) {
1114 9
                    $methods[] = $code;
1115
                }
1116
            }
1117
1118 10
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldName, $embeddedClass['class'])) {
1119 10
                $methods[] = $code;
1120
            }
1121
        }
1122
1123 31
        foreach ($metadata->associationMappings as $associationMapping) {
1124 12
            if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
1125 11
                $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null;
1126 11
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1127 9
                    $methods[] = $code;
1128
                }
1129 11
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1130 11
                    $methods[] = $code;
1131
                }
1132 10
            } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1133 10
                if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1134 10
                    $methods[] = $code;
1135
                }
1136 10
                if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1137 10
                    $methods[] = $code;
1138
                }
1139 10
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], Collection::class)) {
1140 12
                    $methods[] = $code;
1141
                }
1142
            }
1143
        }
1144
1145 31
        return implode("\n\n", $methods);
1146
    }
1147
1148 11
    protected function isAssociationIsNullable(array $associationMapping): bool
1149
    {
1150 11
        if (isset($associationMapping['id']) && $associationMapping['id']) {
1151
            return false;
1152
        }
1153
1154 11
        if (isset($associationMapping['joinColumns'])) {
1155 2
            $joinColumns = $associationMapping['joinColumns'];
1156
        } else {
1157
            //@todo there is no way to retrieve targetEntity metadata
1158 9
            $joinColumns = [];
1159
        }
1160
1161 11
        foreach ($joinColumns as $joinColumn) {
1162 2
            if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
1163 2
                return false;
1164
            }
1165
        }
1166
1167 11
        return true;
1168
    }
1169
1170 32
    protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata): ?string
1171
    {
1172 32
        if (empty($metadata->lifecycleCallbacks)) {
1173 29
            return null;
1174
        }
1175
1176 10
        $methods = [];
1177
1178 10
        foreach ($metadata->lifecycleCallbacks as $name => $callbacks) {
1179 10
            foreach ($callbacks as $callback) {
1180 10
                $methods[] = $this->generateLifecycleCallbackMethod($name, $callback, $metadata);
1181
            }
1182
        }
1183
1184 10
        return implode("\n\n", array_filter($methods));
1185
    }
1186
1187 32
    protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata): string
1188
    {
1189 32
        $lines = [];
1190
1191 32
        foreach ($metadata->associationMappings as $associationMapping) {
1192 13
            if ($this->hasProperty($associationMapping['fieldName'], $metadata)) {
1193 4
                continue;
1194
            }
1195
1196 11
            $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata);
1197 11
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName']
1198 11
                     . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n";
1199
        }
1200
1201 32
        return implode("\n", $lines);
1202
    }
1203
1204 32
    protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata): string
1205
    {
1206 32
        $lines = [];
1207
1208 32
        foreach ($metadata->fieldMappings as $fieldMapping) {
1209 31
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']]) ||
1210 31
                $this->hasProperty($fieldMapping['fieldName'], $metadata) ||
1211 31
                $metadata->isInheritedField($fieldMapping['fieldName'])
1212
            ) {
1213 4
                continue;
1214
            }
1215
1216 29
            $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
1217 29
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName']
1218 29
                     . (isset($fieldMapping['options']['default']) ? ' = ' . var_export($fieldMapping['options']['default'], true) : null) . ";\n";
1219
        }
1220
1221 32
        return implode("\n", $lines);
1222
    }
1223
1224 32
    protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata): string
1225
    {
1226 32
        $lines = [];
1227
1228 32
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1229 10
            if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) {
1230 2
                continue;
1231
            }
1232
1233 10
            $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass);
1234 10
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n";
1235
        }
1236
1237 32
        return implode("\n", $lines);
1238
    }
1239
1240 30
    protected function generateEntityStubMethod(
1241
        ClassMetadataInfo $metadata,
1242
        string $type,
1243
        string $fieldName,
1244
        ?string $typeHint = null,
1245
        ?string $defaultValue = null): string
1246
    {
1247 30
        $methodName = $type . Inflector::classify($fieldName);
1248 30
        $variableName = Inflector::camelize($fieldName);
1249 30
        if (in_array($type, ["add", "remove"])) {
1250 10
            $methodName = Inflector::singularize($methodName);
1251 10
            $variableName = Inflector::singularize($variableName);
1252
        }
1253
1254 30
        if ($this->hasMethod($methodName, $metadata)) {
1255 5
            return '';
1256
        }
1257 30
        $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName);
1258
1259 30
        $var = sprintf('%sMethodTemplate', $type);
1260 30
        $template = static::$$var;
1261
1262 30
        $methodTypeHint = null;
1263 30
        $types          = Type::getTypesMap();
1264 30
        $variableType   = $typeHint ? $this->getType($typeHint) : null;
1265
1266 30
        if ($typeHint && ! isset($types[$typeHint])) {
1267 14
            $variableType   =  '\\' . ltrim($variableType, '\\');
1268 14
            $methodTypeHint =  '\\' . $typeHint . ' ';
1269
        }
1270
1271
        $replacements = [
1272 30
          '<description>'       => ucfirst($type) . ' ' . $variableName . '.',
1273 30
          '<methodTypeHint>'    => $methodTypeHint,
1274 30
          '<variableType>'      => $variableType . (null !== $defaultValue ? ('|' . $defaultValue) : ''),
1275 30
          '<variableName>'      => $variableName,
1276 30
          '<methodName>'        => $methodName,
1277 30
          '<fieldName>'         => $fieldName,
1278 30
          '<variableDefault>'   => ($defaultValue !== null ) ? (' = ' . $defaultValue) : '',
1279 30
          '<entity>'            => $this->getClassName($metadata)
1280
        ];
1281
1282 30
        $method = str_replace(
1283 30
            array_keys($replacements),
1284 30
            array_values($replacements),
1285 30
            $template
1286
        );
1287
1288 30
        return $this->prefixCodeWithSpaces($method);
1289
    }
1290
1291 10
    protected function generateLifecycleCallbackMethod(string $name, string $methodName, ClassMetadataInfo $metadata): ?string
1292
    {
1293 10
        if ($this->hasMethod($methodName, $metadata)) {
1294 2
            return null;
1295
        }
1296
1297 10
        $this->staticReflection[$metadata->name]['methods'][] = $methodName;
1298
1299
        $replacements = [
1300 10
            '<name>'        => $this->annotationsPrefix . ucfirst($name),
1301 10
            '<methodName>'  => $methodName,
1302
        ];
1303
1304 10
        $method = str_replace(
1305 10
            array_keys($replacements),
1306 10
            array_values($replacements),
1307 10
            static::$lifecycleCallbackMethodTemplate
1308
        );
1309
1310 10
        return $this->prefixCodeWithSpaces($method);
1311
    }
1312
1313 11
    protected function generateJoinColumnAnnotation(array $joinColumn): string
1314
    {
1315 11
        $joinColumnAnnot = [];
1316
1317 11
        if (isset($joinColumn['name'])) {
1318 11
            $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"';
1319
        }
1320
1321 11
        if (isset($joinColumn['referencedColumnName'])) {
1322 11
            $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"';
1323
        }
1324
1325 11
        if (isset($joinColumn['unique']) && $joinColumn['unique']) {
1326 1
            $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false');
1327
        }
1328
1329 11
        if (isset($joinColumn['nullable'])) {
1330 1
            $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false');
1331
        }
1332
1333 11
        if (isset($joinColumn['onDelete'])) {
1334 1
            $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
1335
        }
1336
1337 11
        if (isset($joinColumn['columnDefinition'])) {
1338 1
            $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
1339
        }
1340
1341 11
        return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
1342
    }
1343
1344 11
    protected function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata): string
1345
    {
1346 11
        $lines = [];
1347 11
        $lines[] = $this->spaces . '/**';
1348
1349 11
        if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1350 11
            $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection';
1351
        } else {
1352 10
            $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\');
1353
        }
1354
1355 11
        if ($this->generateAnnotations) {
1356 11
            $lines[] = $this->spaces . ' *';
1357
1358 11
            if (isset($associationMapping['id']) && $associationMapping['id']) {
1359
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1360
1361
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1362
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1363
                }
1364
            }
1365
1366 11
            $type = null;
1367 11
            switch ($associationMapping['type']) {
1368 11
                case ClassMetadataInfo::ONE_TO_ONE:
1369 10
                    $type = 'OneToOne';
1370 10
                    break;
1371 11
                case ClassMetadataInfo::MANY_TO_ONE:
1372 1
                    $type = 'ManyToOne';
1373 1
                    break;
1374 11
                case ClassMetadataInfo::ONE_TO_MANY:
1375 1
                    $type = 'OneToMany';
1376 1
                    break;
1377 11
                case ClassMetadataInfo::MANY_TO_MANY:
1378 11
                    $type = 'ManyToMany';
1379 11
                    break;
1380
            }
1381 11
            $typeOptions = [];
1382
1383 11
            if (isset($associationMapping['targetEntity'])) {
1384 11
                $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"';
1385
            }
1386
1387 11
            if (isset($associationMapping['inversedBy'])) {
1388 1
                $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"';
1389
            }
1390
1391 11
            if (isset($associationMapping['mappedBy'])) {
1392 10
                $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"';
1393
            }
1394
1395 11
            if ($associationMapping['cascade']) {
1396 1
                $cascades = [];
1397
1398 1
                if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"';
1399 1
                if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"';
1400 1
                if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"';
1401 1
                if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
1402 1
                if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
1403
1404 1
                if (count($cascades) === 5) {
1405 1
                    $cascades = ['"all"'];
1406
                }
1407
1408 1
                $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
1409
            }
1410
1411 11
            if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
1412 1
                $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
1413
            }
1414
1415 11
            if (isset($associationMapping['fetch']) && $associationMapping['fetch'] !== ClassMetadataInfo::FETCH_LAZY) {
1416
                $fetchMap = [
1417 10
                    ClassMetadataInfo::FETCH_EXTRA_LAZY => 'EXTRA_LAZY',
1418
                    ClassMetadataInfo::FETCH_EAGER      => 'EAGER',
1419
                ];
1420
1421 10
                $typeOptions[] = 'fetch="' . $fetchMap[$associationMapping['fetch']] . '"';
1422
            }
1423
1424 11
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
1425
1426 11
            if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
1427 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({';
1428
1429 1
                $joinColumnsLines = [];
1430
1431 1
                foreach ($associationMapping['joinColumns'] as $joinColumn) {
1432 1
                    if ($joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn)) {
1433 1
                        $joinColumnsLines[] = $this->spaces . ' *   ' . $joinColumnAnnot;
1434
                    }
1435
                }
1436
1437 1
                $lines[] = implode(",\n", $joinColumnsLines);
1438 1
                $lines[] = $this->spaces . ' * })';
1439
            }
1440
1441 11
            if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) {
1442 11
                $joinTable = [];
1443 11
                $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"';
1444
1445 11
                if (isset($associationMapping['joinTable']['schema'])) {
1446
                    $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
1447
                }
1448
1449 11
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
1450 11
                $lines[] = $this->spaces . ' *   joinColumns={';
1451
1452 11
                $joinColumnsLines = [];
1453
1454 11
                foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
1455 11
                    $joinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1456
                }
1457
1458 11
                $lines[] = implode(",". PHP_EOL, $joinColumnsLines);
1459 11
                $lines[] = $this->spaces . ' *   },';
1460 11
                $lines[] = $this->spaces . ' *   inverseJoinColumns={';
1461
1462 11
                $inverseJoinColumnsLines = [];
1463
1464 11
                foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
1465 11
                    $inverseJoinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1466
                }
1467
1468 11
                $lines[] = implode(",". PHP_EOL, $inverseJoinColumnsLines);
1469 11
                $lines[] = $this->spaces . ' *   }';
1470 11
                $lines[] = $this->spaces . ' * )';
1471
            }
1472
1473 11
            if (isset($associationMapping['orderBy'])) {
1474 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({';
1475
1476 1
                foreach ($associationMapping['orderBy'] as $name => $direction) {
1477 1
                    $lines[] = $this->spaces . ' *     "' . $name . '"="' . $direction . '",';
1478
                }
1479
1480 1
                $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
1481 1
                $lines[] = $this->spaces . ' * })';
1482
            }
1483
        }
1484
1485 11
        $lines[] = $this->spaces . ' */';
1486
1487 11
        return implode("\n", $lines);
1488
    }
1489
1490 29
    protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata): string
1491
    {
1492 29
        $lines = [];
1493 29
        $lines[] = $this->spaces . '/**';
1494 29
        $lines[] = $this->spaces . ' * @var '
1495 29
            . $this->getType($fieldMapping['type'])
1496 29
            . ($this->nullableFieldExpression($fieldMapping) ? '|null' : '');
1497
1498 29
        if ($this->generateAnnotations) {
1499 29
            $lines[] = $this->spaces . ' *';
1500
1501 29
            $column = [];
1502 29
            if (isset($fieldMapping['columnName'])) {
1503 29
                $column[] = 'name="' . $fieldMapping['columnName'] . '"';
1504
            }
1505
1506 29
            if (isset($fieldMapping['type'])) {
1507 29
                $column[] = 'type="' . $fieldMapping['type'] . '"';
1508
            }
1509
1510 29
            if (isset($fieldMapping['length'])) {
1511 4
                $column[] = 'length=' . $fieldMapping['length'];
1512
            }
1513
1514 29
            if (isset($fieldMapping['precision'])) {
1515 4
                $column[] = 'precision=' .  $fieldMapping['precision'];
1516
            }
1517
1518 29
            if (isset($fieldMapping['scale'])) {
1519 4
                $column[] = 'scale=' . $fieldMapping['scale'];
1520
            }
1521
1522 29
            if (isset($fieldMapping['nullable'])) {
1523 10
                $column[] = 'nullable=' .  var_export($fieldMapping['nullable'], true);
1524
            }
1525
1526 29
            $options = [];
1527
1528 29
            if (isset($fieldMapping['options']['unsigned']) && $fieldMapping['options']['unsigned']) {
1529 1
                $options[] = '"unsigned"=true';
1530
            }
1531
1532 29
            if ($options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options 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...
1533 1
                $column[] = 'options={'.implode(',', $options).'}';
1534
            }
1535
1536 29
            if (isset($fieldMapping['columnDefinition'])) {
1537 1
                $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
1538
            }
1539
1540 29
            if (isset($fieldMapping['unique'])) {
1541 4
                $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
1542
            }
1543
1544 29
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
1545
1546 29
            if (isset($fieldMapping['id']) && $fieldMapping['id']) {
1547 25
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1548
1549 25
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1550 25
                    $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1551
                }
1552
1553 25
                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...
1554 1
                    $sequenceGenerator = [];
1555
1556 1
                    if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) {
1557 1
                        $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"';
1558
                    }
1559
1560 1
                    if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
1561 1
                        $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize'];
1562
                    }
1563
1564 1
                    if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
1565 1
                        $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue'];
1566
                    }
1567
1568 1
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
1569
                }
1570
            }
1571
1572 29
            if (isset($fieldMapping['version']) && $fieldMapping['version']) {
1573
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version';
1574
            }
1575
        }
1576
1577 29
        $lines[] = $this->spaces . ' */';
1578
1579 29
        return implode("\n", $lines);
1580
    }
1581
1582 10
    protected function generateEmbeddedPropertyDocBlock(array $embeddedClass): string
1583
    {
1584 10
        $lines = [];
1585 10
        $lines[] = $this->spaces . '/**';
1586 10
        $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\');
1587
1588 10
        if ($this->generateAnnotations) {
1589 10
            $lines[] = $this->spaces . ' *';
1590
1591 10
            $embedded = ['class="' . $embeddedClass['class'] . '"'];
1592
1593 10
            if (isset($embeddedClass['columnPrefix'])) {
1594 8
                if (is_string($embeddedClass['columnPrefix'])) {
1595 1
                    $embedded[] = 'columnPrefix="' . $embeddedClass['columnPrefix'] . '"';
1596
                } else {
1597 7
                    $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true);
1598
                }
1599
            }
1600
1601 10
            $lines[] = $this->spaces . ' * @' .
1602 10
                $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')';
1603
        }
1604
1605 10
        $lines[] = $this->spaces . ' */';
1606
1607 10
        return implode("\n", $lines);
1608
    }
1609
1610 31
    protected function generateEntityListenerAnnotation(ClassMetadataInfo $metadata): ?string
1611
    {
1612 31
        if (0 === \count($metadata->entityListeners)) {
1613 30
            return null;
1614
        }
1615
1616 1
        $processedClasses = [];
1617 1
        foreach ($metadata->entityListeners as $event => $eventListeners) {
1618 1
            foreach ($eventListeners as $eventListener) {
1619 1
                $processedClasses[] = '"' . $eventListener['class'] . '"';
1620
            }
1621
        }
1622
1623 1
        return \sprintf(
1624 1
            '%s%s({%s})',
1625 1
            '@' . $this->annotationsPrefix,
1626 1
            'EntityListeners',
1627 1
            \implode(',', \array_unique($processedClasses))
1628
        );
1629
    }
1630
1631 31
    protected function prefixCodeWithSpaces($code, $num = 1): string
1632
    {
1633 31
        $lines = explode("\n", $code);
1634
1635 31
        foreach ($lines as $key => $value) {
1636 31
            if ( ! empty($value)) {
1637 31
                $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
1638
            }
1639
        }
1640
1641 31
        return implode("\n", $lines);
1642
    }
1643
1644
    /**
1645
     * @param integer $type The inheritance type used by the class and its subclasses.
1646
     *
1647
     * @return string The literal string for the inheritance type.
1648
     *
1649
     * @throws \InvalidArgumentException When the inheritance type does not exist.
1650
     */
1651 1
    protected function getInheritanceTypeString($type): string
1652
    {
1653 1
        if ( ! isset(static::$inheritanceTypeMap[$type])) {
1654 1
            throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type));
1655
        }
1656
1657 1
        return static::$inheritanceTypeMap[$type];
1658
    }
1659
1660
    /**
1661
     * @param integer $type The policy used for change-tracking for the mapped class.
1662
     *
1663
     * @return string The literal string for the change-tracking type.
1664
     *
1665
     * @throws \InvalidArgumentException When the change-tracking type does not exist.
1666
     */
1667 1
    protected function getChangeTrackingPolicyString($type): string
1668
    {
1669 1
        if ( ! isset(static::$changeTrackingPolicyMap[$type])) {
1670 1
            throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type));
1671
        }
1672
1673 1
        return static::$changeTrackingPolicyMap[$type];
1674
    }
1675
1676
    /**
1677
     * @param integer $type The generator to use for the mapped class.
1678
     *
1679
     * @return string The literal string for the generator type.
1680
     *
1681
     * @throws \InvalidArgumentException    When the generator type does not exist.
1682
     */
1683 26
    protected function getIdGeneratorTypeString($type): string
1684
    {
1685 26
        if ( ! isset(static::$generatorStrategyMap[$type])) {
1686 1
            throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type));
1687
        }
1688
1689 26
        return static::$generatorStrategyMap[$type];
1690
    }
1691
1692 31
    private function nullableFieldExpression(array $fieldMapping): ?string
1693
    {
1694 31
        if (isset($fieldMapping['nullable']) && true === $fieldMapping['nullable']) {
1695 7
            return 'null';
1696
        }
1697
1698 31
        return null;
1699
    }
1700
1701
    /**
1702
     * Exports (nested) option elements.
1703
     *
1704
     * @param array $options
1705
     *
1706
     * @return string
1707
     */
1708 1
    private function exportTableOptions(array $options): string
1709
    {
1710 1
        $optionsStr = [];
1711
1712 1
        foreach ($options as $name => $option) {
1713 1
            if (is_array($option)) {
1714 1
                $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}';
1715
            } else {
1716 1
                $optionsStr[] = '"' . $name . '"="' . (string) $option . '"';
1717
            }
1718
        }
1719
1720 1
        return implode(',', $optionsStr);
1721
    }
1722
}
1723