Completed
Pull Request — master (#5987)
by Javier
09:33
created

EntityGenerator::writeEntityClass()   D

Complexity

Conditions 9
Paths 56

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9.0117

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 18
cts 19
cp 0.9474
rs 4.909
c 0
b 0
f 0
cc 9
eloc 19
nc 56
nop 2
crap 9.0117
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\ORM\Mapping\ClassMetadataInfo;
23
use Doctrine\Common\Util\Inflector;
24
use Doctrine\DBAL\Types\Type;
25
26
/**
27
 * Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances.
28
 *
29
 *     [php]
30
 *     $classes = $em->getClassMetadataFactory()->getAllMetadata();
31
 *
32
 *     $generator = new \Doctrine\ORM\Tools\EntityGenerator();
33
 *     $generator->setGenerateAnnotations(true);
34
 *     $generator->setGenerateStubMethods(true);
35
 *     $generator->setRegenerateEntityIfExists(false);
36
 *     $generator->setUpdateEntityIfExists(true);
37
 *     $generator->generate($classes, '/path/to/generate/entities');
38
 *
39
 *
40
 * @link    www.doctrine-project.org
41
 * @since   2.0
42
 * @author  Benjamin Eberlei <[email protected]>
43
 * @author  Guilherme Blanco <[email protected]>
44
 * @author  Jonathan Wage <[email protected]>
45
 * @author  Roman Borschel <[email protected]>
46
 */
47
class EntityGenerator
48
{
49
    /**
50
     * Specifies class fields should be protected.
51
     */
52
    const FIELD_VISIBLE_PROTECTED = 'protected';
53
54
    /**
55
     * Specifies class fields should be private.
56
     */
57
    const FIELD_VISIBLE_PRIVATE = 'private';
58
59
    /**
60
     * @var bool
61
     */
62
    protected $backupExisting = true;
63
64
    /**
65
     * The extension to use for written php files.
66
     *
67
     * @var string
68
     */
69
    protected $extension = '.php';
70
71
    /**
72
     * Whether or not the current ClassMetadataInfo instance is new or old.
73
     *
74
     * @var boolean
75
     */
76
    protected $isNew = true;
77
78
    /**
79
     * @var array
80
     */
81
    protected $staticReflection = array();
82
83
    /**
84
     * Number of spaces to use for indention in generated code.
85
     */
86
    protected $numSpaces = 4;
87
88
    /**
89
     * The actual spaces to use for indention.
90
     *
91
     * @var string
92
     */
93
    protected $spaces = '    ';
94
95
    /**
96
     * The class all generated entities should extend.
97
     *
98
     * @var string
99
     */
100
    protected $classToExtend;
101
102
    /**
103
     * Whether or not to generation annotations.
104
     *
105
     * @var boolean
106
     */
107
    protected $generateAnnotations = false;
108
109
    /**
110
     * @var string
111
     */
112
    protected $annotationsPrefix = '';
113
114
    /**
115
     * Whether or not to generate sub methods.
116
     *
117
     * @var boolean
118
     */
119
    protected $generateEntityStubMethods = false;
120
121
    /**
122
     * Whether or not to update the entity class if it exists already.
123
     *
124
     * @var boolean
125
     */
126
    protected $updateEntityIfExists = false;
127
128
    /**
129
     * Whether or not to re-generate entity class if it exists already.
130
     *
131
     * @var boolean
132
     */
133
    protected $regenerateEntityIfExists = false;
134
135
    /**
136
     * Visibility of the field
137
     *
138
     * @var string
139
     */
140
    protected $fieldVisibility = 'private';
141
142
    /**
143
     * Whether or not to make generated embeddables immutable.
144
     *
145
     * @var boolean.
146
     */
147
    protected $embeddablesImmutable = false;
148
149
    /**
150
     * Hash-map for handle types.
151
     *
152
     * @var array
153
     */
154
    protected $typeAlias = array(
155
        Type::DATETIMETZ    => '\DateTime',
156
        Type::DATETIME      => '\DateTime',
157
        Type::DATE          => '\DateTime',
158
        Type::TIME          => '\DateTime',
159
        Type::OBJECT        => '\stdClass',
160
        Type::INTEGER       => 'int',
161
        Type::BIGINT        => 'int',
162
        Type::SMALLINT      => 'int',
163
        Type::TEXT          => 'string',
164
        Type::BLOB          => 'string',
165
        Type::DECIMAL       => 'string',
166
        Type::JSON_ARRAY    => 'array',
167
        Type::SIMPLE_ARRAY  => 'array',
168
        Type::BOOLEAN       => 'bool',
169
    );
170
171
    /**
172
     * Hash-map to handle generator types string.
173
     *
174
     * @var array
175
     */
176
    protected static $generatorStrategyMap = array(
177
        ClassMetadataInfo::GENERATOR_TYPE_AUTO      => 'AUTO',
178
        ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE  => 'SEQUENCE',
179
        ClassMetadataInfo::GENERATOR_TYPE_TABLE     => 'TABLE',
180
        ClassMetadataInfo::GENERATOR_TYPE_IDENTITY  => 'IDENTITY',
181
        ClassMetadataInfo::GENERATOR_TYPE_NONE      => 'NONE',
182
        ClassMetadataInfo::GENERATOR_TYPE_UUID      => 'UUID',
183
        ClassMetadataInfo::GENERATOR_TYPE_CUSTOM    => 'CUSTOM'
184
    );
185
186
    /**
187
     * Hash-map to handle the change tracking policy string.
188
     *
189
     * @var array
190
     */
191
    protected static $changeTrackingPolicyMap = array(
192
        ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT  => 'DEFERRED_IMPLICIT',
193
        ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT  => 'DEFERRED_EXPLICIT',
194
        ClassMetadataInfo::CHANGETRACKING_NOTIFY             => 'NOTIFY',
195
    );
196
197
    /**
198
     * Hash-map to handle the inheritance type string.
199
     *
200
     * @var array
201
     */
202
    protected static $inheritanceTypeMap = array(
203
        ClassMetadataInfo::INHERITANCE_TYPE_NONE            => 'NONE',
204
        ClassMetadataInfo::INHERITANCE_TYPE_JOINED          => 'JOINED',
205
        ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE    => 'SINGLE_TABLE',
206
        ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS => 'TABLE_PER_CLASS',
207
    );
208
209
    /**
210
     * @var string
211
     */
212
    protected static $classTemplate =
213
'<?php
214
215
<namespace>
216
<useStatement>
217
<entityAnnotation>
218
<entityClassName>
219
{
220
<entityBody>
221
}
222
';
223
224
    /**
225
     * @var string
226
     */
227
    protected static $getMethodTemplate =
228
'/**
229
 * <description>
230
 *
231
 * @return <variableType>
232
 */
233
public function <methodName>()
234
{
235
<spaces>return $this-><fieldName>;
236
}';
237
238
    /**
239
     * @var string
240
     */
241
    protected static $setMethodTemplate =
242
'/**
243
 * <description>
244
 *
245
 * @param <variableType> $<variableName>
246
 *
247
 * @return <entity>
248
 */
249
public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
250
{
251
<spaces>$this-><fieldName> = $<variableName>;
252
253
<spaces>return $this;
254
}';
255
256
    /**
257
     * @var string
258
     */
259
    protected static $addMethodTemplate =
260
'/**
261
 * <description>
262
 *
263
 * @param <variableType> $<variableName>
264
 *
265
 * @return <entity>
266
 */
267
public function <methodName>(<methodTypeHint>$<variableName>)
268
{
269
<spaces>$this-><fieldName>[] = $<variableName>;
270
271
<spaces>return $this;
272
}';
273
274
    /**
275
     * @var string
276
     */
277
    protected static $removeMethodTemplate =
278
'/**
279
 * <description>
280
 *
281
 * @param <variableType> $<variableName>
282
 *
283
 * @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
284
 */
285
public function <methodName>(<methodTypeHint>$<variableName>)
286
{
287
<spaces>return $this-><fieldName>->removeElement($<variableName>);
288
}';
289
290
    /**
291
     * @var string
292
     */
293
    protected static $lifecycleCallbackMethodTemplate =
294
'/**
295
 * @<name>
296
 */
297
public function <methodName>()
298
{
299
<spaces>// Add your code here
300
}';
301
302
    /**
303
     * @var string
304
     */
305
    protected static $constructorMethodTemplate =
306
'/**
307
 * Constructor
308
 */
309
public function __construct()
310
{
311
<spaces><collections>
312
}
313
';
314
315
    /**
316
     * @var string
317
     */
318
    protected static $embeddableConstructorMethodTemplate =
319
'/**
320
 * Constructor
321
 *
322
 * <paramTags>
323
 */
324
public function __construct(<params>)
325
{
326
<spaces><fields>
327
}
328
';
329
330
    /**
331
     * Constructor.
332
     */
333 38
    public function __construct()
334
    {
335 38
        if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
336 38
            $this->annotationsPrefix = 'ORM\\';
337
        }
338 38
    }
339
340
    /**
341
     * Generates and writes entity classes for the given array of ClassMetadataInfo instances.
342
     *
343
     * @param array  $metadatas
344
     * @param string $outputDirectory
345
     *
346
     * @return void
347
     */
348
    public function generate(array $metadatas, $outputDirectory)
349
    {
350
        foreach ($metadatas as $metadata) {
351
            $this->writeEntityClass($metadata, $outputDirectory);
352
        }
353
    }
354
355
    /**
356
     * Generates and writes entity class to disk for the given ClassMetadataInfo instance.
357
     *
358
     * @param ClassMetadataInfo $metadata
359
     * @param string            $outputDirectory
360
     *
361
     * @return void
362
     *
363
     * @throws \RuntimeException
364
     */
365 29
    public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
366
    {
367 29
        $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension;
368 29
        $dir = dirname($path);
369
370 29
        if ( ! is_dir($dir)) {
371 2
            mkdir($dir, 0775, true);
372
        }
373
374 29
        $this->isNew = !file_exists($path) || $this->regenerateEntityIfExists;
375
376 29
        if ( ! $this->isNew) {
377 3
            $this->parseTokensInEntityFile(file_get_contents($path));
378
        } else {
379 28
            $this->staticReflection[$metadata->name] = array('properties' => array(), 'methods' => array());
380
        }
381
382 29
        if ($this->backupExisting && file_exists($path)) {
383 3
            $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~";
384 3
            if (!copy($path, $backupPath)) {
385
                throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed.");
386
            }
387
        }
388
389
        // If entity doesn't exist or we're re-generating the entities entirely
390 29
        if ($this->isNew) {
391 28
            file_put_contents($path, $this->generateEntityClass($metadata));
392
        // If entity exists and we're allowed to update the entity class
393 3
        } elseif ($this->updateEntityIfExists) {
394 3
            file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path));
395
        }
