Completed
Push — master ( 89a008...28025b )
by Marco
09:44 queued 19s
created

EntityGenerator::generateJoinColumnAnnotation()   D

Complexity

Conditions 10
Paths 144

Size

Total Lines 30
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 10

Importance

Changes 0
Metric Value
dl 0
loc 30
c 0
b 0
f 0
ccs 15
cts 15
cp 1
rs 4.606
cc 10
eloc 15
nc 144
nop 1
crap 10

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Tools;
21
22
use Doctrine\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
            $nullableField = $this->nullableFieldExpression($fieldMapping);
1172
1173 28
            if (( ! isset($fieldMapping['id']) ||
1174 26
                    ! $fieldMapping['id'] ||
1175 28
                    $metadata->generatorType == ClassMetadataInfo::GENERATOR_TYPE_NONE
1176 28
                ) && (! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable)
1177 28
                && $code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)
1178
            ) {
1179 25
                $methods[] = $code;
1180
            }
1181
1182 28
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)) {
1183 28
                $methods[] = $code;
1184
            }
1185
        }
1186
1187 29
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1188 8
            if (isset($embeddedClass['declaredField'])) {
1189 1
                continue;
1190
            }
1191
1192 8
            if ( ! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) {
1193 7
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldName, $embeddedClass['class'])) {
1194 7
                    $methods[] = $code;
1195
                }
1196
            }
1197
1198 8
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldName, $embeddedClass['class'])) {
1199 8
                $methods[] = $code;
1200
            }
1201
        }
1202
1203 29
        foreach ($metadata->associationMappings as $associationMapping) {
1204 12
            if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
1205 11
                $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null;
1206 11
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1207 9
                    $methods[] = $code;
1208
                }
1209 11
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1210 11
                    $methods[] = $code;
1211
                }
1212 10
            } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1213 10
                if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1214 10
                    $methods[] = $code;
1215
                }
1216 10
                if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1217 10
                    $methods[] = $code;
1218
                }
1219 10
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], 'Doctrine\Common\Collections\Collection')) {
1220 12
                    $methods[] = $code;
1221
                }
1222
            }
1223
        }
1224
1225 29
        return implode("\n\n", $methods);
1226
    }
1227
1228
    /**
1229
     * @param array $associationMapping
1230
     *
1231
     * @return bool
1232
     */
1233 11
    protected function isAssociationIsNullable(array $associationMapping)
1234
    {
1235 11
        if (isset($associationMapping['id']) && $associationMapping['id']) {
1236
            return false;
1237
        }
1238
1239 11
        if (isset($associationMapping['joinColumns'])) {
1240 2
            $joinColumns = $associationMapping['joinColumns'];
1241
        } else {
1242
            //@todo there is no way to retrieve targetEntity metadata
1243 9
            $joinColumns = array();
1244
        }
1245
1246 11
        foreach ($joinColumns as $joinColumn) {
1247 2
            if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
1248 2
                return false;
1249
            }
1250
        }
1251
1252 11
        return true;
1253
    }
1254
1255
    /**
1256
     * @param ClassMetadataInfo $metadata
1257
     *
1258
     * @return string
1259
     */
1260 30
    protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
1261
    {
1262 30
        if (empty($metadata->lifecycleCallbacks)) {
1263 27
            return '';
1264
        }
1265
1266 10
        $methods = [];
1267
1268 10
        foreach ($metadata->lifecycleCallbacks as $name => $callbacks) {
1269 10
            foreach ($callbacks as $callback) {
1270 10
                $methods[] = $this->generateLifecycleCallbackMethod($name, $callback, $metadata);
1271
            }
1272
        }
1273
1274 10
        return implode("\n\n", array_filter($methods));
1275
    }
1276
1277
    /**
1278
     * @param ClassMetadataInfo $metadata
1279
     *
1280
     * @return string
1281
     */
1282 30
    protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
1283
    {
1284 30
        $lines = array();
1285
1286 30
        foreach ($metadata->associationMappings as $associationMapping) {
1287 13
            if ($this->hasProperty($associationMapping['fieldName'], $metadata)) {
1288 4
                continue;
1289
            }
1290
1291 11
            $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata);
1292 11
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName']
1293 11
                     . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n";
1294
        }
1295
1296 30
        return implode("\n", $lines);
1297
    }
1298
1299
    /**
1300
     * @param ClassMetadataInfo $metadata
1301
     *
1302
     * @return string
1303
     */
1304 30
    protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata)
