Passed
Push — 2.7 ( 2785cd...1edfca )
by Benjamin
06:54 queued 11s
created

EntityGenerator::generateEntityNamespace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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