396 29
        chmod($path, 0664);
397 29
    }
398
399
    /**
400
     * Generates a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance.
401
     *
402
     * @param ClassMetadataInfo $metadata
403
     *
404
     * @return string
405
     */
406 29
    public function generateEntityClass(ClassMetadataInfo $metadata)
407
    {
408
        $placeHolders = array(
409 29
            '<namespace>',
410
            '<useStatement>',
411
            '<entityAnnotation>',
412
            '<entityClassName>',
413
            '<entityBody>'
414
        );
415
416
        $replacements = array(
417 29
            $this->generateEntityNamespace($metadata),
418 29
            $this->generateEntityUse(),
419 29
            $this->generateEntityDocBlock($metadata),
420 29
            $this->generateEntityClassName($metadata),
421 29
            $this->generateEntityBody($metadata)
422
        );
423
424 29
        $code = str_replace($placeHolders, $replacements, static::$classTemplate) . "\n";
425
426 29
        return str_replace('<spaces>', $this->spaces, $code);
427
    }
428
429
    /**
430
     * Generates the updated code for the given ClassMetadataInfo and entity at path.
431
     *
432
     * @param ClassMetadataInfo $metadata
433
     * @param string            $path
434
     *
435
     * @return string
436
     */
437 3
    public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
438
    {
439 3
        $currentCode = file_get_contents($path);
440
441 3
        $body = $this->generateEntityBody($metadata);
442 3
        $body = str_replace('<spaces>', $this->spaces, $body);
443 3
        $last = strrpos($currentCode, '}');
444
445 3
        return substr($currentCode, 0, $last) . $body . ($body ? "\n" : '') . "}\n";
446
    }