1305
    {
1306 30
        $lines = array();
1307
1308 30
        foreach ($metadata->fieldMappings as $fieldMapping) {
1309 29
            if ($this->hasProperty($fieldMapping['fieldName'], $metadata) ||
1310 28
                $metadata->isInheritedField($fieldMapping['fieldName']) ||
1311
                (
1312 27
                    isset($fieldMapping['declaredField']) &&
1313 29
                    isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
1314
                )
1315
            ) {
1316 4
                continue;
1317
            }
1318
1319 27
            $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
1320 27
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName']
1321 27
                     . (isset($fieldMapping['options']['default']) ? ' = ' . var_export($fieldMapping['options']['default'], true) : null) . ";\n";
1322
        }
1323
1324 30
        return implode("\n", $lines);
1325
    }
1326
1327
    /**
1328
     * @param ClassMetadataInfo $metadata
1329
     *
1330
     * @return string
1331
     */
1332 30
    protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata)
1333
    {
1334 30
        $lines = array();
1335
1336 30
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1337 8
            if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) {
1338 2
                continue;
1339
            }
1340
1341 8
            $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass);
1342 8
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n";
1343
        }
1344
1345 30
        return implode("\n", $lines);
1346
    }
1347
1348
    /**
1349
     * @param ClassMetadataInfo $metadata
1350
     * @param string            $type
1351
     * @param string            $fieldName
1352
     * @param string|null       $typeHint
1353
     * @param string|null       $defaultValue
1354
     *
1355
     * @return string
1356
     */
1357 28
    protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
1358
    {
1359 28
        $methodName = $type . Inflector::classify($fieldName);
1360 28
        $variableName = Inflector::camelize($fieldName);
1361 28
        if (in_array($type, array("add", "remove"))) {
1362 10
            $methodName = Inflector::singularize($methodName);
1363 10
            $variableName = Inflector::singularize($variableName);
1364
        }
1365
1366 28
        if ($this->hasMethod($methodName, $metadata)) {
1367 5
            return '';
1368
        }
1369 28
        $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName);
1370
1371 28
        $var = sprintf('%sMethodTemplate', $type);
1372 28
        $template = static::$$var;
1373
1374 28
        $methodTypeHint = null;
1375 28
        $types          = Type::getTypesMap();
1376 28
        $variableType   = $typeHint ? $this->getType($typeHint) : null;
1377
1378 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...
1379 12
            $variableType   =  '\\' . ltrim($variableType, '\\');
1380 12
            $methodTypeHint =  '\\' . $typeHint . ' ';
1381
        }
1382
1383
        $replacements = array(
1384 28
          '<description>'       => ucfirst($type) . ' ' . $variableName . '.',
1385 28
          '<methodTypeHint>'    => $methodTypeHint,
1386 28
          '<variableType>'      => $variableType . (null !== $defaultValue ? ('|' . $defaultValue) : ''),
1387 28
          '<variableName>'      => $variableName,
1388 28
          '<methodName>'        => $methodName,
1389 28
          '<fieldName>'         => $fieldName,
1390 28
          '<variableDefault>'   => ($defaultValue !== null ) ? (' = ' . $defaultValue) : '',
1391 28
          '<entity>'            => $this->getClassName($metadata)
1392
        );
1393
1394 28
        $method = str_replace(
1395
            array_keys($replacements),
1396
            array_values($replacements),
1397
            $template
1398
        );
1399
1400 28
        return $this->prefixCodeWithSpaces($method);
1401
    }
1402
1403
    /**
1404
     * @param string            $name
1405
     * @param string            $methodName
1406
     * @param ClassMetadataInfo $metadata
1407
     *
1408
     * @return string
1409
     */
1410 10
    protected function generateLifecycleCallbackMethod($name, $methodName, ClassMetadataInfo $metadata)
1411
    {
1412 10
        if ($this->hasMethod($methodName, $metadata)) {
1413 2
            return '';
1414
        }
1415 10
        $this->staticReflection[$metadata->name]['methods'][] = $methodName;
1416
1417
        $replacements = array(
1418 10
            '<name>'        => $this->annotationsPrefix . ucfirst($name),
1419 10
            '<methodName>'  => $methodName,
1420
        );
1421
1422 10
        $method = str_replace(
1423
            array_keys($replacements),
1424
            array_values($replacements),
1425 10
            static::$lifecycleCallbackMethodTemplate
1426
        );
1427
1428 10
        return $this->prefixCodeWithSpaces($method);
1429
    }
