Completed
Pull Request — master (#6068)
by Javier
10:51
created

EntityGenerator::getNamespace()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
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) || (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->isNew && $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 . (strlen($body) > 0 ? "\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
        $lastSeenNamespace = "";
806 8
        $lastSeenClass = false;
807
808 8
        $inNamespace = false;
809 8
        $inClass = false;
810
811 8
        for ($i = 0; $i < count($tokens); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
812 8
            $token = $tokens[$i];
813 8
            if (in_array($token[0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT))) {
814 8
                continue;
815
            }
816
817 8
            if ($inNamespace) {
818 8
                if ($token[0] == T_NS_SEPARATOR || $token[0] == T_STRING) {
819 8
                    $lastSeenNamespace .= $token[1];
820 8
                } elseif (is_string($token) && in_array($token, array(';', '{'))) {
821 8
                    $inNamespace = false;
822
                }
823
            }
824
825 8
            if ($inClass) {
826 8
                $inClass = false;
827 8
                $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
828 8
                $this->staticReflection[$lastSeenClass]['properties'] = array();
829 8
                $this->staticReflection[$lastSeenClass]['methods'] = array();
830
            }
831
832 8
            if ($token[0] == T_NAMESPACE) {
833 8
                $lastSeenNamespace = "";
834 8
                $inNamespace = true;
835 8
            } elseif ($token[0] == T_CLASS && $tokens[$i-1][0] != T_DOUBLE_COLON) {
836 8
                $inClass = true;
837 8
            } elseif ($token[0] == T_FUNCTION) {
838 3
                if ($tokens[$i+2][0] == T_STRING) {
839 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+2][1]);
840
                } elseif ($tokens[$i+2] == "&" && $tokens[$i+3][0] == T_STRING) {
841 3
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+3][1]);
842
                }
843 8
            } elseif (in_array($token[0], array(T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED)) && $tokens[$i+2][0] != T_FUNCTION) {
844 4
                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
845
            }
846
        }
847 8
    }
848
849
    /**
850
     * @param string            $property
851
     * @param ClassMetadataInfo $metadata
852
     *
853
     * @return bool
854
     */
855 29
    protected function hasProperty($property, ClassMetadataInfo $metadata)
856
    {
857 29
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
858
            // don't generate property if its already on the base class.
859 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
860 2
            if ($reflClass->hasProperty($property)) {
861 1
                return true;
862
            }
863
        }
864
865
        // check traits for existing property
866 28
        foreach ($this->getTraits($metadata) as $trait) {
867 2
            if ($trait->hasProperty($property)) {
868 2
                return true;
869
            }
870
        }
871
872
        return (
873 28
            isset($this->staticReflection[$metadata->name]) &&
874 28
            in_array($property, $this->staticReflection[$metadata->name]['properties'])
875
        );
876
    }
877
878
    /**
879
     * @param string            $method
880
     * @param ClassMetadataInfo $metadata
881
     *
882
     * @return bool
883
     */
884 30
    protected function hasMethod($method, ClassMetadataInfo $metadata)
885
    {
886 30
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
887
            // don't generate method if its already on the base class.
888 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
889
890 2
            if ($reflClass->hasMethod($method)) {
891 1
                return true;
892
            }
893
        }
894
895
        // check traits for existing method
896 30
        foreach ($this->getTraits($metadata) as $trait) {
897 2
            if ($trait->hasMethod($method)) {
898 2
                return true;
899
            }
900
        }
901
902
        return (
903 30
            isset($this->staticReflection[$metadata->name]) &&
904 30
            in_array(strtolower($method), $this->staticReflection[$metadata->name]['methods'])
905
        );
906
    }
907
908
    /**
909
     * @param ClassMetadataInfo $metadata
910
     *
911
     * @return array
912
     */
913 30
    protected function getTraits(ClassMetadataInfo $metadata)
914
    {
915 30
        if (! ($metadata->reflClass !== null || class_exists($metadata->name))) {
916 24
            return [];
917
        }
918
919 7
        $reflClass = $metadata->reflClass === null
920 1
            ? new \ReflectionClass($metadata->name)
921 7
            : $metadata->reflClass;
922
923 7
        $traits = array();
924
925 7
        while ($reflClass !== false) {
926 7
            $traits = array_merge($traits, $reflClass->getTraits());
927
928 7
            $reflClass = $reflClass->getParentClass();
929
        }
930
931 7
        return $traits;
932
    }