447
448
    /**
449
     * Sets the number of spaces the exported class should have.
450
     *
451
     * @param integer $numSpaces
452
     *
453
     * @return void
454
     */
455
    public function setNumSpaces($numSpaces)
456
    {
457
        $this->spaces = str_repeat(' ', $numSpaces);
458
        $this->numSpaces = $numSpaces;
459
    }
460
461
    /**
462
     * Sets the extension to use when writing php files to disk.
463
     *
464
     * @param string $extension
465
     *
466
     * @return void
467
     */
468
    public function setExtension($extension)
469
    {
470
        $this->extension = $extension;
471
    }
472
473
    /**
474
     * Sets the name of the class the generated classes should extend from.
475
     *
476
     * @param string $classToExtend
477
     *
478
     * @return void
479
     */
480 1
    public function setClassToExtend($classToExtend)
481
    {
482 1
        $this->classToExtend = $classToExtend;
483 1
    }
484
485
    /**
486
     * Sets whether or not to generate annotations for the entity.
487
     *
488
     * @param bool $bool
489
     *
490
     * @return void
491
     */
492 38
    public function setGenerateAnnotations($bool)
493
    {
494 38
        $this->generateAnnotations = $bool;
495 38
    }
496
497
    /**
498
     * Sets the class fields visibility for the entity (can either be private or protected).
499
     *
500
     * @param bool $visibility
501
     *
502
     * @return void
503
     *
504
     * @throws \InvalidArgumentException
505
     */
506 37
    public function setFieldVisibility($visibility)
507
    {
508 37
        if ($visibility !== static::FIELD_VISIBLE_PRIVATE && $visibility !== static::FIELD_VISIBLE_PROTECTED) {
509
            throw new \InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility);
510
        }
511
512 37
        $this->fieldVisibility = $visibility;
513 37
    }
514
515
    /**
516
     * Sets whether or not to generate immutable embeddables.
517
     *
518
     * @param boolean $embeddablesImmutable
519
     */
520 1
    public function setEmbeddablesImmutable($embeddablesImmutable)
521
    {
522 1
        $this->embeddablesImmutable = (boolean) $embeddablesImmutable;
523 1
    }
524
525
    /**
526
     * Sets an annotation prefix.
527
     *
528
     * @param string $prefix
529
     *
530
     * @return void
531
     */
532 38
    public function setAnnotationPrefix($prefix)
533
    {
534 38
        $this->annotationsPrefix = $prefix;
535 38
    }
536
537
    /**
538
     * Sets whether or not to try and update the entity if it already exists.
539
     *
540
     * @param bool $bool
541
     *
542
     * @return void
543
     */
544 38
    public function setUpdateEntityIfExists($bool)
545
    {
546 38
        $this->updateEntityIfExists = $bool;
547 38
    }
548
549
    /**
550
     * Sets whether or not to regenerate the entity if it exists.
551
     *
552
     * @param bool $bool
553
     *
554
     * @return void
555
     */
556 38
    public function setRegenerateEntityIfExists($bool)
557
    {
558 38
        $this->regenerateEntityIfExists = $bool;
559 38
    }
560
561
    /**
562
     * Sets whether or not to generate stub methods for the entity.
563
     *
564
     * @param bool $bool
565
     *
566
     * @return void
567
     */
568 38
    public function setGenerateStubMethods($bool)
569
    {
570 38
        $this->generateEntityStubMethods = $bool;
571 38
    }
572
573
    /**
574
     * Should an existing entity be backed up if it already exists?
575
     *
576
     * @param bool $bool
577
     *
578
     * @return void
579
     */
580 1
    public function setBackupExisting($bool)
581
    {
582 1
        $this->backupExisting = $bool;
583 1
    }
584
585
    /**
586
     * @param string $type
587
     *
588
     * @return string
589
     */
590 29
    protected function getType($type)
591
    {
592 29
        if (isset($this->typeAlias[$type])) {
593 28
            return $this->typeAlias[$type];
594
        }
595
596 18
        return $type;
597
    }
598
599
    /**
600
     * @param ClassMetadataInfo $metadata
601
     *
602
     * @return string
603
     */
604 29
    protected function generateEntityNamespace(ClassMetadataInfo $metadata)
605
    {
606 29
        if ($this->hasNamespace($metadata)) {
607 29
            return 'namespace ' . $this->getNamespace($metadata) .';';
608
        }
609 2
    }
610
611 29
    protected function generateEntityUse()
612
    {
613 29
        if ($this->generateAnnotations) {
614 29
            return "\n".'use Doctrine\ORM\Mapping as ORM;'."\n";
615
        } else {
616
            return "";
617
        }
618
    }
619
620
    /**
621
     * @param ClassMetadataInfo $metadata
622
     *
623
     * @return string
624
     */
625 29
    protected function generateEntityClassName(ClassMetadataInfo $metadata)
626
    {
627 29
        return 'class ' . $this->getClassName($metadata) .
628 29
            ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
629
    }
630
631
    /**
632
     * @param ClassMetadataInfo $metadata
633
     *
634
     * @return string
635
     */
636 30
    protected function generateEntityBody(ClassMetadataInfo $metadata)
637
    {
638 30
        $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata);
639 30
        $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata);
640 30
        $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata);
641 30
        $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null;
642 30
        $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata);
643
644 30
        $code = array();