1430
1431
    /**
1432
     * @param array $joinColumn
1433
     *
1434
     * @return string
1435
     */
1436 11
    protected function generateJoinColumnAnnotation(array $joinColumn)
1437
    {
1438 11
        $joinColumnAnnot = array();
1439
1440 11
        if (isset($joinColumn['name'])) {
1441 11
            $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"';
1442
        }
1443
1444 11
        if (isset($joinColumn['referencedColumnName'])) {
1445 11
            $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"';
1446
        }
1447
1448 11
        if (isset($joinColumn['unique']) && $joinColumn['unique']) {
1449 1
            $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false');
1450
        }
1451
1452 11
        if (isset($joinColumn['nullable'])) {
1453 1
            $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false');
1454
        }
1455
1456 11
        if (isset($joinColumn['onDelete'])) {
1457 1
            $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
1458
        }
1459
1460 11
        if (isset($joinColumn['columnDefinition'])) {
1461 1
            $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
1462
        }
1463
1464 11
        return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
1465
    }
1466
1467
    /**
1468
     * @param array             $associationMapping
1469
     * @param ClassMetadataInfo $metadata
1470
     *
1471
     * @return string
1472
     */
1473 11
    protected function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
1474
    {
1475 11
        $lines = array();
1476 11
        $lines[] = $this->spaces . '/**';
1477
1478 11
        if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1479 11
            $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection';
1480
        } else {
1481 10
            $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\');
1482
        }
1483
1484 11
        if ($this->generateAnnotations) {
1485 11
            $lines[] = $this->spaces . ' *';
1486
1487 11
            if (isset($associationMapping['id']) && $associationMapping['id']) {
1488
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1489
1490
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1491
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1492
                }
1493
            }
1494
1495 11
            $type = null;
1496 11
            switch ($associationMapping['type']) {
1497 11
                case ClassMetadataInfo::ONE_TO_ONE:
1498 10
                    $type = 'OneToOne';
1499 10
                    break;
1500 11
                case ClassMetadataInfo::MANY_TO_ONE:
1501 1
                    $type = 'ManyToOne';
1502 1
                    break;
1503 11
                case ClassMetadataInfo::ONE_TO_MANY:
1504 1
                    $type = 'OneToMany';
1505 1
                    break;
1506 11
                case ClassMetadataInfo::MANY_TO_MANY:
1507 11
                    $type = 'ManyToMany';
1508 11
                    break;
1509
            }
1510 11
            $typeOptions = array();
1511
1512 11
            if (isset($associationMapping['targetEntity'])) {
1513 11
                $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"';
1514
            }
1515
1516 11
            if (isset($associationMapping['inversedBy'])) {
1517 1
                $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"';
1518
            }
1519
1520 11
            if (isset($associationMapping['mappedBy'])) {
1521 10
                $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"';
1522
            }
1523
1524 11
            if ($associationMapping['cascade']) {
1525 1
                $cascades = array();
1526
1527 1
                if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"';
1528 1
                if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"';
1529 1
                if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"';
1530 1
                if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
1531 1
                if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
1532
1533 1
                if (count($cascades) === 5) {
1534 1
                    $cascades = array('"all"');
1535
                }
1536
1537 1
                $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
1538
            }
1539
1540 11
            if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
1541 1
                $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
1542
            }
1543
1544 11
            if (isset($associationMapping['fetch']) && $associationMapping['fetch'] !== ClassMetadataInfo::FETCH_LAZY) {
1545
                $fetchMap = array(
1546 10
                    ClassMetadataInfo::FETCH_EXTRA_LAZY => 'EXTRA_LAZY',
1547 10
                    ClassMetadataInfo::FETCH_EAGER      => 'EAGER',
1548
                );
1549
1550 10
                $typeOptions[] = 'fetch="' . $fetchMap[$associationMapping['fetch']] . '"';
1551
            }
1552
1553 11
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
1554
1555 11
            if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
1556 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({';
1557
1558 1
                $joinColumnsLines = array();
1559
1560 1
                foreach ($associationMapping['joinColumns'] as $joinColumn) {
1561 1
                    if ($joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn)) {
1562 1
                        $joinColumnsLines[] = $this->spaces . ' *   ' . $joinColumnAnnot;
1563
                    }
1564
                }
1565
1566 1
                $lines[] = implode(",\n", $joinColumnsLines);
1567 1
                $lines[] = $this->spaces . ' * })';
1568
            }
