Completed
Pull Request — master (#6494)
by Artem
10:22
created

EntityGenerator::generateInheritanceAnnotation()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
dl 0
loc 6
c 0
b 0
f 0
ccs 3
cts 4
cp 0.75
rs 9.4285
cc 2
eloc 3
nc 2
nop 1
crap 2.0625
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Tools;
21
22
use Doctrine\Common\Collections\Collection;
23
use Doctrine\Common\Util\Inflector;
24
use Doctrine\DBAL\Types\Type;
25
use Doctrine\ORM\Mapping\ClassMetadataInfo;
26
27
/**
28
 * Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances.
29
 *
30
 *     [php]
31
 *     $classes = $em->getClassMetadataFactory()->getAllMetadata();
32
 *
33
 *     $generator = new \Doctrine\ORM\Tools\EntityGenerator();
34
 *     $generator->setGenerateAnnotations(true);
35
 *     $generator->setGenerateStubMethods(true);
36
 *     $generator->setRegenerateEntityIfExists(false);
37
 *     $generator->setUpdateEntityIfExists(true);
38
 *     $generator->generate($classes, '/path/to/generate/entities');
39
 *
40
 *
41
 * @link    www.doctrine-project.org
42
 * @since   2.0
43
 * @author  Benjamin Eberlei <[email protected]>
44
 * @author  Guilherme Blanco <[email protected]>
45
 * @author  Jonathan Wage <[email protected]>
46
 * @author  Roman Borschel <[email protected]>
47
 */
48
class EntityGenerator
49
{
50
    /**
51
     * Specifies class fields should be protected.
52
     */
53
    const FIELD_VISIBLE_PROTECTED = 'protected';
54
55
    /**
56
     * Specifies class fields should be private.
57
     */
58
    const FIELD_VISIBLE_PRIVATE = 'private';
59
60
    /**
61
     * @var bool
62
     */
63
    protected $backupExisting = true;
64
65
    /**
66
     * The extension to use for written php files.
67
     *
68
     * @var string
69
     */
70
    protected $extension = '.php';
71
72
    /**
73
     * Whether or not the current ClassMetadataInfo instance is new or old.
74
     *
75
     * @var boolean
76
     */
77
    protected $isNew = true;
78
79
    /**
80
     * @var array
81
     */
82
    protected $staticReflection = [];
83
84
    /**
85
     * Number of spaces to use for indention in generated code.
86
     */
87
    protected $numSpaces = 4;
88
89
    /**
90
     * The actual spaces to use for indention.
91
     *
92
     * @var string
93
     */
94
    protected $spaces = '    ';
95
96
    /**
97
     * The class all generated entities should extend.
98
     *
99
     * @var string
100
     */
101
    protected $classToExtend;
102
103
    /**
104
     * Whether or not to generation annotations.
105
     *
106
     * @var boolean
107
     */
108
    protected $generateAnnotations = false;
109
110
    /**
111
     * @var string
112
     */
113
    protected $annotationsPrefix = '';
114
115
    /**
116
     * Whether or not to generate sub methods.
117
     *
118
     * @var boolean
119
     */
120
    protected $generateEntityStubMethods = false;
121
122
    /**
123
     * Whether or not to update the entity class if it exists already.
124
     *
125
     * @var boolean
126
     */
127
    protected $updateEntityIfExists = false;
128
129
    /**
130
     * Whether or not to re-generate entity class if it exists already.
131
     *
132
     * @var boolean
133
     */
134
    protected $regenerateEntityIfExists = false;
135
136
    /**
137
     * Visibility of the field
138
     *
139
     * @var string
140
     */
141
    protected $fieldVisibility = 'private';
142
143
    /**
144
     * Whether or not to make generated embeddables immutable.
145
     *
146
     * @var boolean.
147
     */
148
    protected $embeddablesImmutable = false;
149
150
    /**
151
     * Hash-map for handle types.
152
     *
153
     * @var array
154
     */
155
    protected $typeAlias = [
156
        Type::DATETIMETZ    => '\DateTime',
157
        Type::DATETIME      => '\DateTime',
158
        Type::DATE          => '\DateTime',
159
        Type::TIME          => '\DateTime',
160
        Type::OBJECT        => '\stdClass',
161
        Type::INTEGER       => 'int',
162
        Type::BIGINT        => 'int',
163
        Type::SMALLINT      => 'int',
164
        Type::TEXT          => 'string',
165
        Type::BLOB          => 'string',
166
        Type::DECIMAL       => 'string',
167
        Type::JSON_ARRAY    => 'array',
168
        Type::SIMPLE_ARRAY  => 'array',
169
        Type::BOOLEAN       => 'bool',
170
    ];
171
172
    /**
173
     * Hash-map to handle generator types string.
174
     *
175
     * @var array
176
     */
177
    protected static $generatorStrategyMap = [
178
        ClassMetadataInfo::GENERATOR_TYPE_AUTO      => 'AUTO',
179
        ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE  => 'SEQUENCE',
180
        ClassMetadataInfo::GENERATOR_TYPE_TABLE     => 'TABLE',
181
        ClassMetadataInfo::GENERATOR_TYPE_IDENTITY  => 'IDENTITY',
182
        ClassMetadataInfo::GENERATOR_TYPE_NONE      => 'NONE',
183
        ClassMetadataInfo::GENERATOR_TYPE_UUID      => 'UUID',
184
        ClassMetadataInfo::GENERATOR_TYPE_CUSTOM    => 'CUSTOM'
185
    ];
186
187
    /**
188
     * Hash-map to handle the change tracking policy string.
189
     *
190
     * @var array
191
     */
192
    protected static $changeTrackingPolicyMap = [
193
        ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT  => 'DEFERRED_IMPLICIT',
194
        ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT  => 'DEFERRED_EXPLICIT',
195
        ClassMetadataInfo::CHANGETRACKING_NOTIFY             => 'NOTIFY',
196
    ];
197
198
    /**
199
     * Hash-map to handle the inheritance type string.
200
     *
201
     * @var array
202
     */
203
    protected static $inheritanceTypeMap = [
204
        ClassMetadataInfo::INHERITANCE_TYPE_NONE            => 'NONE',
205
        ClassMetadataInfo::INHERITANCE_TYPE_JOINED          => 'JOINED',
206
        ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE    => 'SINGLE_TABLE',
207
        ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS => 'TABLE_PER_CLASS',
208
    ];
209
210
    /**
211
     * @var string
212
     */
213
    protected static $classTemplate =
214
'<?php
215
216
<namespace>
217
<useStatement>
218
<entityAnnotation>
219
<entityClassName>
220
{
221
<entityBody>
222
}
223
';
224
225
    /**
226
     * @var string
227
     */
228
    protected static $getMethodTemplate =
229
'/**
230
 * <description>
231
 *
232
 * @return <variableType>
233
 */
234
public function <methodName>()
235
{
236
<spaces>return $this-><fieldName>;
237
}';
238
239
    /**
240
     * @var string
241
     */
242
    protected static $setMethodTemplate =
243
'/**
244
 * <description>
245
 *
246
 * @param <variableType> $<variableName>
247
 *
248
 * @return <entity>
249
 */
250
public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
251
{
252
<spaces>$this-><fieldName> = $<variableName>;
253
254
<spaces>return $this;
255
}';
256
257
    /**
258
     * @var string
259
     */
260
    protected static $addMethodTemplate =
261
'/**
262
 * <description>
263
 *
264
 * @param <variableType> $<variableName>
265
 *
266
 * @return <entity>
267
 */
268
public function <methodName>(<methodTypeHint>$<variableName>)
269
{
270
<spaces>$this-><fieldName>[] = $<variableName>;
271
272
<spaces>return $this;
273
}';
274
275
    /**
276
     * @var string
277
     */
278
    protected static $removeMethodTemplate =
279
'/**
280
 * <description>
281
 *
282
 * @param <variableType> $<variableName>
283
 *
284
 * @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
285
 */
286
public function <methodName>(<methodTypeHint>$<variableName>)
287
{
288
<spaces>return $this-><fieldName>->removeElement($<variableName>);
289
}';
290
291
    /**
292
     * @var string
293
     */
294
    protected static $lifecycleCallbackMethodTemplate =
295
'/**
296
 * @<name>
297
 */
298
public function <methodName>()
299
{
300
<spaces>// Add your code here
301
}';
302
303
    /**
304
     * @var string
305
     */
306
    protected static $constructorMethodTemplate =
307
'/**
308
 * Constructor
309
 */
310
public function __construct()
311
{
312
<spaces><collections>
313
}
314
';
315
316
    /**
317
     * @var string
318
     */
319
    protected static $embeddableConstructorMethodTemplate =
320
'/**
321
 * Constructor
322
 *
323
 * <paramTags>
324
 */
325
public function __construct(<params>)
326
{
327
<spaces><fields>
328
}
329
';
330
331
    /**
332
     * Constructor.
333
     */
334 40
    public function __construct()
335
    {
336 40
        if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
337 40
            $this->annotationsPrefix = 'ORM\\';
338
        }
339 40
    }
340
341
    /**
342
     * Generates and writes entity classes for the given array of ClassMetadataInfo instances.
343
     *
344
     * @param array  $metadatas
345
     * @param string $outputDirectory
346
     *
347
     * @return void
348
     */
349
    public function generate(array $metadatas, $outputDirectory)
350
    {
351
        foreach ($metadatas as $metadata) {
352
            $this->writeEntityClass($metadata, $outputDirectory);
353
        }
354
    }
355
356
    /**
357
     * Generates and writes entity class to disk for the given ClassMetadataInfo instance.
358
     *
359
     * @param ClassMetadataInfo $metadata
360
     * @param string            $outputDirectory
361
     *
362
     * @return void
363
     *
364
     * @throws \RuntimeException
365
     */
366 31
    public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
367
    {
368 31
        $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension;
369 31
        $dir = dirname($path);
370
371 31
        if ( ! is_dir($dir)) {
372 2
            mkdir($dir, 0775, true);
373
        }
374
375 31
        $this->isNew = ! file_exists($path) || $this->regenerateEntityIfExists;
376
377 31
        if ( ! $this->isNew) {
378 3
            $this->parseTokensInEntityFile(file_get_contents($path));
379
        } else {
380 30
            $this->staticReflection[$metadata->name] = ['properties' => [], 'methods' => []];
381
        }
382
383 31
        if ($this->backupExisting && file_exists($path)) {
384 3
            $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~";
385 3
            if (!copy($path, $backupPath)) {
386
                throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed.");
387
            }
388
        }
389
390
        // If entity doesn't exist or we're re-generating the entities entirely
391 31
        if ($this->isNew) {
392 30
            file_put_contents($path, $this->generateEntityClass($metadata));
393
        // If entity exists and we're allowed to update the entity class
394 3
        } elseif ($this->updateEntityIfExists) {
395 3
            file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path));
396
        }