933
934
    /**
935
     * @param ClassMetadataInfo $metadata
936
     *
937
     * @return bool
938
     */
939 29
    protected function hasNamespace(ClassMetadataInfo $metadata)
940
    {
941 29
        return strpos($metadata->name, '\\') ? true : false;
942
    }
943
944
    /**
945
     * @return bool
946
     */
947 30
    protected function extendsClass()
948
    {
949 30
        return $this->classToExtend ? true : false;
950
    }
951
952
    /**
953
     * @return string
954
     */
955 2
    protected function getClassToExtend()
956
    {
957 2
        return $this->classToExtend;
958
    }
959
960
    /**
961
     * @return string
962
     */
963 1
    protected function getClassToExtendName()
964
    {
965 1
        $refl = new \ReflectionClass($this->getClassToExtend());
966
967 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...
968
    }
969
970
    /**
971
     * @param ClassMetadataInfo $metadata
972
     *
973
     * @return string
974
     */
975 30
    protected function getClassName(ClassMetadataInfo $metadata)
976
    {
977 30
        return ($pos = strrpos($metadata->name, '\\'))
978 30
            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
979
    }
980
981
    /**
982
     * @param ClassMetadataInfo $metadata
983
     *
984
     * @return string
985
     */
986 29
    protected function getNamespace(ClassMetadataInfo $metadata)
987
    {
988 29
        return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
989
    }
990
991
    /**
992
     * @param ClassMetadataInfo $metadata
993
     *
994
     * @return string
995
     */
996 29
    protected function generateEntityDocBlock(ClassMetadataInfo $metadata)
997
    {
998 29
        $lines = array();
999 29
        $lines[] = '/**';
1000 29
        $lines[] = ' * ' . $this->getClassName($metadata);
1001
1002 29
        if ($this->generateAnnotations) {
1003 29
            $lines[] = ' *';
1004
1005
            $methods = array(
1006 29
                'generateTableAnnotation',
1007
                'generateInheritanceAnnotation',
1008
                'generateDiscriminatorColumnAnnotation',
1009
                'generateDiscriminatorMapAnnotation',
1010
                'generateEntityAnnotation',
1011
            );
1012
1013 29
            foreach ($methods as $method) {
1014 29
                if ($code = $this->$method($metadata)) {
1015 29
                    $lines[] = ' * ' . $code;
1016
                }
1017
            }
1018
1019 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...
1020 10
                $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks';
1021
            }
1022
        }
1023
1024 29
        $lines[] = ' */';
1025
1026 29
        return implode("\n", $lines);
1027
    }
1028
1029
    /**
1030
     * @param ClassMetadataInfo $metadata
1031
     *
1032
     * @return string
1033
     */
1034 29
    protected function generateEntityAnnotation(ClassMetadataInfo $metadata)
1035
    {
1036 29
        $prefix = '@' . $this->annotationsPrefix;
1037
1038 29
        if ($metadata->isEmbeddedClass) {
1039 9
            return $prefix . 'Embeddable';
1040
        }
1041
1042 27
        $customRepository = $metadata->customRepositoryClassName
1043 11
            ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")'
1044 27
            : '';
1045
1046 27
        return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository;
1047
    }
1048
1049
    /**
1050
     * @param ClassMetadataInfo $metadata
1051
     *
1052
     * @return string
1053
     */
1054 29
    protected function generateTableAnnotation($metadata)
1055
    {
1056 29
        if ($metadata->isEmbeddedClass) {
1057 9
            return '';
1058
        }
1059
1060 27
        $table = array();
1061
1062 27
        if (isset($metadata->table['schema'])) {
1063
            $table[] = 'schema="' . $metadata->table['schema'] . '"';
1064
        }
1065
1066 27
        if (isset($metadata->table['name'])) {
1067 24
            $table[] = 'name="' . $metadata->table['name'] . '"';
1068
        }
1069
1070 27
        if (isset($metadata->table['options']) && $metadata->table['options']) {
1071 1
            $table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}';
1072
        }
1073
1074 27
        if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
1075 9
            $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