645
646 30
        if ($fieldMappingProperties) {
647 27
            $code[] = $fieldMappingProperties;
648
        }
649
650 30
        if ($embeddedProperties) {
651 8
            $code[] = $embeddedProperties;
652
        }
653
654 30
        if ($associationMappingProperties) {
655 11
            $code[] = $associationMappingProperties;
656
        }
657
658 30
        $code[] = $this->generateEntityConstructor($metadata);
659
660 30
        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...
661 28
            $code[] = $stubMethods;
662
        }
663
664 30
        if ($lifecycleCallbackMethods) {
665 10
            $code[] = $lifecycleCallbackMethods;
666
        }
667
668 30
        return implode("\n", $code);
669
    }
670
671
    /**
672
     * @param ClassMetadataInfo $metadata
673
     *
674
     * @return string
675
     */
676 30
    protected function generateEntityConstructor(ClassMetadataInfo $metadata)
677
    {
678 30
        if ($this->hasMethod('__construct', $metadata)) {
679 2
            return '';
680
        }
681
682 30
        if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) {
683 1
            return $this->generateEmbeddableConstructor($metadata);
684
        }
685
686 29
        $collections = array();
687
688 29
        foreach ($metadata->associationMappings as $mapping) {
689 13
            if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
690 13
                $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
691
            }
692
        }
693
694 29
        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...
695 11
            return $this->prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->spaces, $collections), static::$constructorMethodTemplate));
696
        }
697
698 25
        return '';
699
    }
700
701
    /**
702
     * @param ClassMetadataInfo $metadata
703
     *
704
     * @return string
705
     */
706 1
    private function generateEmbeddableConstructor(ClassMetadataInfo $metadata)
707
    {
708 1
        $paramTypes = array();
709 1
        $paramVariables = array();
710 1
        $params = array();
711 1
        $fields = array();
712
713
        // Resort fields to put optional fields at the end of the method signature.
714 1
        $requiredFields = array();
715 1
        $optionalFields = array();
716
717 1
        foreach ($metadata->fieldMappings as $fieldMapping) {
718 1
            if (empty($fieldMapping['nullable'])) {
719 1
                $requiredFields[] = $fieldMapping;
720
721 1
                continue;
722
            }
723
724 1
            $optionalFields[] = $fieldMapping;
725
        }
726
727 1
        $fieldMappings = array_merge($requiredFields, $optionalFields);
728
729 1
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
730 1
            $paramType = '\\' . ltrim($embeddedClass['class'], '\\');
731 1
            $paramVariable = '$' . $fieldName;
732
733 1
            $paramTypes[] = $paramType;
734 1
            $paramVariables[] = $paramVariable;
735 1
            $params[] = $paramType . ' ' . $paramVariable;
736 1
            $fields[] = '$this->' . $fieldName . ' = ' . $paramVariable . ';';
737
        }
738
739 1
        foreach ($fieldMappings as $fieldMapping) {
740 1
            if (isset($fieldMapping['declaredField']) &&
741 1
                isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
742
            ) {
743
                continue;
744
            }
745
746 1
            $paramTypes[] = $this->getType($fieldMapping['type']) . (!empty($fieldMapping['nullable']) ? '|null' : '');
747 1
            $param = '$' . $fieldMapping['fieldName'];
748 1
            $paramVariables[] = $param;
749
750 1
            if ($fieldMapping['type'] === 'datetime') {
751 1
                $param = $this->getType($fieldMapping['type']) . ' ' . $param;
752
            }
753
754 1
            if (!empty($fieldMapping['nullable'])) {
755 1
                $param .= ' = null';
756
            }
757
758 1
            $params[] = $param;
759
760 1
            $fields[] = '$this->' . $fieldMapping['fieldName'] . ' = $' . $fieldMapping['fieldName'] . ';';
761
        }
762
763 1
        $maxParamTypeLength = max(array_map('strlen', $paramTypes));
764 1
        $paramTags = array_map(
765 1
            function ($type, $variable) use ($maxParamTypeLength) {
766 1
                return '@param ' . $type . str_repeat(' ', $maxParamTypeLength - strlen($type) + 1) . $variable;
767 1
            },
768
            $paramTypes,
769
            $paramVariables
770
        );
771
772
        // Generate multi line constructor if the signature exceeds 120 characters.
773 1
        if (array_sum(array_map('strlen', $params)) + count($params) * 2 + 29 > 120) {
774 1
            $delimiter = "\n" . $this->spaces;
775 1
            $params = $delimiter . implode(',' . $delimiter, $params) . "\n";
776
        } else {
777 1
            $params = implode(', ', $params);
778
        }
779
780
        $replacements = array(
781 1
            '<paramTags>' => implode("\n * ", $paramTags),
782 1
            '<params>'    => $params,
783 1
            '<fields>'    => implode("\n" . $this->spaces, $fields),
784
        );
785
786 1
        $constructor = str_replace(
787
            array_keys($replacements),
788
            array_values($replacements),
789 1
            static::$embeddableConstructorMethodTemplate
790
        );
791
792 1
        return $this->prefixCodeWithSpaces($constructor);
793
    }
794
795
    /**
796
     * @todo this won't work if there is a namespace in brackets and a class outside of it.
797
     *
798
     * @param string $src
799
     *
800
     * @return void
801
     */
802 8
    protected function parseTokensInEntityFile($src)
803
    {
804 8
        $tokens = token_get_all($src);
805 8
        $tokensCount = count($tokens);
806 8
        $lastSeenNamespace = '';
807 8
        $lastSeenClass = false;
808
809 8
        $inNamespace = false;
810 8
        $inClass = false;
811
812 8
        for ($i = 0; $i < $tokensCount; $i++) {
813 8
            $token = $tokens[$i];
814 8
            if (in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT), true)) {
815 8
                continue;
816
            }
817
818 8
            if ($inNamespace) {
819 8
                if (in_array($token[0], array(T_NS_SEPARATOR, T_STRING), true)) {
820 8
                    $lastSeenNamespace .= $token[1];
821 8
                } elseif (is_string($token) && in_array($token, array(';', '{'), true)) {
822 8
                    $inNamespace = false;
823
                }
824
            }