397 31
        chmod($path, 0664);
398 31
    }
399
400
    /**
401
     * Generates a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance.
402
     *
403
     * @param ClassMetadataInfo $metadata
404
     *
405
     * @return string
406
     */
407 31
    public function generateEntityClass(ClassMetadataInfo $metadata)
408
    {
409
        $placeHolders = [
410 31
            '<namespace>',
411
            '<useStatement>',
412
            '<entityAnnotation>',
413
            '<entityClassName>',
414
            '<entityBody>'
415
        ];
416
417
        $replacements = [
418 31
            $this->generateEntityNamespace($metadata),
419 31
            $this->generateEntityUse(),
420 31
            $this->generateEntityDocBlock($metadata),
421 31
            $this->generateEntityClassName($metadata),
422 31
            $this->generateEntityBody($metadata)
423
        ];
424
425 31
        $code = str_replace($placeHolders, $replacements, static::$classTemplate);
426
427 31
        return str_replace('<spaces>', $this->spaces, $code);
428
    }
429
430
    /**
431
     * Generates the updated code for the given ClassMetadataInfo and entity at path.
432
     *
433
     * @param ClassMetadataInfo $metadata
434
     * @param string            $path
435
     *
436
     * @return string
437
     */
438 3
    public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
439
    {
440 3
        $currentCode = file_get_contents($path);
441
442 3
        $body = $this->generateEntityBody($metadata);
443 3
        $body = str_replace('<spaces>', $this->spaces, $body);
444 3
        $last = strrpos($currentCode, '}');
445
446 3
        return substr($currentCode, 0, $last) . $body . ($body ? "\n" : '') . "}\n";
447
    }
448
449
    /**
450
     * Sets the number of spaces the exported class should have.
451
     *
452
     * @param integer $numSpaces
453
     *
454
     * @return void
455
     */
456
    public function setNumSpaces($numSpaces)
457
    {
458
        $this->spaces = str_repeat(' ', $numSpaces);
459
        $this->numSpaces = $numSpaces;
460
    }
461
462
    /**
463
     * Sets the extension to use when writing php files to disk.
464
     *
465
     * @param string $extension
466
     *
467
     * @return void
468
     */
469
    public function setExtension($extension)
470
    {
471
        $this->extension = $extension;
472
    }
473
474
    /**
475
     * Sets the name of the class the generated classes should extend from.
476
     *
477
     * @param string $classToExtend
478
     *
479
     * @return void
480
     */
481 1
    public function setClassToExtend($classToExtend)
482
    {
483 1
        $this->classToExtend = $classToExtend;
484 1
    }
485
486
    /**
487
     * Sets whether or not to generate annotations for the entity.
488
     *
489
     * @param bool $bool
490
     *
491
     * @return void
492
     */
493 40
    public function setGenerateAnnotations($bool)
494
    {
495 40
        $this->generateAnnotations = $bool;
496 40
    }
497
498
    /**
499
     * Sets the class fields visibility for the entity (can either be private or protected).
500
     *
501
     * @param bool $visibility
502
     *
503
     * @return void
504
     *
505
     * @throws \InvalidArgumentException
506
     */
507 39
    public function setFieldVisibility($visibility)
