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

EntityGenerator::generateEmbeddableConstructor()   B

Complexity

Conditions 10
Paths 120

Size

Total Lines 85
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 49
CRAP Score 10.0008

Importance

Changes 0
Metric Value
cc 10
eloc 51
c 0
b 0
f 0
nop 1
dl 0
loc 85
ccs 49
cts 50
cp 0.98
crap 10.0008
rs 7.069
nc 120

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Tools;
21
22
use Doctrine\Common\Collections\Collection;
23
use Doctrine\Common\Inflector\Inflector;
24
use Doctrine\DBAL\Types\Type;
25
use Doctrine\ORM\Mapping\ClassMetadataInfo;
26
use function str_replace;
27
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