825
826 8
            if ($inClass) {
827 8
                $inClass = false;
828 8
                $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
829 8
                $this->staticReflection[$lastSeenClass]['properties'] = array();
830 8
                $this->staticReflection[$lastSeenClass]['methods'] = array();
831
            }
832
833 8
            if (T_NAMESPACE === $token[0]) {
834 8
                $lastSeenNamespace = '';
835 8
                $inNamespace = true;
836 8
            } elseif (T_CLASS === $token[0] && T_DOUBLE_COLON !== $tokens[$i-1][0]) {
837 8
                $inClass = true;
838 8
            } elseif (T_FUNCTION === $token[0]) {
839 3
                if (T_STRING === $tokens[$i+2][0]) {
840 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+2][1]);
841
                } elseif ($tokens[$i+2] == '&' && T_STRING === $tokens[$i+3][0]) {
842 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+3][1]);
843
                }
844 8
            } elseif (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED), true) && T_FUNCTION !== $tokens[$i+2][0]) {
845 4
                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
846
            }
847
        }
848 8
    }
849
850
    /**
851
     * @param string            $property
852
     * @param ClassMetadataInfo $metadata
853
     *
854
     * @return bool
855
     */
856 29
    protected function hasProperty($property, ClassMetadataInfo $metadata)
857
    {
858 29
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
859
            // don't generate property if its already on the base class.
860 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
861 2
            if ($reflClass->hasProperty($property)) {
862 1
                return true;
863
            }
864
        }
865
866
        // check traits for existing property
867 28
        foreach ($this->getTraits($metadata) as $trait) {
868 2
            if ($trait->hasProperty($property)) {
869 2
                return true;
870
            }
871
        }
872
873
        return (
874 28
            isset($this->staticReflection[$metadata->name]) &&
875 28
            in_array($property, $this->staticReflection[$metadata->name]['properties'], true)
876
        );
877
    }
878
879
    /**
880
     * @param string            $method
881
     * @param ClassMetadataInfo $metadata
882
     *
883
     * @return bool
884
     */
885 30
    protected function hasMethod($method, ClassMetadataInfo $metadata)
886
    {
887 30
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
888
            // don't generate method if its already on the base class.
889 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
890
891 2
            if ($reflClass->hasMethod($method)) {
892 1
                return true;
893
            }
894
        }
895
896
        // check traits for existing method
897 30
        foreach ($this->getTraits($metadata) as $trait) {
898 2
            if ($trait->hasMethod($method)) {
899 2
                return true;
900
            }
901
        }
902
903
        return (
904 30
            isset($this->staticReflection[$metadata->name]) &&
905 30
            in_array(strtolower($method), $this->staticReflection[$metadata->name]['methods'], true)
906
        );
907
    }
908
909
    /**
910
     * @param ClassMetadataInfo $metadata
911
     *
912
     * @return array
913
     */
914 30
    protected function getTraits(ClassMetadataInfo $metadata)
915
    {
916 30
        if (! ($metadata->reflClass !== null || class_exists($metadata->name))) {
917 24
            return [];
918
        }
919
920 7
        $reflClass = $metadata->reflClass === null
921 1
            ? new \ReflectionClass($metadata->name)
922 7
            : $metadata->reflClass;
923
924 7
        $traits = array();
925
926 7
        while ($reflClass !== false) {
927 7
            $traits = array_merge($traits, $reflClass->getTraits());
928
929 7
            $reflClass = $reflClass->getParentClass();
930
        }
931
932 7
        return $traits;
933
    }
934
935
    /**
936
     * @param ClassMetadataInfo $metadata
937
     *
938
     * @return bool
939
     */
940 29
    protected function hasNamespace(ClassMetadataInfo $metadata)
941
    {
942 29
        return (bool) strpos($metadata->name, '\\');
943
    }
944
945
    /**
946
     * @return bool
947
     */
948 30
    protected function extendsClass()
949
    {
950 30
        return (bool) $this->classToExtend;
951
    }
952
953
    /**
954
     * @return string
955
     */
956 2
    protected function getClassToExtend()
957
    {
958 2
        return $this->classToExtend;
959
    }
960
961
    /**
962
     * @return string
963
     */
964 1
    protected function getClassToExtendName()
965
    {
966 1
        $refl = new \ReflectionClass($this->getClassToExtend());
967
968 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...
969
    }
970
971
    /**
972
     * @param ClassMetadataInfo $metadata
973
     *
974
     * @return string
975
     */
976 30
    protected function getClassName(ClassMetadataInfo $metadata)
977
    {
978 30
        return ($pos = strrpos($metadata->name, '\\'))
979 30
            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
980
    }
981
982
    /**
983
     * @param ClassMetadataInfo $metadata
984
     *
985
     * @return string
986
     */
987 29
    protected function getNamespace(ClassMetadataInfo $metadata)
988
    {
989 29
        return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
990
    }
991
992
    /**
993
     * @param ClassMetadataInfo $metadata
994
     *
995
     * @return string
996
     */
997 29
    protected function generateEntityDocBlock(ClassMetadataInfo $metadata)
998
    {
999 29
        $lines = array();
1000 29
        $lines[] = '/**';
1001 29
        $lines[] = ' * ' . $this->getClassName($metadata);
1002
1003 29
        if ($this->generateAnnotations) {
1004 29
            $lines[] = ' *';
1005
1006
            $methods = array(
1007 29
                'generateTableAnnotation',
1008
                'generateInheritanceAnnotation',
1009
                'generateDiscriminatorColumnAnnotation',
1010
                'generateDiscriminatorMapAnnotation',
1011
                'generateEntityAnnotation',
1012
            );
1013
1014 29
            foreach ($methods as $method) {
1015 29
                if ($code = $this->$method($metadata)) {
1016 29
                    $lines[] = ' * ' . $code;
1017
                }
1018
            }
1019
1020 29
            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...
1021 10
                $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks';
1022
            }
1023
        }