508
    {
509 39
        if ($visibility !== static::FIELD_VISIBLE_PRIVATE && $visibility !== static::FIELD_VISIBLE_PROTECTED) {
510
            throw new \InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility);
511
        }
512
513 39
        $this->fieldVisibility = $visibility;
514 39
    }
515
516
    /**
517
     * Sets whether or not to generate immutable embeddables.
518
     *
519
     * @param boolean $embeddablesImmutable
520
     */
521 1
    public function setEmbeddablesImmutable($embeddablesImmutable)
522
    {
523 1
        $this->embeddablesImmutable = (boolean) $embeddablesImmutable;
524 1
    }
525
526
    /**
527
     * Sets an annotation prefix.
528
     *
529
     * @param string $prefix
530
     *
531
     * @return void
532
     */
533 40
    public function setAnnotationPrefix($prefix)
534
    {
535 40
        $this->annotationsPrefix = $prefix;
536 40
    }
537
538
    /**
539
     * Sets whether or not to try and update the entity if it already exists.
540
     *
541
     * @param bool $bool
542
     *
543
     * @return void
544
     */
545 40
    public function setUpdateEntityIfExists($bool)
546
    {
547 40
        $this->updateEntityIfExists = $bool;
548 40
    }
549
550
    /**
551
     * Sets whether or not to regenerate the entity if it exists.
552
     *
553
     * @param bool $bool
554
     *
555
     * @return void
556
     */
557 40
    public function setRegenerateEntityIfExists($bool)
558
    {
559 40
        $this->regenerateEntityIfExists = $bool;
560 40
    }
561
562
    /**
563
     * Sets whether or not to generate stub methods for the entity.
564
     *
565
     * @param bool $bool
566
     *
567
     * @return void
568
     */
569 40
    public function setGenerateStubMethods($bool)
570
    {
571 40
        $this->generateEntityStubMethods = $bool;
572 40
    }
573
574
    /**
575
     * Should an existing entity be backed up if it already exists?
576
     *
577
     * @param bool $bool
578
     *
579
     * @return void
580
     */
581 1
    public function setBackupExisting($bool)
582
    {
583 1
        $this->backupExisting = $bool;
584 1
    }
585
586
    /**
587
     * @param string $type
588
     *
589
     * @return string
590
     */
591 31
    protected function getType($type)
592
    {
593 31
        if (isset($this->typeAlias[$type])) {
594 30
            return $this->typeAlias[$type];
595
        }
596
597 20
        return $type;
598
    }
599
600
    /**
601
     * @param ClassMetadataInfo $metadata
602
     *
603
     * @return string
604
     */
605 31
    protected function generateEntityNamespace(ClassMetadataInfo $metadata)
606
    {
607 31
        if ($this->hasNamespace($metadata)) {
608 31
            return 'namespace ' . $this->getNamespace($metadata) .';';
609
        }
610 2
    }
611
612 31
    protected function generateEntityUse()
613
    {
614 31
        if ($this->generateAnnotations) {
615 31
            return "\n".'use Doctrine\ORM\Mapping as ORM;'."\n";
616
        } else {
617
            return "";
618
        }
619
    }
620
621
    /**
622
     * @param ClassMetadataInfo $metadata
623
     *
624
     * @return string
625
     */
626 31
    protected function generateEntityClassName(ClassMetadataInfo $metadata)
627
    {
628 31
        return 'class ' . $this->getClassName($metadata) .
629 31
            ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
630
    }
631
632
    /**
633
     * @param ClassMetadataInfo $metadata
634
     *
635
     * @return string
636
     */
637 32
    protected function generateEntityBody(ClassMetadataInfo $metadata)
638
    {
639 32
        $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata);
640 32
        $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata);
641 32
        $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata);
642 32
        $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null;
643 32
        $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata);
644
645 32
        $code = [];
646
647 32
        if ($fieldMappingProperties) {
648 29
            $code[] = $fieldMappingProperties;
649
        }
650
651 32
        if ($embeddedProperties) {
652 10
            $code[] = $embeddedProperties;
653
        }
654
655 32
        if ($associationMappingProperties) {
656 11
            $code[] = $associationMappingProperties;
657
        }
658
659 32
        $code[] = $this->generateEntityConstructor($metadata);
660
661 32
        if ($stubMethods) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stubMethods of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
662 30
            $code[] = $stubMethods;
663
        }
664
665 32
        if ($lifecycleCallbackMethods) {
666 10
            $code[] = $lifecycleCallbackMethods;
667
        }
668
669 32
        return implode("\n", $code);
670
    }
671
672
    /**
673
     * @param ClassMetadataInfo $metadata
674
     *
675
     * @return string
676
     */
677 32
    protected function generateEntityConstructor(ClassMetadataInfo $metadata)
678
    {
679 32
        if ($this->hasMethod('__construct', $metadata)) {
680 2
            return '';
681
        }
682
683 32
        if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) {
684 1
            return $this->generateEmbeddableConstructor($metadata);
685
        }
686
687 31
        $collections = [];
688
689 31
        foreach ($metadata->associationMappings as $mapping) {
690 13
            if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
691 13
                $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
692
            }
693
        }
694
695 31
        if ($collections) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $collections of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
696 11
            return $this->prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->spaces, $collections), static::$constructorMethodTemplate));
697
        }
698
699 27
        return '';
700
    }
701
702
    /**
703
     * @param ClassMetadataInfo $metadata
704
     *
705
     * @return string
706
     */
707 1
    private function generateEmbeddableConstructor(ClassMetadataInfo $metadata)
708
    {
709 1
        $paramTypes = [];
710 1
        $paramVariables = [];
711 1
        $params = [];
712 1
        $fields = [];
713
714
        // Resort fields to put optional fields at the end of the method signature.
715 1
        $requiredFields = [];
716 1
        $optionalFields = [];
717
718 1
        foreach ($metadata->fieldMappings as $fieldMapping) {
719 1
            if (empty($fieldMapping['nullable'])) {
720 1
                $requiredFields[] = $fieldMapping;
721
722 1
                continue;
723
            }
724
725 1
            $optionalFields[] = $fieldMapping;
726
        }
727
728 1
        $fieldMappings = array_merge($requiredFields, $optionalFields);
729
730 1
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
731 1
            $paramType = '\\' . ltrim($embeddedClass['class'], '\\');
732 1
            $paramVariable = '$' . $fieldName;
733
734 1
            $paramTypes[] = $paramType;
735 1
            $paramVariables[] = $paramVariable;
736 1
            $params[] = $paramType . ' ' . $paramVariable;
737 1
            $fields[] = '$this->' . $fieldName . ' = ' . $paramVariable . ';';
738
        }