1076 9
            $table[] = 'uniqueConstraints={' . $constraints . '}';
1077
        }
1078
1079 27
        if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
1080 9
            $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']);
1081 9
            $table[] = 'indexes={' . $constraints . '}';
1082
        }
1083
1084 27
        return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
1085
    }
1086
1087
    /**
1088
     * @param string $constraintName
1089
     * @param array  $constraints
1090
     *
1091
     * @return string
1092
     */
1093 9
    protected function generateTableConstraints($constraintName, $constraints)
1094
    {
1095 9
        $annotations = array();
1096 9
        foreach ($constraints as $name => $constraint) {
1097 9
            $columns = array();
1098 9
            foreach ($constraint['columns'] as $column) {
1099 9
                $columns[] = '"' . $column . '"';
1100
            }
1101 9
            $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})';
1102
        }
1103
1104 9
        return implode(', ', $annotations);
1105
    }
1106
1107
    /**
1108
     * @param ClassMetadataInfo $metadata
1109
     *
1110
     * @return string
1111
     */
1112 29
    protected function generateInheritanceAnnotation($metadata)
1113
    {
1114 29
        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1115
            return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")';
1116
        }
1117 29
    }
1118
1119
    /**
1120
     * @param ClassMetadataInfo $metadata
1121
     *
1122
     * @return string
1123
     */
1124 29
    protected function generateDiscriminatorColumnAnnotation($metadata)
1125
    {
1126 29
        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1127
            $discrColumn = $metadata->discriminatorColumn;
1128
            $columnDefinition = 'name="' . $discrColumn['name']
1129
                . '", type="' . $discrColumn['type']
1130
                . '", length=' . $discrColumn['length'];
1131
1132
            return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
1133
        }
1134 29
    }
1135
1136
    /**
1137
     * @param ClassMetadataInfo $metadata
1138
     *
1139
     * @return string
1140
     */
1141 29
    protected function generateDiscriminatorMapAnnotation($metadata)
1142
    {
1143 29
        if ($metadata->inheritanceType != ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1144
            $inheritanceClassMap = array();
1145
1146
            foreach ($metadata->discriminatorMap as $type => $class) {
1147
                $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
1148
            }
1149
1150
            return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
1151
        }
1152 29
    }
1153
1154
    /**
1155
     * @param ClassMetadataInfo $metadata
1156
     *
1157
     * @return string
1158
     */
1159 29
    protected function generateEntityStubMethods(ClassMetadataInfo $metadata)
1160
    {
1161 29
        $methods = array();
1162
1163 29
        foreach ($metadata->fieldMappings as $fieldMapping) {
1164 28
            if (isset($fieldMapping['declaredField']) &&
1165 28
                isset($metadata->embeddedClasses[$fieldMapping['declaredField']])
1166
            ) {
1167
                continue;
1168
            }
1169
1170 28
            $nullableField = $this->getNullableField($fieldMapping);
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'], $nullableField)) {
1177 25
                    $methods[] = $code;
1178
                }
1179
            }
1180
1181 28
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)) {
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'], $nullable)) {
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($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.(null !== $defaultValue ? ('|'.$defaultValue) : ''),
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, $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']) . ($this->getNullableField($fieldMapping) ? '|' . $this->getNullableField($fieldMapping) : '');;
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
     * @param array $fieldMapping
1814
     *
1815
     * @return string|null
1816
     */
1817 29
    protected function getNullableField(array $fieldMapping)
1818
    {
1819 29
        if (isset($fieldMapping['nullable']) && true === $fieldMapping['nullable']) {
1820 5
            return 'null';
1821
        }
1822 29
    }
1823
1824
    /**
1825
     * Exports (nested) option elements.
1826
     *
1827
     * @param array $options
1828
     *
1829
     * @return string
1830
     */
1831 1
    private function exportTableOptions(array $options)
1832
    {
1833 1
        $optionsStr = array();
1834
1835 1
        foreach ($options as $name => $option) {
1836 1
            if (is_array($option)) {
1837 1
                $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}';
1838
            } else {
1839 1
                $optionsStr[] = '"' . $name . '"="' . (string) $option . '"';
1840
            }
1841
        }
1842
1843 1
        return implode(',', $optionsStr);
1844
    }
1845
}
1846