1024
1025 29
        $lines[] = ' */';
1026
1027 29
        return implode("\n", $lines);
1028
    }
1029
1030
    /**
1031
     * @param ClassMetadataInfo $metadata
1032
     *
1033
     * @return string
1034
     */
1035 29
    protected function generateEntityAnnotation(ClassMetadataInfo $metadata)
1036
    {
1037 29
        $prefix = '@' . $this->annotationsPrefix;
1038
1039 29
        if ($metadata->isEmbeddedClass) {
1040 9
            return $prefix . 'Embeddable';
1041
        }
1042
1043 27
        $customRepository = $metadata->customRepositoryClassName
1044 11
            ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")'
1045 27
            : '';
1046
1047 27
        return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository;
1048
    }
1049
1050
    /**
1051
     * @param ClassMetadataInfo $metadata
1052
     *
1053
     * @return string
1054
     */
1055 29
    protected function generateTableAnnotation(ClassMetadataInfo $metadata)
1056
    {
1057 29
        if ($metadata->isEmbeddedClass) {
1058 9
            return '';
1059
        }
1060
1061 27
        $table = array();
1062
1063 27
        if (isset($metadata->table['schema'])) {
1064
            $table[] = 'schema="' . $metadata->table['schema'] . '"';
1065
        }
1066
1067 27
        if (isset($metadata->table['name'])) {
1068 24
            $table[] = 'name="' . $metadata->table['name'] . '"';
1069
        }
1070
1071 27
        if (isset($metadata->table['options']) && $metadata->table['options']) {
1072 1
            $table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}';
1073
        }
1074
1075 27
        if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
1076 9
            $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
1077 9
            $table[] = 'uniqueConstraints={' . $constraints . '}';
1078
        }
1079
1080 27
        if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
1081 9
            $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']);
1082 9
            $table[] = 'indexes={' . $constraints . '}';
1083
        }
1084
1085 27
        return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
1086
    }
1087
1088
    /**
1089
     * @param string $constraintName
1090
     * @param array  $constraints
1091
     *
1092
     * @return string
1093
     */
1094 9
    protected function generateTableConstraints($constraintName, array $constraints)
1095
    {
1096 9
        $annotations = array();
1097 9
        foreach ($constraints as $name => $constraint) {
1098 9
            $columns = array();
1099 9
            foreach ($constraint['columns'] as $column) {
1100 9
                $columns[] = '"' . $column . '"';
1101
            }
1102 9
            $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})';
1103
        }
1104
1105 9
        return implode(', ', $annotations);
1106
    }
1107
1108
    /**
1109
     * @param ClassMetadataInfo $metadata
1110
     *
1111
     * @return string
1112
     */
1113 29
    protected function generateInheritanceAnnotation(ClassMetadataInfo $metadata)
1114
    {
1115 29
        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1116
            return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")';
1117
        }
1118 29
    }
1119
1120
    /**
1121
     * @param ClassMetadataInfo $metadata
1122
     *
1123
     * @return string
1124
     */
1125 29
    protected function generateDiscriminatorColumnAnnotation(ClassMetadataInfo $metadata)
1126
    {
1127 29
        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1128
            $discrColumn = $metadata->discriminatorColumn;
1129
            $columnDefinition = 'name="' . $discrColumn['name']
1130
                . '", type="' . $discrColumn['type']
1131
                . '", length=' . $discrColumn['length'];
1132
1133
            return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
1134
        }
1135 29
    }
1136
1137
    /**
1138
     * @param ClassMetadataInfo $metadata
1139
     *
1140
     * @return string
1141
     */
1142 29
    protected function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata)
1143
    {
1144 29
        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1145
            $inheritanceClassMap = array();
1146
1147
            foreach ($metadata->discriminatorMap as $type => $class) {
1148
                $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
1149
            }
1150
1151
            return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
1152
        }
1153 29
    }
1154
1155
    /**
1156
     * @param ClassMetadataInfo $metadata
1157
     *
1158
     * @return string
1159
     */
1160 29
    protected function generateEntityStubMethods(ClassMetadataInfo $metadata)
1161
    {
1162 29
        $methods = array();
1163
1164 29
        foreach ($metadata->fieldMappings as $fieldMapping) {
1165 28
            if (isset($fieldMapping['declaredField']) &&
1166 28
                isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
1167
            ) {
1168
                continue;
1169
            }
1170
1171 28
            if (( ! isset($fieldMapping['id']) ||
1172 26
                    ! $fieldMapping['id'] ||
1173 28
                    $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE
1174 28
                ) && (! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable)
1175
            ) {
1176 25
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'])) {
1177 25
                    $methods[] = $code;
1178
                }
1179
            }
1180
1181 28
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'])) {
1182 28
                $methods[] = $code;
1183
            }
1184
        }
1185
1186 29
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1187 8
            if (isset($embeddedClass['declaredField'])) {
1188 1
                continue;
1189
            }
1190
1191 8
            if ( ! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) {
1192 7
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldName, $embeddedClass['class'])) {
1193 7
                    $methods[] = $code;
1194
                }
1195
            }
1196
1197 8
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldName, $embeddedClass['class'])) {
1198 8
                $methods[] = $code;
1199
            }
1200
        }
1201
1202 29
        foreach ($metadata->associationMappings as $associationMapping) {
1203 12
            if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
1204 11
                $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null;
1205 11
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1206 9
                    $methods[] = $code;
1207
                }
1208 11
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1209 11
                    $methods[] = $code;
1210
                }
1211 10
            } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1212 10
                if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1213 10
                    $methods[] = $code;
1214
                }
1215 10
                if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1216 10
                    $methods[] = $code;
1217
                }
1218 10
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) {
1219 12
                    $methods[] = $code;
1220
                }
1221
            }
1222
        }
1223
1224 29
        return implode("\n\n", $methods);
1225
    }
1226
1227
    /**
1228
     * @param array $associationMapping
1229
     *
1230
     * @return bool
1231
     */