739
740 1
        foreach ($fieldMappings as $fieldMapping) {
741 1
            if (isset($fieldMapping['declaredField']) &&
742 1
                isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
743
            ) {
744
                continue;
745
            }
746
747 1
            $paramTypes[] = $this->getType($fieldMapping['type']) . (!empty($fieldMapping['nullable']) ? '|null' : '');
748 1
            $param = '$' . $fieldMapping['fieldName'];
749 1
            $paramVariables[] = $param;
750
751 1
            if ($fieldMapping['type'] === 'datetime') {
752 1
                $param = $this->getType($fieldMapping['type']) . ' ' . $param;
753
            }
754
755 1
            if (!empty($fieldMapping['nullable'])) {
756 1
                $param .= ' = null';
757
            }
758
759 1
            $params[] = $param;
760
761 1
            $fields[] = '$this->' . $fieldMapping['fieldName'] . ' = $' . $fieldMapping['fieldName'] . ';';
762
        }
763
764 1
        $maxParamTypeLength = max(array_map('strlen', $paramTypes));
765 1
        $paramTags = array_map(
766 1
            function ($type, $variable) use ($maxParamTypeLength) {
767 1
                return '@param ' . $type . str_repeat(' ', $maxParamTypeLength - strlen($type) + 1) . $variable;
768 1
            },
769
            $paramTypes,
770 1
            $paramVariables
771
        );
772
773
        // Generate multi line constructor if the signature exceeds 120 characters.
774 1
        if (array_sum(array_map('strlen', $params)) + count($params) * 2 + 29 > 120) {
775 1
            $delimiter = "\n" . $this->spaces;
776 1
            $params = $delimiter . implode(',' . $delimiter, $params) . "\n";
777
        } else {
778 1
            $params = implode(', ', $params);
779
        }
780
781
        $replacements = [
782 1
            '<paramTags>' => implode("\n * ", $paramTags),
783 1
            '<params>'    => $params,
784 1
            '<fields>'    => implode("\n" . $this->spaces, $fields),
785
        ];
786
787 1
        $constructor = str_replace(
788 1
            array_keys($replacements),
789 1
            array_values($replacements),
790 1
            static::$embeddableConstructorMethodTemplate
791
        );
792
793 1
        return $this->prefixCodeWithSpaces($constructor);
794
    }
795
796
    /**
797
     * @todo this won't work if there is a namespace in brackets and a class outside of it.
798
     *
799
     * @param string $src
800
     *
801
     * @return void
802
     */
803 8
    protected function parseTokensInEntityFile($src)
804
    {
805 8
        $tokens = token_get_all($src);
806 8
        $tokensCount = count($tokens);
807 8
        $lastSeenNamespace = '';
808 8
        $lastSeenClass = false;
809
810 8
        $inNamespace = false;
811 8
        $inClass = false;
812
813 8
        for ($i = 0; $i < $tokensCount; $i++) {
814 8
            $token = $tokens[$i];
815 8
            if (in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
816 8
                continue;
817
            }
818
819 8
            if ($inNamespace) {
820 8
                if (in_array($token[0], [T_NS_SEPARATOR, T_STRING], true)) {
821 8
                    $lastSeenNamespace .= $token[1];
822 8
                } elseif (is_string($token) && in_array($token, [';', '{'], true)) {
823 8
                    $inNamespace = false;
824
                }
825
            }
826
827 8
            if ($inClass) {
828 8
                $inClass = false;
829 8
                $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
830 8
                $this->staticReflection[$lastSeenClass]['properties'] = [];
831 8
                $this->staticReflection[$lastSeenClass]['methods'] = [];
832
            }
833
834 8
            if (T_NAMESPACE === $token[0]) {
835 8
                $lastSeenNamespace = '';
836 8
                $inNamespace = true;
837 8
            } elseif (T_CLASS === $token[0] && T_DOUBLE_COLON !== $tokens[$i-1][0]) {
838 8
                $inClass = true;
839 8
            } elseif (T_FUNCTION === $token[0]) {
840 3
                if (T_STRING === $tokens[$i+2][0]) {
841 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+2][1]);
842
                } elseif ($tokens[$i+2] == '&' && T_STRING === $tokens[$i+3][0]) {
843 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+3][1]);
844
                }
845 8
            } elseif (in_array($token[0], [T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED], true) && T_FUNCTION !== $tokens[$i+2][0]) {
846 4
                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
847
            }
848
        }
849 8
    }
850
851
    /**
852
     * @param string            $property
853
     * @param ClassMetadataInfo $metadata
854
     *
855
     * @return bool
856
     */
857 31
    protected function hasProperty($property, ClassMetadataInfo $metadata)
858
    {
859 31
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
860
            // don't generate property if its already on the base class.
861 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
862 2
            if ($reflClass->hasProperty($property)) {
863 1
                return true;
864
            }
865
        }
866
867
        // check traits for existing property
868 30
        foreach ($this->getTraits($metadata) as $trait) {
869 2
            if ($trait->hasProperty($property)) {
870 2
                return true;
871
            }
872
        }
873
874
        return (
875 30
            isset($this->staticReflection[$metadata->name]) &&
876 30
            in_array($property, $this->staticReflection[$metadata->name]['properties'], true)
877
        );
878
    }
879
880
    /**
881
     * @param string            $method
882
     * @param ClassMetadataInfo $metadata
883
     *
884
     * @return bool
885
     */
886 32
    protected function hasMethod($method, ClassMetadataInfo $metadata)
887
    {
888 32
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
889
            // don't generate method if its already on the base class.
890 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
891
892 2
            if ($reflClass->hasMethod($method)) {
893 1
                return true;
894
            }
895
        }
896
897
        // check traits for existing method
898 32
        foreach ($this->getTraits($metadata) as $trait) {
899 2
            if ($trait->hasMethod($method)) {
900 2
                return true;
901
            }
902
        }
903
904
        return (
905 32
            isset($this->staticReflection[$metadata->name]) &&
906 32
            in_array(strtolower($method), $this->staticReflection[$metadata->name]['methods'], true)
907
        );
908
    }
909
910
    /**
911
     * @param ClassMetadataInfo $metadata
912
     *
913
     * @return array
914
     */
915 32
    protected function getTraits(ClassMetadataInfo $metadata)
916
    {
917 32
        if (! ($metadata->reflClass !== null || class_exists($metadata->name))) {
918 26
            return [];
919
        }
920
921 7
        $reflClass = $metadata->reflClass === null
922 1
            ? new \ReflectionClass($metadata->name)
923 7
            : $metadata->reflClass;
924
925 7
        $traits = [];
926
927 7
        while ($reflClass !== false) {
928 7
            $traits = array_merge($traits, $reflClass->getTraits());
929
930 7
            $reflClass = $reflClass->getParentClass();
931
        }
932
933 7
        return $traits;
934
    }
935
936
    /**
937
     * @param ClassMetadataInfo $metadata
938
     *
939
     * @return bool
940
     */
941 31
    protected function hasNamespace(ClassMetadataInfo $metadata)
942
    {
943 31
        return (bool) strpos($metadata->name, '\\');
944
    }
945
946
    /**
947
     * @return bool
948
     */
949 32
    protected function extendsClass()
950
    {
951 32
        return (bool) $this->classToExtend;
952
    }
953
954
    /**
955
     * @return string
956
     */
957 2
    protected function getClassToExtend()
958
    {
959 2
        return $this->classToExtend;
960
    }
961
962
    /**
963
     * @return string
964
     */