1569
1570 11
            if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) {
1571 11
                $joinTable = array();
1572 11
                $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"';
1573
1574 11
                if (isset($associationMapping['joinTable']['schema'])) {
1575
                    $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
1576
                }
1577
1578 11
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
1579 11
                $lines[] = $this->spaces . ' *   joinColumns={';
1580
1581 11
                $joinColumnsLines = array();
1582
1583 11
                foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
1584 11
                    $joinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1585
                }
1586
1587 11
                $lines[] = implode(",". PHP_EOL, $joinColumnsLines);
1588 11
                $lines[] = $this->spaces . ' *   },';
1589 11
                $lines[] = $this->spaces . ' *   inverseJoinColumns={';
1590
1591 11
                $inverseJoinColumnsLines = array();
1592
1593 11
                foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
1594 11
                    $inverseJoinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1595
                }
1596
1597 11
                $lines[] = implode(",". PHP_EOL, $inverseJoinColumnsLines);
1598 11
                $lines[] = $this->spaces . ' *   }';
1599 11
                $lines[] = $this->spaces . ' * )';
1600
            }
1601
1602 11
            if (isset($associationMapping['orderBy'])) {
1603 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({';
1604
1605 1
                foreach ($associationMapping['orderBy'] as $name => $direction) {
1606 1
                    $lines[] = $this->spaces . ' *     "' . $name . '"="' . $direction . '",';
1607
                }
1608
1609 1
                $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
1610 1
                $lines[] = $this->spaces . ' * })';
1611
            }
1612
        }
1613
1614 11
        $lines[] = $this->spaces . ' */';
1615
1616 11
        return implode("\n", $lines);
1617
    }
1618
1619
    /**
1620
     * @param array             $fieldMapping
1621
     * @param ClassMetadataInfo $metadata
1622
     *
1623
     * @return string
1624
     */
1625 27
    protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
1626
    {
1627 27
        $lines = array();
1628 27
        $lines[] = $this->spaces . '/**';
1629 27
        $lines[] = $this->spaces . ' * @var '
1630 27
            . $this->getType($fieldMapping['type'])
1631 27
            . ($this->nullableFieldExpression($fieldMapping) ? '|null' : '');
1632
1633 27
        if ($this->generateAnnotations) {
1634 27
            $lines[] = $this->spaces . ' *';
1635
1636 27
            $column = array();
1637 27
            if (isset($fieldMapping['columnName'])) {
1638 27
                $column[] = 'name="' . $fieldMapping['columnName'] . '"';
1639
            }
1640
1641 27
            if (isset($fieldMapping['type'])) {
1642 27
                $column[] = 'type="' . $fieldMapping['type'] . '"';
1643
            }
1644
1645 27
            if (isset($fieldMapping['length'])) {
1646 4
                $column[] = 'length=' . $fieldMapping['length'];
1647
            }
1648
1649 27
            if (isset($fieldMapping['precision'])) {
1650 4
                $column[] = 'precision=' .  $fieldMapping['precision'];
1651
            }
1652
1653 27
            if (isset($fieldMapping['scale'])) {
1654 4
                $column[] = 'scale=' . $fieldMapping['scale'];
1655
            }
1656
1657 27
            if (isset($fieldMapping['nullable'])) {
1658 8
                $column[] = 'nullable=' .  var_export($fieldMapping['nullable'], true);
1659
            }
1660
1661 27
            $options = [];
1662
1663 27
            if (isset($fieldMapping['options']['unsigned']) && $fieldMapping['options']['unsigned']) {
1664 1
                $options[] = '"unsigned"=true';
1665
            }
1666
1667 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...
1668 1
                $column[] = 'options={'.implode(',', $options).'}';
1669
            }
1670
1671 27
            if (isset($fieldMapping['columnDefinition'])) {
1672 1
                $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
1673
            }
1674
1675 27
            if (isset($fieldMapping['unique'])) {
1676 4
                $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
1677
            }
1678
1679 27
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
1680
1681 27
            if (isset($fieldMapping['id']) && $fieldMapping['id']) {
1682 25
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1683
1684 25
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1685 25
                    $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1686
                }
1687
1688 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...
1689 1
                    $sequenceGenerator = array();
1690
1691 1
                    if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) {
1692 1
                        $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"';
1693
                    }
1694
1695 1
                    if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
1696 1
                        $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize'];
1697
                    }
1698
1699 1
                    if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
1700 1
                        $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue'];