1232 11
    protected function isAssociationIsNullable(array $associationMapping)
1233
    {
1234 11
        if (isset($associationMapping['id']) && $associationMapping['id']) {
1235
            return false;
1236
        }
1237
1238 11
        if (isset($associationMapping['joinColumns'])) {
1239 2
            $joinColumns = $associationMapping['joinColumns'];
1240
        } else {
1241
            //@todo there is no way to retrieve targetEntity metadata
1242 9
            $joinColumns = array();
1243
        }
1244
1245 11
        foreach ($joinColumns as $joinColumn) {
1246 2
            if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
1247 2
                return false;
1248
            }
1249
        }
1250
1251 11
        return true;
1252
    }
1253
1254
    /**
1255
     * @param ClassMetadataInfo $metadata
1256
     *
1257
     * @return string
1258
     */
1259 30
    protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
1260
    {
1261 30
        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...
1262 10
            $methods = array();
1263
1264 10
            foreach ($metadata->lifecycleCallbacks as $name => $callbacks) {
1265 10
                foreach ($callbacks as $callback) {
1266 10
                    if ($code = $this->generateLifecycleCallbackMethod($name, $callback, $metadata)) {
1267 10
                        $methods[] = $code;
1268
                    }
1269
                }
1270
            }
1271
1272 10
            return implode("\n\n", $methods);
1273
        }
1274
1275 27
        return "";
1276
    }
1277
1278
    /**
1279
     * @param ClassMetadataInfo $metadata
1280
     *
1281
     * @return string
1282
     */
1283 30
    protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
1284
    {
1285 30
        $lines = array();
1286
1287 30
        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 30
        return implode("\n", $lines);
1298
    }
1299
1300
    /**
1301
     * @param ClassMetadataInfo $metadata
1302
     *
1303
     * @return string
1304
     */
1305 30
    protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata)
1306
    {
1307 30
        $lines = array();
1308
1309 30
        foreach ($metadata->fieldMappings as $fieldMapping) {
1310 29
            if ($this->hasProperty($fieldMapping['fieldName'], $metadata) ||
1311 28
                $metadata->isInheritedField($fieldMapping['fieldName']) ||
1312
                (
1313 27
                    isset($fieldMapping['declaredField']) &&
1314 29
                    isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
1315
                )
1316
            ) {
1317 4
                continue;
1318
            }
1319
1320 27
            $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
1321 27
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName']
1322 27
                     . (isset($fieldMapping['options']['default']) ? ' = ' . var_export($fieldMapping['options']['default'], true) : null) . ";\n";
1323
        }
1324
1325 30
        return implode("\n", $lines);
1326
    }
1327
1328
    /**
1329
     * @param ClassMetadataInfo $metadata
1330
     *
1331
     * @return string
1332
     */
1333 30
    protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata)
1334
    {
1335 30
        $lines = array();
1336
1337 30
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1338 8
            if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) {
1339 2
                continue;
1340
            }
1341
1342 8
            $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass);
1343 8
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n";
1344
        }
1345
1346 30
        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 28
    protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null,  $defaultValue = null)
1359
    {
1360 28
        $methodName = $type . Inflector::classify($fieldName);
1361 28
        $variableName = Inflector::camelize($fieldName);
1362 28
        if (in_array($type, array("add", "remove"))) {
1363 10
            $methodName = Inflector::singularize($methodName);
1364 10
            $variableName = Inflector::singularize($variableName);
1365
        }
1366
1367 28
        if ($this->hasMethod($methodName, $metadata)) {
1368 5
            return '';
1369
        }
1370 28
        $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName);
1371
1372 28
        $var = sprintf('%sMethodTemplate', $type);
1373 28
        $template = static::$$var;
1374
1375 28
        $methodTypeHint = null;
1376 28
        $types          = Type::getTypesMap();
1377 28
        $variableType   = $typeHint ? $this->getType($typeHint) : null;
1378
1379 28
        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 12
            $variableType   =  '\\' . ltrim($variableType, '\\');
1381 12
            $methodTypeHint =  '\\' . $typeHint . ' ';
1382
        }
1383
1384
        $replacements = array(
1385 28
          '<description>'       => ucfirst($type) . ' ' . $variableName . '.',
1386 28
          '<methodTypeHint>'    => $methodTypeHint,
1387 28
          '<variableType>'      => $variableType,
1388 28
          '<variableName>'      => $variableName,
1389 28
          '<methodName>'        => $methodName,
1390 28
          '<fieldName>'         => $fieldName,
1391 28
          '<variableDefault>'   => ($defaultValue !== null ) ? (' = '.$defaultValue) : '',
1392 28
          '<entity>'            => $this->getClassName($metadata)
1393
        );
1394
1395 28
        $method = str_replace(
1396
            array_keys($replacements),
1397
            array_values($replacements),
1398
            $template
1399
        );
1400
1401 28
        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 = array(
1419 10
            '<name>'        => $this->annotationsPrefix . ucfirst($name),
1420 10
            '<methodName>'  => $methodName,
1421
        );