965 1
    protected function getClassToExtendName()
966
    {
967 1
        $refl = new \ReflectionClass($this->getClassToExtend());
968
969 1
        return '\\' . $refl->getName();
0 ignored issues
show
Bug introduced by
Consider using $refl->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
970
    }
971
972
    /**
973
     * @param ClassMetadataInfo $metadata
974
     *
975
     * @return string
976
     */
977 32
    protected function getClassName(ClassMetadataInfo $metadata)
978
    {
979 32
        return ($pos = strrpos($metadata->name, '\\'))
980 32
            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
981
    }
982
983
    /**
984
     * @param ClassMetadataInfo $metadata
985
     *
986
     * @return string
987
     */
988 31
    protected function getNamespace(ClassMetadataInfo $metadata)
989
    {
990 31
        return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
991
    }
992
993
    /**
994
     * @param ClassMetadataInfo $metadata
995
     *
996
     * @return string
997
     */
998 31
    protected function generateEntityDocBlock(ClassMetadataInfo $metadata)
999
    {
1000 31
        $lines = [];
1001 31
        $lines[] = '/**';
1002 31
        $lines[] = ' * ' . $this->getClassName($metadata);
1003
1004 31
        if ($this->generateAnnotations) {
1005 31
            $lines[] = ' *';
1006
1007
            $methods = [
1008 31
                'generateTableAnnotation',
1009
                'generateInheritanceAnnotation',
1010
                'generateDiscriminatorColumnAnnotation',
1011
                'generateDiscriminatorMapAnnotation',
1012
                'generateEntityAnnotation',
1013
            ];
1014
1015 31
            foreach ($methods as $method) {
1016 31
                if ($code = $this->$method($metadata)) {
1017 31
                    $lines[] = ' * ' . $code;
1018
                }
1019
            }
1020
1021 31
            if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata->lifecycleCallbacks of type array[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1022 10
                $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks';
1023
            }
1024
        }
1025
1026 31
        $lines[] = ' */';
1027
1028 31
        return implode("\n", $lines);
1029
    }
1030
1031
    /**
1032
     * @param ClassMetadataInfo $metadata
1033
     *
1034
     * @return string
1035
     */
1036 31
    protected function generateEntityAnnotation(ClassMetadataInfo $metadata)
1037
    {
1038 31
        $prefix = '@' . $this->annotationsPrefix;
1039
1040 31
        if ($metadata->isEmbeddedClass) {
1041 11
            return $prefix . 'Embeddable';
1042
        }
1043
1044 27
        $customRepository = $metadata->customRepositoryClassName
1045 11
            ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")'
1046 27
            : '';
1047
1048 27
        return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository;
1049
    }
1050
1051
    /**
1052
     * @param ClassMetadataInfo $metadata
1053
     *
1054
     * @return string
1055
     */
1056 31
    protected function generateTableAnnotation(ClassMetadataInfo $metadata)
1057
    {
1058 31
        if ($metadata->isEmbeddedClass) {
1059 11
            return '';
1060
        }
1061
1062 27
        $table = [];
1063
1064 27
        if (isset($metadata->table['schema'])) {
1065
            $table[] = 'schema="' . $metadata->table['schema'] . '"';
1066
        }
1067
1068 27
        if (isset($metadata->table['name'])) {
1069 24
            $table[] = 'name="' . $metadata->table['name'] . '"';
1070
        }
1071
1072 27
        if (isset($metadata->table['options']) && $metadata->table['options']) {
1073 1
            $table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}';
1074
        }
1075
1076 27
        if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
1077 9
            $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
1078 9
            $table[] = 'uniqueConstraints={' . $constraints . '}';
1079
        }
1080
1081 27
        if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
1082 9
            $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']);
1083 9
            $table[] = 'indexes={' . $constraints . '}';
1084
        }
1085
1086 27
        return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
1087
    }
1088
1089
    /**
1090
     * @param string $constraintName
1091
     * @param array  $constraints
1092
     *
1093
     * @return string
1094
     */
1095 9
    protected function generateTableConstraints($constraintName, array $constraints)
1096
    {
1097 9
        $annotations = [];
1098 9
        foreach ($constraints as $name => $constraint) {
1099 9
            $columns = [];
1100 9
            foreach ($constraint['columns'] as $column) {
1101 9
                $columns[] = '"' . $column . '"';
1102
            }
1103 9
            $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})';
1104
        }
1105
1106 9
        return implode(', ', $annotations);
1107
    }
1108
1109
    /**
1110
     * @param ClassMetadataInfo $metadata
1111
     *
1112
     * @return string
1113
     */
1114 31
    protected function generateInheritanceAnnotation(ClassMetadataInfo $metadata)
1115
    {
1116 31
        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1117
            return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")';
1118
        }
1119 31
    }
1120
1121
    /**
1122
     * @param ClassMetadataInfo $metadata
1123
     *
1124
     * @return string
1125
     */
1126 31
    protected function generateDiscriminatorColumnAnnotation(ClassMetadataInfo $metadata)
1127
    {
1128 31
        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1129
            $discrColumn = $metadata->discriminatorColumn;
1130
            $columnDefinition = 'name="' . $discrColumn['name']
1131
                . '", type="' . $discrColumn['type']
1132
                . '", length=' . $discrColumn['length'];
1133
1134
            return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
1135
        }
1136 31
    }
1137
1138
    /**
1139
     * @param ClassMetadataInfo $metadata
1140
     *
1141
     * @return string
1142
     */
1143 31
    protected function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata)
1144
    {
1145 31
        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1146
            $inheritanceClassMap = [];
1147
1148
            foreach ($metadata->discriminatorMap as $type => $class) {
1149
                $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
1150
            }
1151
1152
            return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
1153
        }
1154 31
    }
1155
1156
    /**
1157
     * @param ClassMetadataInfo $metadata
1158
     *
1159
     * @return string
1160
     */
1161 31
    protected function generateEntityStubMethods(ClassMetadataInfo $metadata)
1162
    {
1163 31
        $methods = [];
1164
1165 31
        foreach ($metadata->fieldMappings as $fieldMapping) {
1166 30
            if (isset($fieldMapping['declaredField']) &&
1167 30
                isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
1168
            ) {
1169
                continue;
1170
            }
1171
1172 30
            $nullableField = $this->nullableFieldExpression($fieldMapping);
1173
1174 30
            if (( ! isset($fieldMapping['id']) ||
1175 26
                    ! $fieldMapping['id'] ||
1176 30
                    $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE
1177 30
                ) && (! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable)
1178 30
                && $code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)
1179
            ) {
1180 27
                $methods[] = $code;
1181
            }
1182
1183 30
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)) {
1184 30
                $methods[] = $code;
1185
            }
1186
        }
1187
1188 31
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1189 10
            if (isset($embeddedClass['declaredField'])) {
1190 1
                continue;
1191
            }
1192
1193 10
            if ( ! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) {
1194 9
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldName, $embeddedClass['class'])) {
1195 9
                    $methods[] = $code;
1196
                }
1197
            }