1701
                    }
1702
1703 1
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
1704
                }
1705
            }
1706
1707 27
            if (isset($fieldMapping['version']) && $fieldMapping['version']) {
1708
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version';
1709
            }
1710
        }
1711
1712 27
        $lines[] = $this->spaces . ' */';
1713
1714 27
        return implode("\n", $lines);
1715
    }
1716
1717
    /**
1718
     * @param array $embeddedClass
1719
     *
1720
     * @return string
1721
     */
1722 8
    protected function generateEmbeddedPropertyDocBlock(array $embeddedClass)
1723
    {
1724 8
        $lines = array();
1725 8
        $lines[] = $this->spaces . '/**';
1726 8
        $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\');
1727
1728 8
        if ($this->generateAnnotations) {
1729 8
            $lines[] = $this->spaces . ' *';
1730
1731 8
            $embedded = array('class="' . $embeddedClass['class'] . '"');
1732
1733 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...
1734
                $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true);
1735
            }
1736
1737 8
            $lines[] = $this->spaces . ' * @' .
1738 8
                $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')';
1739
        }
1740
1741 8
        $lines[] = $this->spaces . ' */';
1742
1743 8
        return implode("\n", $lines);
1744
    }
1745
1746
    /**
1747
     * @param string $code
1748
     * @param int    $num
1749
     *
1750
     * @return string
1751
     */
1752 29
    protected function prefixCodeWithSpaces($code, $num = 1)
1753
    {
1754 29
        $lines = explode("\n", $code);
1755
1756 29
        foreach ($lines as $key => $value) {
1757 29
            if ( ! empty($value)) {
1758 29
                $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
1759
            }
1760
        }
1761
1762 29
        return implode("\n", $lines);
1763
    }
1764
1765
    /**
1766
     * @param integer $type The inheritance type used by the class and its subclasses.
1767
     *
1768
     * @return string The literal string for the inheritance type.
1769
     *
1770
     * @throws \InvalidArgumentException When the inheritance type does not exist.
1771
     */
1772 1
    protected function getInheritanceTypeString($type)
1773
    {
1774 1
        if ( ! isset(static::$inheritanceTypeMap[$type])) {
1775 1
            throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type));
1776
        }
1777
1778 1
        return static::$inheritanceTypeMap[$type];
1779
    }
1780
1781
    /**
1782
     * @param integer $type The policy used for change-tracking for the mapped class.
1783
     *
1784
     * @return string The literal string for the change-tracking type.
1785
     *
1786
     * @throws \InvalidArgumentException When the change-tracking type does not exist.
1787
     */
1788 1
    protected function getChangeTrackingPolicyString($type)
1789
    {
1790 1
        if ( ! isset(static::$changeTrackingPolicyMap[$type])) {
1791 1
            throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type));
1792
        }
1793
1794 1
        return static::$changeTrackingPolicyMap[$type];
1795
    }
1796
1797
    /**
1798
     * @param integer $type The generator to use for the mapped class.
1799
     *
1800
     * @return string The literal string for the generator type.
1801
     *
1802
     * @throws \InvalidArgumentException    When the generator type does not exist.
1803
     */
1804 26
    protected function getIdGeneratorTypeString($type)
1805
    {
1806 26
        if ( ! isset(static::$generatorStrategyMap[$type])) {
1807 1
            throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type));
1808
        }
1809
1810 26
        return static::$generatorStrategyMap[$type];
1811
    }
1812
1813
    /**
1814
     * @param array $fieldMapping
1815
     *
1816
     * @return string|null
1817
     */
1818 29
    private function nullableFieldExpression(array $fieldMapping)
1819
    {
1820 29
        if (isset($fieldMapping['nullable']) && true === $fieldMapping['nullable']) {
1821 5
            return 'null';
1822
        }
1823
1824 29
        return null;
1825
    }
1826
1827
    /**
1828
     * Exports (nested) option elements.
1829
     *
1830
     * @param array $options
1831
     *
1832
     * @return string
1833
     */
1834 1
    private function exportTableOptions(array $options)
1835
    {
1836 1
        $optionsStr = array();
1837
1838 1
        foreach ($options as $name => $option) {
1839 1
            if (is_array($option)) {
1840 1
                $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}';
1841
            } else {
1842 1
                $optionsStr[] = '"' . $name . '"="' . (string) $option . '"';
1843
            }
1844
        }
1845
1846 1
        return implode(',', $optionsStr);
1847
    }
1848
}
1849