1422
1423 10
        $method = str_replace(
1424
            array_keys($replacements),
1425
            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 = array();
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 = array();
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 = array();
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 = array();
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 = array('"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 = array(
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 = array();
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 = array();
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 = array();
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 = array();
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 27
    protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
1627
    {
1628 27
        $lines = array();
1629 27
        $lines[] = $this->spaces . '/**';
1630 27
        $lines[] = $this->spaces . ' * @var ' . $this->getType($fieldMapping['type']);
1631
1632 27
        if ($this->generateAnnotations) {
1633 27
            $lines[] = $this->spaces . ' *';
1634
1635 27
            $column = array();
1636 27
            if (isset($fieldMapping['columnName'])) {
1637 27
                $column[] = 'name="' . $fieldMapping['columnName'] . '"';
1638
            }
1639
1640 27
            if (isset($fieldMapping['type'])) {
1641 27
                $column[] = 'type="' . $fieldMapping['type'] . '"';
1642
            }
1643
1644 27
            if (isset($fieldMapping['length'])) {
1645 4
                $column[] = 'length=' . $fieldMapping['length'];
1646
            }
1647
1648 27
            if (isset($fieldMapping['precision'])) {
1649 4
                $column[] = 'precision=' .  $fieldMapping['precision'];
1650
            }
1651
1652 27
            if (isset($fieldMapping['scale'])) {
1653 4
                $column[] = 'scale=' . $fieldMapping['scale'];
1654
            }
1655
1656 27
            if (isset($fieldMapping['nullable'])) {
1657 8
                $column[] = 'nullable=' .  var_export($fieldMapping['nullable'], true);
1658
            }
1659
1660 27
            $options = [];
1661
1662 27
            if (isset($fieldMapping['options']['unsigned']) && $fieldMapping['options']['unsigned']) {
1663 1
                $options[] = '"unsigned"=true';
1664
            }
1665
1666 27
            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...
1667 1
                $column[] = 'options={'.implode(',', $options).'}';
1668
            }
1669
1670 27
            if (isset($fieldMapping['columnDefinition'])) {
1671 1
                $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
1672
            }
1673
1674 27
            if (isset($fieldMapping['unique'])) {
1675 4
                $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
1676
            }
1677
1678 27
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
1679
1680 27
            if (isset($fieldMapping['id']) && $fieldMapping['id']) {
1681 25
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1682
1683 25
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1684 25
                    $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1685
                }
1686
1687 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...
1688 1
                    $sequenceGenerator = array();
1689
1690 1
                    if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) {
1691 1
                        $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"';
1692
                    }
1693
1694 1
                    if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
1695 1
                        $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize'];
1696
                    }
1697
1698 1
                    if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
1699 1
                        $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue'];
1700
                    }
1701
1702 1
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
1703
                }
1704
            }
1705
1706 27
            if (isset($fieldMapping['version']) && $fieldMapping['version']) {
1707
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version';
1708
            }
1709
        }
1710
1711 27
        $lines[] = $this->spaces . ' */';
1712
1713 27
        return implode("\n", $lines);
1714
    }
1715
1716
    /**
1717
     * @param array $embeddedClass
1718
     *
1719
     * @return string
1720
     */
1721 8
    protected function generateEmbeddedPropertyDocBlock(array $embeddedClass)
1722
    {
1723 8
        $lines = array();
1724 8
        $lines[] = $this->spaces . '/**';
1725 8
        $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\');
1726
1727 8
        if ($this->generateAnnotations) {
1728 8
            $lines[] = $this->spaces . ' *';
1729
1730 8
            $embedded = array('class="' . $embeddedClass['class'] . '"');
1731
1732 8
            if (isset($fieldMapping['columnPrefix'])) {
0 ignored issues
show
Bug introduced by
The variable $fieldMapping seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
1733
                $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true);
1734
            }
1735
1736 8
            $lines[] = $this->spaces . ' * @' .
1737 8
                $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')';
1738
        }
1739
1740 8
        $lines[] = $this->spaces . ' */';
1741
1742 8
        return implode("\n", $lines);
1743
    }
1744
1745
    /**
1746
     * @param string $code
1747
     * @param int    $num
1748
     *
1749
     * @return string
1750
     */
1751 29
    protected function prefixCodeWithSpaces($code, $num = 1)
1752
    {
1753 29
        $lines = explode("\n", $code);
1754
1755 29
        foreach ($lines as $key => $value) {
1756 29
            if ( ! empty($value)) {
1757 29
                $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
1758
            }
1759
        }
1760
1761 29
        return implode("\n", $lines);
1762
    }
1763
1764
    /**
1765
     * @param integer $type The inheritance type used by the class and its subclasses.
1766
     *
1767
     * @return string The literal string for the inheritance type.
1768
     *
1769
     * @throws \InvalidArgumentException When the inheritance type does not exist.
1770
     */
1771 1
    protected function getInheritanceTypeString($type)
1772
    {
1773 1
        if ( ! isset(static::$inheritanceTypeMap[$type])) {
1774 1
            throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type));
1775
        }
1776
1777 1
        return static::$inheritanceTypeMap[$type];
1778
    }
1779
1780
    /**
1781
     * @param integer $type The policy used for change-tracking for the mapped class.
1782
     *
1783
     * @return string The literal string for the change-tracking type.
1784
     *
1785
     * @throws \InvalidArgumentException When the change-tracking type does not exist.
1786
     */
1787 1
    protected function getChangeTrackingPolicyString($type)
1788
    {
1789 1
        if ( ! isset(static::$changeTrackingPolicyMap[$type])) {
1790 1
            throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type));
1791
        }
1792
1793 1
        return static::$changeTrackingPolicyMap[$type];
1794
    }
1795
1796
    /**
1797
     * @param integer $type The generator to use for the mapped class.
1798
     *
1799
     * @return string The literal string for the generator type.
1800
     *
1801
     * @throws \InvalidArgumentException    When the generator type does not exist.
1802
     */
1803 26
    protected function getIdGeneratorTypeString($type)
1804
    {
1805 26
        if ( ! isset(static::$generatorStrategyMap[$type])) {
1806 1
            throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type));
1807
        }
1808
1809 26
        return static::$generatorStrategyMap[$type];
1810
    }
1811
1812
    /**
1813
     * Exports (nested) option elements.
1814
     *
1815
     * @param array $options
1816
     *
1817
     * @return string
1818
     */
1819 1
    private function exportTableOptions(array $options)
1820
    {
1821 1
        $optionsStr = array();
1822
1823 1
        foreach ($options as $name => $option) {
1824 1
            if (is_array($option)) {
1825 1
                $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}';
1826
            } else {
1827 1
                $optionsStr[] = '"' . $name . '"="' . (string) $option . '"';
1828
            }
1829
        }
1830
1831 1
        return implode(',', $optionsStr);
1832
    }
1833
}
1834