1198
1199 10
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldName, $embeddedClass['class'])) {
1200 10
                $methods[] = $code;
1201
            }
1202
        }
1203
1204 31
        foreach ($metadata->associationMappings as $associationMapping) {
1205 12
            if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
1206 11
                $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null;
1207 11
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1208 9
                    $methods[] = $code;
1209
                }
1210 11
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1211 11
                    $methods[] = $code;
1212
                }
1213 10
            } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1214 10
                if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1215 10
                    $methods[] = $code;
1216
                }
1217 10
                if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1218 10
                    $methods[] = $code;
1219
                }
1220 10
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], Collection::class)) {
1221 12
                    $methods[] = $code;
1222
                }
1223
            }
1224
        }
1225
1226 31
        return implode("\n\n", $methods);
1227
    }
1228
1229
    /**
1230
     * @param array $associationMapping
1231
     *
1232
     * @return bool
1233
     */
1234 11
    protected function isAssociationIsNullable(array $associationMapping)
1235
    {
1236 11
        if (isset($associationMapping['id']) && $associationMapping['id']) {
1237
            return false;
1238
        }
1239
1240 11
        if (isset($associationMapping['joinColumns'])) {
1241 2
            $joinColumns = $associationMapping['joinColumns'];
1242
        } else {
1243
            //@todo there is no way to retrieve targetEntity metadata
1244 9
            $joinColumns = [];
1245
        }
1246
1247 11
        foreach ($joinColumns as $joinColumn) {
1248 2
            if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
1249 2
                return false;
1250
            }
1251
        }
1252
1253 11
        return true;
1254
    }
1255
1256
    /**
1257
     * @param ClassMetadataInfo $metadata
1258
     *
1259
     * @return string
1260
     */
1261 32
    protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
1262
    {
1263 32
        if (empty($metadata->lifecycleCallbacks)) {
1264 29
            return '';
1265
        }
1266
1267 10
        $methods = [];
1268
1269 10
        foreach ($metadata->lifecycleCallbacks as $name => $callbacks) {
1270 10
            foreach ($callbacks as $callback) {
1271 10
                $methods[] = $this->generateLifecycleCallbackMethod($name, $callback, $metadata);
1272
            }
1273
        }
1274
1275 10
        return implode("\n\n", array_filter($methods));
1276
    }
1277
1278
    /**
1279
     * @param ClassMetadataInfo $metadata
1280
     *
1281
     * @return string
1282
     */
1283 32
    protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
1284
    {
1285 32
        $lines = [];
1286
1287 32
        foreach ($metadata->associationMappings as $associationMapping) {
1288 13
            if ($this->hasProperty($associationMapping['fieldName'], $metadata)) {
1289 4
                continue;
1290
            }
1291
1292 11
            $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata);
1293 11
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName']
1294 11
                     . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n";
1295
        }
1296
1297 32
        return implode("\n", $lines);
1298
    }
1299
1300
    /**
1301
     * @param ClassMetadataInfo $metadata
1302
     *
1303
     * @return string
1304
     */
1305 32
    protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata)
1306
    {
1307 32
        $lines = [];
1308
1309 32
        foreach ($metadata->fieldMappings as $fieldMapping) {
1310 31
            if ($this->hasProperty($fieldMapping['fieldName'], $metadata) ||
1311 30
                $metadata->isInheritedField($fieldMapping['fieldName']) ||
1312
                (
1313 29
                    isset($fieldMapping['declaredField']) &&
1314 31
                    isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
1315
                )
1316
            ) {
1317 4
                continue;
1318
            }
1319
1320 29
            $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
1321 29
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName']
1322 29
                     . (isset($fieldMapping['options']['default']) ? ' = ' . var_export($fieldMapping['options']['default'], true) : null) . ";\n";
1323
        }
1324
1325 32
        return implode("\n", $lines);
1326
    }
1327
1328
    /**
1329
     * @param ClassMetadataInfo $metadata
1330
     *
1331
     * @return string
1332
     */
1333 32
    protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata)
1334
    {
1335 32
        $lines = [];
1336
1337 32
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1338 10
            if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) {
1339 2
                continue;
1340
            }
1341
1342 10
            $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass);
1343 10
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n";
1344
        }
1345
1346 32
        return implode("\n", $lines);
1347
    }
1348
1349
    /**
1350
     * @param ClassMetadataInfo $metadata
1351
     * @param string            $type
1352
     * @param string            $fieldName
1353
     * @param string|null       $typeHint
1354
     * @param string|null       $defaultValue
1355
     *
1356
     * @return string
1357
     */
1358 30
    protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
1359
    {
1360 30
        $methodName = $type . Inflector::classify($fieldName);
1361 30
        $variableName = Inflector::camelize($fieldName);
1362 30
        if (in_array($type, ["add", "remove"])) {
1363 10
            $methodName = Inflector::singularize($methodName);
1364 10
            $variableName = Inflector::singularize($variableName);
1365
        }
1366
1367 30
        if ($this->hasMethod($methodName, $metadata)) {
1368 5
            return '';
1369
        }
1370 30
        $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName);
1371
1372 30
        $var = sprintf('%sMethodTemplate', $type);
1373 30
        $template = static::$$var;
1374
1375 30
        $methodTypeHint = null;
1376 30
        $types          = Type::getTypesMap();
1377 30
        $variableType   = $typeHint ? $this->getType($typeHint) : null;
1378
1379 30
        if ($typeHint && ! isset($types[$typeHint])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $typeHint of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1380 14
            $variableType   =  '\\' . ltrim($variableType, '\\');
1381 14
            $methodTypeHint =  '\\' . $typeHint . ' ';
1382
        }
1383
1384
        $replacements = [
1385 30
          '<description>'       => ucfirst($type) . ' ' . $variableName . '.',
1386 30
          '<methodTypeHint>'    => $methodTypeHint,
1387 30
          '<variableType>'      => $variableType . (null !== $defaultValue ? ('|' . $defaultValue) : ''),
1388 30
          '<variableName>'      => $variableName,
1389 30
          '<methodName>'        => $methodName,
1390 30
          '<fieldName>'         => $fieldName,
1391 30
          '<variableDefault>'   => ($defaultValue !== null ) ? (' = ' . $defaultValue) : '',
1392 30
          '<entity>'            => $this->getClassName($metadata)
1393
        ];
1394
1395 30
        $method = str_replace(
1396 30
            array_keys($replacements),
1397 30
            array_values($replacements),
1398 30
            $template
1399
        );
1400
1401 30
        return $this->prefixCodeWithSpaces($method);
1402
    }
1403
1404
    /**
1405
     * @param string            $name
1406
     * @param string            $methodName
1407
     * @param ClassMetadataInfo $metadata
1408
     *
1409
     * @return string
1410
     */
1411 10
    protected function generateLifecycleCallbackMethod($name, $methodName, ClassMetadataInfo $metadata)
1412
    {
1413 10
        if ($this->hasMethod($methodName, $metadata)) {
1414 2
            return '';
1415
        }
1416 10
        $this->staticReflection[$metadata->name]['methods'][] = $methodName;
1417
1418
        $replacements = [
1419 10
            '<name>'        => $this->annotationsPrefix . ucfirst($name),
1420 10
            '<methodName>'  => $methodName,
1421
        ];
1422
1423 10
        $method = str_replace(
1424 10
            array_keys($replacements),
1425 10
            array_values($replacements),
1426 10
            static::$lifecycleCallbackMethodTemplate
1427
        );
1428
1429 10
        return $this->prefixCodeWithSpaces($method);
1430
    }
1431
1432
    /**
1433
     * @param array $joinColumn
1434
     *
1435
     * @return string
1436
     */
1437 11
    protected function generateJoinColumnAnnotation(array $joinColumn)
1438
    {
1439 11
        $joinColumnAnnot = [];
1440
1441 11
        if (isset($joinColumn['name'])) {
1442 11
            $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"';
1443
        }
1444
1445 11
        if (isset($joinColumn['referencedColumnName'])) {
1446 11
            $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"';
1447
        }
1448
1449 11
        if (isset($joinColumn['unique']) && $joinColumn['unique']) {
1450 1
            $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false');
1451
        }
1452
1453 11
        if (isset($joinColumn['nullable'])) {
1454 1
            $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false');
1455
        }
1456
1457 11
        if (isset($joinColumn['onDelete'])) {
1458 1
            $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
1459
        }
1460
1461 11
        if (isset($joinColumn['columnDefinition'])) {
1462 1
            $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
1463
        }
1464
1465 11
        return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
1466
    }
1467
1468
    /**
1469
     * @param array             $associationMapping
1470
     * @param ClassMetadataInfo $metadata
1471
     *
1472
     * @return string
1473
     */
1474 11
    protected function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
1475
    {
1476 11
        $lines = [];
1477 11
        $lines[] = $this->spaces . '/**';
1478
1479 11
        if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1480 11
            $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection';
1481
        } else {
1482 10
            $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\');
1483
        }
1484
1485 11
        if ($this->generateAnnotations) {
1486 11
            $lines[] = $this->spaces . ' *';
1487
1488 11
            if (isset($associationMapping['id']) && $associationMapping['id']) {
1489
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1490
1491
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1492
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1493
                }
1494
            }
1495
1496 11
            $type = null;
1497 11
            switch ($associationMapping['type']) {
1498 11
                case ClassMetadataInfo::ONE_TO_ONE:
1499 10
                    $type = 'OneToOne';
1500 10
                    break;
1501 11
                case ClassMetadataInfo::MANY_TO_ONE:
1502 1
                    $type = 'ManyToOne';
1503 1
                    break;
1504 11
                case ClassMetadataInfo::ONE_TO_MANY:
1505 1
                    $type = 'OneToMany';
1506 1
                    break;
1507 11
                case ClassMetadataInfo::MANY_TO_MANY:
1508 11
                    $type = 'ManyToMany';
1509 11
                    break;
1510
            }
1511 11
            $typeOptions = [];
1512
1513 11
            if (isset($associationMapping['targetEntity'])) {
1514 11
                $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"';
1515
            }
1516
1517 11
            if (isset($associationMapping['inversedBy'])) {
1518 1
                $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"';
1519
            }
1520
1521 11
            if (isset($associationMapping['mappedBy'])) {
1522 10
                $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"';
1523
            }
1524
1525 11
            if ($associationMapping['cascade']) {
1526 1
                $cascades = [];
1527
1528 1
                if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"';
1529 1
                if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"';
1530 1
                if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"';
1531 1
                if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
1532 1
                if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
1533
1534 1
                if (count($cascades) === 5) {
1535 1
                    $cascades = ['"all"'];
1536
                }
1537
1538 1
                $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
1539
            }
1540
1541 11
            if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
1542 1
                $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
1543
            }
1544
1545 11
            if (isset($associationMapping['fetch']) && $associationMapping['fetch'] !== ClassMetadataInfo::FETCH_LAZY) {
1546
                $fetchMap = [
1547 10
                    ClassMetadataInfo::FETCH_EXTRA_LAZY => 'EXTRA_LAZY',
1548 10
                    ClassMetadataInfo::FETCH_EAGER      => 'EAGER',
1549
                ];
1550
1551 10
                $typeOptions[] = 'fetch="' . $fetchMap[$associationMapping['fetch']] . '"';
1552
            }
1553
1554 11
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
1555
1556 11
            if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
1557 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({';
1558
1559 1
                $joinColumnsLines = [];
1560
1561 1
                foreach ($associationMapping['joinColumns'] as $joinColumn) {
1562 1
                    if ($joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn)) {
1563 1
                        $joinColumnsLines[] = $this->spaces . ' *   ' . $joinColumnAnnot;
1564
                    }
1565
                }
1566
1567 1
                $lines[] = implode(",\n", $joinColumnsLines);
1568 1
                $lines[] = $this->spaces . ' * })';
1569
            }
1570
1571 11
            if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) {
1572 11
                $joinTable = [];
1573 11
                $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"';
1574
1575 11
                if (isset($associationMapping['joinTable']['schema'])) {
1576
                    $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
1577
                }
1578
1579 11
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
1580 11
                $lines[] = $this->spaces . ' *   joinColumns={';
1581
1582 11
                $joinColumnsLines = [];
1583
1584 11
                foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
1585 11
                    $joinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1586
                }
1587
1588 11
                $lines[] = implode(",". PHP_EOL, $joinColumnsLines);
1589 11
                $lines[] = $this->spaces . ' *   },';
1590 11
                $lines[] = $this->spaces . ' *   inverseJoinColumns={';
1591
1592 11
                $inverseJoinColumnsLines = [];
1593
1594 11
                foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
1595 11
                    $inverseJoinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1596
                }
1597
1598 11
                $lines[] = implode(",". PHP_EOL, $inverseJoinColumnsLines);
1599 11
                $lines[] = $this->spaces . ' *   }';
1600 11
                $lines[] = $this->spaces . ' * )';
1601
            }
1602
1603 11
            if (isset($associationMapping['orderBy'])) {
1604 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({';
1605
1606 1
                foreach ($associationMapping['orderBy'] as $name => $direction) {
1607 1
                    $lines[] = $this->spaces . ' *     "' . $name . '"="' . $direction . '",';
1608
                }
1609
1610 1
                $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
1611 1
                $lines[] = $this->spaces . ' * })';
1612
            }
1613
        }
1614
1615 11
        $lines[] = $this->spaces . ' */';
1616
1617 11
        return implode("\n", $lines);
1618
    }
1619
1620
    /**
1621
     * @param array             $fieldMapping
1622
     * @param ClassMetadataInfo $metadata
1623
     *
1624
     * @return string
1625
     */
1626 29
    protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
1627
    {
1628 29
        $lines = [];
1629 29
        $lines[] = $this->spaces . '/**';
1630 29
        $lines[] = $this->spaces . ' * @var '
1631 29
            . $this->getType($fieldMapping['type'])
1632 29
            . ($this->nullableFieldExpression($fieldMapping) ? '|null' : '');
1633
1634 29
        if ($this->generateAnnotations) {
1635 29
            $lines[] = $this->spaces . ' *';
1636
1637 29
            $column = [];
1638 29
            if (isset($fieldMapping['columnName'])) {
1639 29
                $column[] = 'name="' . $fieldMapping['columnName'] . '"';
1640
            }
1641
1642 29
            if (isset($fieldMapping['type'])) {
1643 29
                $column[] = 'type="' . $fieldMapping['type'] . '"';
1644
            }
1645
1646 29
            if (isset($fieldMapping['length'])) {
1647 4
                $column[] = 'length=' . $fieldMapping['length'];
1648
            }
1649
1650 29
            if (isset($fieldMapping['precision'])) {
1651 4
                $column[] = 'precision=' .  $fieldMapping['precision'];
1652
            }
1653
1654 29
            if (isset($fieldMapping['scale'])) {
1655 4
                $column[] = 'scale=' . $fieldMapping['scale'];
1656
            }
1657
1658 29
            if (isset($fieldMapping['nullable'])) {
1659 10
                $column[] = 'nullable=' .  var_export($fieldMapping['nullable'], true);
1660
            }
1661
1662 29
            $options = [];
1663
1664 29
            if (isset($fieldMapping['options']['unsigned']) && $fieldMapping['options']['unsigned']) {
1665 1
                $options[] = '"unsigned"=true';
1666
            }
1667
1668 29
            if ($options) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $options of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1669 1
                $column[] = 'options={'.implode(',', $options).'}';
1670
            }
1671
1672 29
            if (isset($fieldMapping['columnDefinition'])) {
1673 1
                $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
1674
            }
1675
1676 29
            if (isset($fieldMapping['unique'])) {
1677 4
                $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
1678
            }
1679
1680 29
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
1681
1682 29
            if (isset($fieldMapping['id']) && $fieldMapping['id']) {
1683 25
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1684
1685 25
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1686 25
                    $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1687
                }
1688
1689 25
                if ($metadata->sequenceGeneratorDefinition) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata->sequenceGeneratorDefinition of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1690 1
                    $sequenceGenerator = [];
1691
1692 1
                    if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) {
1693 1
                        $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"';
1694
                    }
1695
1696 1
                    if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
1697 1
                        $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize'];
1698
                    }
1699
1700 1
                    if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
1701 1
                        $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue'];
1702
                    }
1703
1704 1
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
1705
                }
1706
            }
1707
1708 29
            if (isset($fieldMapping['version']) && $fieldMapping['version']) {
1709
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version';
1710
            }
1711
        }
1712
1713 29
        $lines[] = $this->spaces . ' */';
1714
1715 29
        return implode("\n", $lines);
1716
    }
1717
1718
    /**
1719
     * @param array $embeddedClass
1720
     *
1721
     * @return string
1722
     */
1723 10
    protected function generateEmbeddedPropertyDocBlock(array $embeddedClass)
1724
    {
1725 10
        $lines = [];
1726 10
        $lines[] = $this->spaces . '/**';
1727 10
        $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\');
1728
1729 10
        if ($this->generateAnnotations) {
1730 10
            $lines[] = $this->spaces . ' *';
1731
1732 10
            $embedded = ['class="' . $embeddedClass['class'] . '"'];
1733
1734 10
            if (isset($embeddedClass['columnPrefix'])) {
1735 8
                if (is_string($embeddedClass['columnPrefix'])) {
1736 1
                    $embedded[] = 'columnPrefix="' . $embeddedClass['columnPrefix'] . '"';
1737
                } else {
1738 7
                    $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true);
1739
                }
1740
            }
1741
1742 10
            $lines[] = $this->spaces . ' * @' .
1743 10
                $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')';
1744
        }
1745
1746 10
        $lines[] = $this->spaces . ' */';
1747
1748 10
        return implode("\n", $lines);
1749
    }
1750
1751
    /**
1752
     * @param string $code
1753
     * @param int    $num
1754
     *
1755
     * @return string
1756
     */
1757 31
    protected function prefixCodeWithSpaces($code, $num = 1)
1758
    {
1759 31
        $lines = explode("\n", $code);
1760
1761 31
        foreach ($lines as $key => $value) {
1762 31
            if ( ! empty($value)) {
1763 31
                $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
1764
            }
1765
        }
1766
1767 31
        return implode("\n", $lines);
1768
    }
1769
1770
    /**
1771
     * @param integer $type The inheritance type used by the class and its subclasses.
1772
     *
1773
     * @return string The literal string for the inheritance type.
1774
     *
1775
     * @throws \InvalidArgumentException When the inheritance type does not exist.
1776
     */
1777 1
    protected function getInheritanceTypeString($type)
1778
    {
1779 1
        if ( ! isset(static::$inheritanceTypeMap[$type])) {
1780 1
            throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type));
1781
        }
1782
1783 1
        return static::$inheritanceTypeMap[$type];
1784
    }
1785
1786
    /**
1787
     * @param integer $type The policy used for change-tracking for the mapped class.
1788
     *
1789
     * @return string The literal string for the change-tracking type.
1790
     *
1791
     * @throws \InvalidArgumentException When the change-tracking type does not exist.
1792
     */
1793 1
    protected function getChangeTrackingPolicyString($type)
1794
    {
1795 1
        if ( ! isset(static::$changeTrackingPolicyMap[$type])) {
1796 1
            throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type));
1797
        }
1798
1799 1
        return static::$changeTrackingPolicyMap[$type];
1800
    }
1801
1802
    /**
1803
     * @param integer $type The generator to use for the mapped class.
1804
     *
1805
     * @return string The literal string for the generator type.
1806
     *
1807
     * @throws \InvalidArgumentException    When the generator type does not exist.
1808
     */
1809 26
    protected function getIdGeneratorTypeString($type)
1810
    {
1811 26
        if ( ! isset(static::$generatorStrategyMap[$type])) {
1812 1
            throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type));
1813
        }
1814
1815 26
        return static::$generatorStrategyMap[$type];
1816
    }
1817
1818
    /**
1819
     * @param array $fieldMapping
1820
     *
1821
     * @return string|null
1822
     */
1823 31
    private function nullableFieldExpression(array $fieldMapping)
1824
    {
1825 31
        if (isset($fieldMapping['nullable']) && true === $fieldMapping['nullable']) {
1826 7
            return 'null';
1827
        }
1828
1829 31
        return null;
1830
    }
1831
1832
    /**
1833
     * Exports (nested) option elements.
1834
     *
1835
     * @param array $options
1836
     *
1837
     * @return string
1838
     */
1839 1
    private function exportTableOptions(array $options)
1840
    {
1841 1
        $optionsStr = [];
1842
1843 1
        foreach ($options as $name => $option) {
1844 1
            if (is_array($option)) {
1845 1
                $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}';
1846
            } else {
1847 1
                $optionsStr[] = '"' . $name . '"="' . (string) $option . '"';
1848
            }
1849
        }
1850
1851 1
        return implode(',', $optionsStr);
1852
    }
1853
}
1854