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

EntityGenerator::generateEmbeddableConstructor()   B

Complexity

Conditions 10
Paths 120

Size

Total Lines 85
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 49
CRAP Score 10.0008

Importance

Changes 0
Metric Value
cc 10
eloc 51
c 0
b 0
f 0
nop 1
dl 0
loc 85
ccs 49
cts 50
cp 0.98
crap 10.0008
rs 7.069
nc 120

How to fix   Long Method    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\Common\Collections\Collection;
23
use Doctrine\Common\Inflector\Inflector;
24
use Doctrine\DBAL\Types\Type;
25
use Doctrine\ORM\Mapping\ClassMetadataInfo;
26
use const E_USER_DEPRECATED;
27
use function str_replace;
28
use function trigger_error;
29
use function var_export;
30
31
/**
32
 * Generic class used to generate PHP5 entity classes from ClassMetadataInfo instances.
33
 *
34
 *     [php]
35
 *     $classes = $em->getClassMetadataFactory()->getAllMetadata();
36
 *
37
 *     $generator = new \Doctrine\ORM\Tools\EntityGenerator();
38
 *     $generator->setGenerateAnnotations(true);
39
 *     $generator->setGenerateStubMethods(true);
40
 *     $generator->setRegenerateEntityIfExists(false);
41
 *     $generator->setUpdateEntityIfExists(true);
42
 *     $generator->generate($classes, '/path/to/generate/entities');
43
 *
44
 *
45
 * @link    www.doctrine-project.org
46
 * @since   2.0
47
 * @author  Benjamin Eberlei <[email protected]>
48
 * @author  Guilherme Blanco <[email protected]>
49
 * @author  Jonathan Wage <[email protected]>
50
 * @author  Roman Borschel <[email protected]>
51
 *
52
 * @deprecated 2.7 This class is being removed from the ORM and won't have any replacement
53
 */
54
class EntityGenerator
55
{
56
    /**
57
     * Specifies class fields should be protected.
58
     */
59
    const FIELD_VISIBLE_PROTECTED = 'protected';
60
61
    /**
62
     * Specifies class fields should be private.
63
     */
64
    const FIELD_VISIBLE_PRIVATE = 'private';
65
66
    /**
67
     * @var bool
68
     */
69
    protected $backupExisting = true;
70
71
    /**
72
     * The extension to use for written php files.
73
     *
74
     * @var string
75
     */
76
    protected $extension = '.php';
77
78
    /**
79
     * Whether or not the current ClassMetadataInfo instance is new or old.
80
     *
81
     * @var boolean
82
     */
83
    protected $isNew = true;
84
85
    /**
86
     * @var array
87
     */
88
    protected $staticReflection = [];
89
90
    /**
91
     * Number of spaces to use for indention in generated code.
92
     */
93
    protected $numSpaces = 4;
94
95
    /**
96
     * The actual spaces to use for indention.
97
     *
98
     * @var string
99
     */
100
    protected $spaces = '    ';
101
102
    /**
103
     * The class all generated entities should extend.
104
     *
105
     * @var string
106
     */
107
    protected $classToExtend;
108
109
    /**
110
     * Whether or not to generation annotations.
111
     *
112
     * @var boolean
113
     */
114
    protected $generateAnnotations = false;
115
116
    /**
117
     * @var string
118
     */
119
    protected $annotationsPrefix = '';
120
121
    /**
122
     * Whether or not to generate sub methods.
123
     *
124
     * @var boolean
125
     */
126
    protected $generateEntityStubMethods = false;
127
128
    /**
129
     * Whether or not to update the entity class if it exists already.
130
     *
131
     * @var boolean
132
     */
133
    protected $updateEntityIfExists = false;
134
135
    /**
136
     * Whether or not to re-generate entity class if it exists already.
137
     *
138
     * @var boolean
139
     */
140
    protected $regenerateEntityIfExists = false;
141
142
    /**
143
     * Visibility of the field
144
     *
145
     * @var string
146
     */
147
    protected $fieldVisibility = 'private';
148
149
    /**
150
     * Whether or not to make generated embeddables immutable.
151
     *
152
     * @var boolean.
0 ignored issues
show
Documentation Bug introduced by
The doc comment boolean. at position 0 could not be parsed: Unknown type name 'boolean.' at position 0 in boolean..
Loading history...
153
     */
154
    protected $embeddablesImmutable = false;
155
156
    /**
157
     * Hash-map for handle types.
158
     *
159
     * @var array
160
     */
161
    protected $typeAlias = [
162
        Type::DATETIMETZ    => '\DateTime',
163
        Type::DATETIME      => '\DateTime',
164
        Type::DATE          => '\DateTime',
165
        Type::TIME          => '\DateTime',
166
        Type::OBJECT        => '\stdClass',
167
        Type::INTEGER       => 'int',
168
        Type::BIGINT        => 'int',
169
        Type::SMALLINT      => 'int',
170
        Type::TEXT          => 'string',
171
        Type::BLOB          => 'string',
172
        Type::DECIMAL       => 'string',
173
        Type::GUID          => 'string',
174
        Type::JSON_ARRAY    => 'array',
175
        Type::SIMPLE_ARRAY  => 'array',
176
        Type::BOOLEAN       => 'bool',
177
    ];
178
179
    /**
180
     * Hash-map to handle generator types string.
181
     *
182
     * @var array
183
     */
184
    protected static $generatorStrategyMap = [
185
        ClassMetadataInfo::GENERATOR_TYPE_AUTO      => 'AUTO',
186
        ClassMetadataInfo::GENERATOR_TYPE_SEQUENCE  => 'SEQUENCE',
187
        ClassMetadataInfo::GENERATOR_TYPE_TABLE     => 'TABLE',
188
        ClassMetadataInfo::GENERATOR_TYPE_IDENTITY  => 'IDENTITY',
189
        ClassMetadataInfo::GENERATOR_TYPE_NONE      => 'NONE',
190
        ClassMetadataInfo::GENERATOR_TYPE_UUID      => 'UUID',
191
        ClassMetadataInfo::GENERATOR_TYPE_CUSTOM    => 'CUSTOM'
192
    ];
193
194
    /**
195
     * Hash-map to handle the change tracking policy string.
196
     *
197
     * @var array
198
     */
199
    protected static $changeTrackingPolicyMap = [
200
        ClassMetadataInfo::CHANGETRACKING_DEFERRED_IMPLICIT  => 'DEFERRED_IMPLICIT',
201
        ClassMetadataInfo::CHANGETRACKING_DEFERRED_EXPLICIT  => 'DEFERRED_EXPLICIT',
202
        ClassMetadataInfo::CHANGETRACKING_NOTIFY             => 'NOTIFY',
203
    ];
204
205
    /**
206
     * Hash-map to handle the inheritance type string.
207
     *
208
     * @var array
209
     */
210
    protected static $inheritanceTypeMap = [
211
        ClassMetadataInfo::INHERITANCE_TYPE_NONE            => 'NONE',
212
        ClassMetadataInfo::INHERITANCE_TYPE_JOINED          => 'JOINED',
213
        ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_TABLE    => 'SINGLE_TABLE',
214
        ClassMetadataInfo::INHERITANCE_TYPE_TABLE_PER_CLASS => 'TABLE_PER_CLASS',
215
    ];
216
217
    /**
218
     * @var string
219
     */
220
    protected static $classTemplate =
221
'<?php
222
223
<namespace>
224
<useStatement>
225
<entityAnnotation>
226
<entityClassName>
227
{
228
<entityBody>
229
}
230
';
231
232
    /**
233
     * @var string
234
     */
235
    protected static $getMethodTemplate =
236
'/**
237
 * <description>
238
 *
239
 * @return <variableType>
240
 */
241
public function <methodName>()
242
{
243
<spaces>return $this-><fieldName>;
244
}';
245
246
    /**
247
     * @var string
248
     */
249
    protected static $setMethodTemplate =
250
'/**
251
 * <description>
252
 *
253
 * @param <variableType> $<variableName>
254
 *
255
 * @return <entity>
256
 */
257
public function <methodName>(<methodTypeHint>$<variableName><variableDefault>)
258
{
259
<spaces>$this-><fieldName> = $<variableName>;
260
261
<spaces>return $this;
262
}';
263
264
    /**
265
     * @var string
266
     */
267
    protected static $addMethodTemplate =
268
'/**
269
 * <description>
270
 *
271
 * @param <variableType> $<variableName>
272
 *
273
 * @return <entity>
274
 */
275
public function <methodName>(<methodTypeHint>$<variableName>)
276
{
277
<spaces>$this-><fieldName>[] = $<variableName>;
278
279
<spaces>return $this;
280
}';
281
282
    /**
283
     * @var string
284
     */
285
    protected static $removeMethodTemplate =
286
'/**
287
 * <description>
288
 *
289
 * @param <variableType> $<variableName>
290
 *
291
 * @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
292
 */
293
public function <methodName>(<methodTypeHint>$<variableName>)
294
{
295
<spaces>return $this-><fieldName>->removeElement($<variableName>);
296
}';
297
298
    /**
299
     * @var string
300
     */
301
    protected static $lifecycleCallbackMethodTemplate =
302
'/**
303
 * @<name>
304
 */
305
public function <methodName>()
306
{
307
<spaces>// Add your code here
308
}';
309
310
    /**
311
     * @var string
312
     */
313
    protected static $constructorMethodTemplate =
314
'/**
315
 * Constructor
316
 */
317
public function __construct()
318
{
319
<spaces><collections>
320
}
321
';
322
323
    /**
324
     * @var string
325
     */
326
    protected static $embeddableConstructorMethodTemplate =
327
'/**
328
 * Constructor
329
 *
330
 * <paramTags>
331
 */
332
public function __construct(<params>)
333
{
334
<spaces><fields>
335
}
336
';
337
338
    /**
339
     * Constructor.
340
     */
341 54
    public function __construct()
342
    {
343 54
        @trigger_error(self::class . ' is deprecated and will be removed in Doctrine ORM 3.0', E_USER_DEPRECATED);
344
345 54
        if (version_compare(\Doctrine\Common\Version::VERSION, '2.2.0-DEV', '>=')) {
346 54
            $this->annotationsPrefix = 'ORM\\';
347
        }
348 54
    }
349
350
    /**
351
     * Generates and writes entity classes for the given array of ClassMetadataInfo instances.
352
     *
353
     * @param array  $metadatas
354
     * @param string $outputDirectory
355
     *
356
     * @return void
357
     */
358
    public function generate(array $metadatas, $outputDirectory)
359
    {
360
        foreach ($metadatas as $metadata) {
361
            $this->writeEntityClass($metadata, $outputDirectory);
362
        }
363
    }
364
365
    /**
366
     * Generates and writes entity class to disk for the given ClassMetadataInfo instance.
367
     *
368
     * @param ClassMetadataInfo $metadata
369
     * @param string            $outputDirectory
370
     *
371
     * @return void
372
     *
373
     * @throws \RuntimeException
374
     */
375 45
    public function writeEntityClass(ClassMetadataInfo $metadata, $outputDirectory)
376
    {
377 45
        $path = $outputDirectory . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $metadata->name) . $this->extension;
378 45
        $dir = dirname($path);
379
380 45
        if ( ! is_dir($dir)) {
381 2
            mkdir($dir, 0775, true);
382
        }
383
384 45
        $this->isNew = ! file_exists($path) || $this->regenerateEntityIfExists;
385
386 45
        if ( ! $this->isNew) {
387 4
            $this->parseTokensInEntityFile(file_get_contents($path));
388
        } else {
389 44
            $this->staticReflection[$metadata->name] = ['properties' => [], 'methods' => []];
390
        }
391
392 45
        if ($this->backupExisting && file_exists($path)) {
393 4
            $backupPath = dirname($path) . DIRECTORY_SEPARATOR . basename($path) . "~";
394 4
            if (!copy($path, $backupPath)) {
395
                throw new \RuntimeException("Attempt to backup overwritten entity file but copy operation failed.");
396
            }
397
        }
398
399
        // If entity doesn't exist or we're re-generating the entities entirely
400 45
        if ($this->isNew) {
401 44
            file_put_contents($path, $this->generateEntityClass($metadata));
402
        // If entity exists and we're allowed to update the entity class
403 4
        } elseif ($this->updateEntityIfExists) {
404 4
            file_put_contents($path, $this->generateUpdatedEntityClass($metadata, $path));
405
        }
406 45
        chmod($path, 0664);
407 45
    }
408
409
    /**
410
     * Generates a PHP5 Doctrine 2 entity class from the given ClassMetadataInfo instance.
411
     *
412
     * @param ClassMetadataInfo $metadata
413
     *
414
     * @return string
415
     */
416 45
    public function generateEntityClass(ClassMetadataInfo $metadata)
417
    {
418
        $placeHolders = [
419 45
            '<namespace>',
420
            '<useStatement>',
421
            '<entityAnnotation>',
422
            '<entityClassName>',
423
            '<entityBody>'
424
        ];
425
426
        $replacements = [
427 45
            $this->generateEntityNamespace($metadata),
428 45
            $this->generateEntityUse(),
429 45
            $this->generateEntityDocBlock($metadata),
430 45
            $this->generateEntityClassName($metadata),
431 45
            $this->generateEntityBody($metadata)
432
        ];
433
434 45
        $code = str_replace($placeHolders, $replacements, static::$classTemplate);
435
436 45
        return str_replace('<spaces>', $this->spaces, $code);
437
    }
438
439
    /**
440
     * Generates the updated code for the given ClassMetadataInfo and entity at path.
441
     *
442
     * @param ClassMetadataInfo $metadata
443
     * @param string            $path
444
     *
445
     * @return string
446
     */
447 4
    public function generateUpdatedEntityClass(ClassMetadataInfo $metadata, $path)
448
    {
449 4
        $currentCode = file_get_contents($path);
450
451 4
        $body = $this->generateEntityBody($metadata);
452 4
        $body = str_replace('<spaces>', $this->spaces, $body);
453 4
        $last = strrpos($currentCode, '}');
454
455 4
        return substr($currentCode, 0, $last) . $body . ($body ? "\n" : '') . "}\n";
456
    }
457
458
    /**
459
     * Sets the number of spaces the exported class should have.
460
     *
461
     * @param integer $numSpaces
462
     *
463
     * @return void
464
     */
465
    public function setNumSpaces($numSpaces)
466
    {
467
        $this->spaces = str_repeat(' ', $numSpaces);
468
        $this->numSpaces = $numSpaces;
469
    }
470
471
    /**
472
     * Sets the extension to use when writing php files to disk.
473
     *
474
     * @param string $extension
475
     *
476
     * @return void
477
     */
478
    public function setExtension($extension)
479
    {
480
        $this->extension = $extension;
481
    }
482
483
    /**
484
     * Sets the name of the class the generated classes should extend from.
485
     *
486
     * @param string $classToExtend
487
     *
488
     * @return void
489
     */
490 1
    public function setClassToExtend($classToExtend)
491
    {
492 1
        $this->classToExtend = $classToExtend;
493 1
    }
494
495
    /**
496
     * Sets whether or not to generate annotations for the entity.
497
     *
498
     * @param bool $bool
499
     *
500
     * @return void
501
     */
502 54
    public function setGenerateAnnotations($bool)
503
    {
504 54
        $this->generateAnnotations = $bool;
505 54
    }
506
507
    /**
508
     * Sets the class fields visibility for the entity (can either be private or protected).
509
     *
510
     * @param bool $visibility
511
     *
512
     * @return void
513
     *
514
     * @throws \InvalidArgumentException
515
     */
516 53
    public function setFieldVisibility($visibility)
517
    {
518 53
        if ($visibility !== static::FIELD_VISIBLE_PRIVATE && $visibility !== static::FIELD_VISIBLE_PROTECTED) {
0 ignored issues
show
introduced by
The condition $visibility !== static::FIELD_VISIBLE_PROTECTED is always true.
Loading history...
519
            throw new \InvalidArgumentException('Invalid provided visibility (only private and protected are allowed): ' . $visibility);
520
        }
521
522 53
        $this->fieldVisibility = $visibility;
523 53
    }
524
525
    /**
526
     * Sets whether or not to generate immutable embeddables.
527
     *
528
     * @param boolean $embeddablesImmutable
529
     */
530 1
    public function setEmbeddablesImmutable($embeddablesImmutable)
531
    {
532 1
        $this->embeddablesImmutable = (boolean) $embeddablesImmutable;
533 1
    }
534
535
    /**
536
     * Sets an annotation prefix.
537
     *
538
     * @param string $prefix
539
     *
540
     * @return void
541
     */
542 54
    public function setAnnotationPrefix($prefix)
543
    {
544 54
        $this->annotationsPrefix = $prefix;
545 54
    }
546
547
    /**
548
     * Sets whether or not to try and update the entity if it already exists.
549
     *
550
     * @param bool $bool
551
     *
552
     * @return void
553
     */
554 54
    public function setUpdateEntityIfExists($bool)
555
    {
556 54
        $this->updateEntityIfExists = $bool;
557 54
    }
558
559
    /**
560
     * Sets whether or not to regenerate the entity if it exists.
561
     *
562
     * @param bool $bool
563
     *
564
     * @return void
565
     */
566 54
    public function setRegenerateEntityIfExists($bool)
567
    {
568 54
        $this->regenerateEntityIfExists = $bool;
569 54
    }
570
571
    /**
572
     * Sets whether or not to generate stub methods for the entity.
573
     *
574
     * @param bool $bool
575
     *
576
     * @return void
577
     */
578 54
    public function setGenerateStubMethods($bool)
579
    {
580 54
        $this->generateEntityStubMethods = $bool;
581 54
    }
582
583
    /**
584
     * Should an existing entity be backed up if it already exists?
585
     *
586
     * @param bool $bool
587
     *
588
     * @return void
589
     */
590 1
    public function setBackupExisting($bool)
591
    {
592 1
        $this->backupExisting = $bool;
593 1
    }
594
595
    /**
596
     * @param string $type
597
     *
598
     * @return string
599
     */
600 45
    protected function getType($type)
601
    {
602 45
        if (isset($this->typeAlias[$type])) {
603 44
            return $this->typeAlias[$type];
604
        }
605
606 28
        return $type;
607
    }
608
609
    /**
610
     * @param ClassMetadataInfo $metadata
611
     *
612
     * @return string
613
     */
614 45
    protected function generateEntityNamespace(ClassMetadataInfo $metadata)
615
    {
616 45
        if (! $this->hasNamespace($metadata)) {
617 2
            return '';
618
        }
619
620 45
        return 'namespace ' . $this->getNamespace($metadata) .';';
621
    }
622
623
    /**
624
     * @return string
625
     */
626 45
    protected function generateEntityUse()
627
    {
628 45
        if (! $this->generateAnnotations) {
629
            return '';
630
        }
631
632 45
        return "\n".'use Doctrine\ORM\Mapping as ORM;'."\n";
633
    }
634
635
    /**
636
     * @param ClassMetadataInfo $metadata
637
     *
638
     * @return string
639
     */
640 45
    protected function generateEntityClassName(ClassMetadataInfo $metadata)
641
    {
642 45
        return 'class ' . $this->getClassName($metadata) .
643 45
            ($this->extendsClass() ? ' extends ' . $this->getClassToExtendName() : null);
644
    }
645
646
    /**
647
     * @param ClassMetadataInfo $metadata
648
     *
649
     * @return string
650
     */
651 46
    protected function generateEntityBody(ClassMetadataInfo $metadata)
652
    {
653 46
        $fieldMappingProperties = $this->generateEntityFieldMappingProperties($metadata);
654 46
        $embeddedProperties = $this->generateEntityEmbeddedProperties($metadata);
655 46
        $associationMappingProperties = $this->generateEntityAssociationMappingProperties($metadata);
656 46
        $stubMethods = $this->generateEntityStubMethods ? $this->generateEntityStubMethods($metadata) : null;
657 46
        $lifecycleCallbackMethods = $this->generateEntityLifecycleCallbackMethods($metadata);
658
659 46
        $code = [];
660
661 46
        if ($fieldMappingProperties) {
662 43
            $code[] = $fieldMappingProperties;
663
        }
664
665 46
        if ($embeddedProperties) {
666 11
            $code[] = $embeddedProperties;
667
        }
668
669 46
        if ($associationMappingProperties) {
670 12
            $code[] = $associationMappingProperties;
671
        }
672
673 46
        $code[] = $this->generateEntityConstructor($metadata);
674
675 46
        if ($stubMethods) {
676 44
            $code[] = $stubMethods;
677
        }
678
679 46
        if ($lifecycleCallbackMethods) {
680 11
            $code[] = $lifecycleCallbackMethods;
681
        }
682
683 46
        return implode("\n", $code);
684
    }
685
686
    /**
687
     * @param ClassMetadataInfo $metadata
688
     *
689
     * @return string
690
     */
691 46
    protected function generateEntityConstructor(ClassMetadataInfo $metadata)
692
    {
693 46
        if ($this->hasMethod('__construct', $metadata)) {
694 3
            return '';
695
        }
696
697 46
        if ($metadata->isEmbeddedClass && $this->embeddablesImmutable) {
698 1
            return $this->generateEmbeddableConstructor($metadata);
699
        }
700
701 45
        $collections = [];
702
703 45
        foreach ($metadata->associationMappings as $mapping) {
704 14
            if ($mapping['type'] & ClassMetadataInfo::TO_MANY) {
705 14
                $collections[] = '$this->'.$mapping['fieldName'].' = new \Doctrine\Common\Collections\ArrayCollection();';
706
            }
707
        }
708
709 45
        if ($collections) {
710 12
            return $this->prefixCodeWithSpaces(str_replace("<collections>", implode("\n".$this->spaces, $collections), static::$constructorMethodTemplate));
711
        }
712
713 41
        return '';
714
    }
715
716
    /**
717
     * @param ClassMetadataInfo $metadata
718
     *
719
     * @return string
720
     */
721 1
    private function generateEmbeddableConstructor(ClassMetadataInfo $metadata)
722
    {
723 1
        $paramTypes = [];
724 1
        $paramVariables = [];
725 1
        $params = [];
726 1
        $fields = [];
727
728
        // Resort fields to put optional fields at the end of the method signature.
729 1
        $requiredFields = [];
730 1
        $optionalFields = [];
731
732 1
        foreach ($metadata->fieldMappings as $fieldMapping) {
733 1
            if (empty($fieldMapping['nullable'])) {
734 1
                $requiredFields[] = $fieldMapping;
735
736 1
                continue;
737
            }
738
739 1
            $optionalFields[] = $fieldMapping;
740
        }
741
742 1
        $fieldMappings = array_merge($requiredFields, $optionalFields);
743
744 1
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
745 1
            $paramType = '\\' . ltrim($embeddedClass['class'], '\\');
746 1
            $paramVariable = '$' . $fieldName;
747
748 1
            $paramTypes[] = $paramType;
749 1
            $paramVariables[] = $paramVariable;
750 1
            $params[] = $paramType . ' ' . $paramVariable;
751 1
            $fields[] = '$this->' . $fieldName . ' = ' . $paramVariable . ';';
752
        }
753
754 1
        foreach ($fieldMappings as $fieldMapping) {
755 1
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) {
756
                continue;
757
            }
758
759 1
            $paramTypes[] = $this->getType($fieldMapping['type']) . (!empty($fieldMapping['nullable']) ? '|null' : '');
760 1
            $param = '$' . $fieldMapping['fieldName'];
761 1
            $paramVariables[] = $param;
762
763 1
            if ($fieldMapping['type'] === 'datetime') {
764 1
                $param = $this->getType($fieldMapping['type']) . ' ' . $param;
765
            }
766
767 1
            if (!empty($fieldMapping['nullable'])) {
768 1
                $param .= ' = null';
769
            }
770
771 1
            $params[] = $param;
772
773 1
            $fields[] = '$this->' . $fieldMapping['fieldName'] . ' = $' . $fieldMapping['fieldName'] . ';';
774
        }
775
776 1
        $maxParamTypeLength = max(array_map('strlen', $paramTypes));
777 1
        $paramTags = array_map(
778
            function ($type, $variable) use ($maxParamTypeLength) {
779 1
                return '@param ' . $type . str_repeat(' ', $maxParamTypeLength - strlen($type) + 1) . $variable;
780 1
            },
781 1
            $paramTypes,
782 1
            $paramVariables
783
        );
784
785
        // Generate multi line constructor if the signature exceeds 120 characters.
786 1
        if (array_sum(array_map('strlen', $params)) + count($params) * 2 + 29 > 120) {
787 1
            $delimiter = "\n" . $this->spaces;
788 1
            $params = $delimiter . implode(',' . $delimiter, $params) . "\n";
789
        } else {
790 1
            $params = implode(', ', $params);
791
        }
792
793
        $replacements = [
794 1
            '<paramTags>' => implode("\n * ", $paramTags),
795 1
            '<params>'    => $params,
796 1
            '<fields>'    => implode("\n" . $this->spaces, $fields),
797
        ];
798
799 1
        $constructor = str_replace(
800 1
            array_keys($replacements),
801 1
            array_values($replacements),
802 1
            static::$embeddableConstructorMethodTemplate
803
        );
804
805 1
        return $this->prefixCodeWithSpaces($constructor);
806
    }
807
808
    /**
809
     * @todo this won't work if there is a namespace in brackets and a class outside of it.
810
     *
811
     * @param string $src
812
     *
813
     * @return void
814
     */
815 9
    protected function parseTokensInEntityFile($src)
816
    {
817 9
        $tokens = token_get_all($src);
818 9
        $tokensCount = count($tokens);
819 9
        $lastSeenNamespace = '';
820 9
        $lastSeenClass = false;
821
822 9
        $inNamespace = false;
823 9
        $inClass = false;
824
825 9
        for ($i = 0; $i < $tokensCount; $i++) {
826 9
            $token = $tokens[$i];
827 9
            if (in_array($token[0], [T_WHITESPACE, T_COMMENT, T_DOC_COMMENT], true)) {
828 9
                continue;
829
            }
830
831 9
            if ($inNamespace) {
832 9
                if (in_array($token[0], [T_NS_SEPARATOR, T_STRING], true)) {
833 9
                    $lastSeenNamespace .= $token[1];
834 9
                } elseif (is_string($token) && in_array($token, [';', '{'], true)) {
835 9
                    $inNamespace = false;
836
                }
837
            }
838
839 9
            if ($inClass) {
840 9
                $inClass = false;
841 9
                $lastSeenClass = $lastSeenNamespace . ($lastSeenNamespace ? '\\' : '') . $token[1];
842 9
                $this->staticReflection[$lastSeenClass]['properties'] = [];
843 9
                $this->staticReflection[$lastSeenClass]['methods'] = [];
844
            }
845
846 9
            if (T_NAMESPACE === $token[0]) {
847 9
                $lastSeenNamespace = '';
848 9
                $inNamespace = true;
849 9
            } elseif (T_CLASS === $token[0] && T_DOUBLE_COLON !== $tokens[$i-1][0]) {
850 9
                $inClass = true;
851 9
            } elseif (T_FUNCTION === $token[0]) {
852 4
                if (T_STRING === $tokens[$i+2][0]) {
853 4
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+2][1]);
854
                } elseif ($tokens[$i+2] == '&' && T_STRING === $tokens[$i+3][0]) {
855 4
                    $this->staticReflection[$lastSeenClass]['methods'][] = strtolower($tokens[$i+3][1]);
856
                }
857 9
            } elseif (in_array($token[0], [T_VAR, T_PUBLIC, T_PRIVATE, T_PROTECTED], true) && T_FUNCTION !== $tokens[$i+2][0]) {
858 5
                $this->staticReflection[$lastSeenClass]['properties'][] = substr($tokens[$i+2][1], 1);
859
            }
860
        }
861 9
    }
862
863
    /**
864
     * @param string            $property
865
     * @param ClassMetadataInfo $metadata
866
     *
867
     * @return bool
868
     */
869 45
    protected function hasProperty($property, ClassMetadataInfo $metadata)
870
    {
871 45
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
872
            // don't generate property if its already on the base class.
873 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
874 2
            if ($reflClass->hasProperty($property)) {
875 1
                return true;
876
            }
877
        }
878
879
        // check traits for existing property
880 44
        foreach ($this->getTraits($metadata) as $trait) {
881 2
            if ($trait->hasProperty($property)) {
882 2
                return true;
883
            }
884
        }
885
886
        return (
887 44
            isset($this->staticReflection[$metadata->name]) &&
888 44
            in_array($property, $this->staticReflection[$metadata->name]['properties'], true)
889
        );
890
    }
891
892
    /**
893
     * @param string            $method
894
     * @param ClassMetadataInfo $metadata
895
     *
896
     * @return bool
897
     */
898 46
    protected function hasMethod($method, ClassMetadataInfo $metadata)
899
    {
900 46
        if ($this->extendsClass() || (!$this->isNew && class_exists($metadata->name))) {
901
            // don't generate method if its already on the base class.
902 2
            $reflClass = new \ReflectionClass($this->getClassToExtend() ?: $metadata->name);
903
904 2
            if ($reflClass->hasMethod($method)) {
905 1
                return true;
906
            }
907
        }
908
909
        // check traits for existing method
910 46
        foreach ($this->getTraits($metadata) as $trait) {
911 2
            if ($trait->hasMethod($method)) {
912 2
                return true;
913
            }
914
        }
915
916
        return (
917 46
            isset($this->staticReflection[$metadata->name]) &&
918 46
            in_array(strtolower($method), $this->staticReflection[$metadata->name]['methods'], true)
919
        );
920
    }
921
922
    /**
923
     * @param ClassMetadataInfo $metadata
924
     *
925
     * @return array
926
     *
927
     * @throws \ReflectionException
928
     */
929 46
    protected function getTraits(ClassMetadataInfo $metadata)
930
    {
931 46
        if (! ($metadata->reflClass !== null || class_exists($metadata->name))) {
932 40
            return [];
933
        }
934
935 7
        $reflClass = $metadata->reflClass ?? new \ReflectionClass($metadata->name);
936
937 7
        $traits = [];
938
939 7
        while ($reflClass !== false) {
940 7
            $traits = array_merge($traits, $reflClass->getTraits());
941
942 7
            $reflClass = $reflClass->getParentClass();
943
        }
944
945 7
        return $traits;
946
    }
947
948
    /**
949
     * @param ClassMetadataInfo $metadata
950
     *
951
     * @return bool
952
     */
953 45
    protected function hasNamespace(ClassMetadataInfo $metadata)
954
    {
955 45
        return (bool) strpos($metadata->name, '\\');
956
    }
957
958
    /**
959
     * @return bool
960
     */
961 46
    protected function extendsClass()
962
    {
963 46
        return (bool) $this->classToExtend;
964
    }
965
966
    /**
967
     * @return string
968
     */
969 2
    protected function getClassToExtend()
970
    {
971 2
        return $this->classToExtend;
972
    }
973
974
    /**
975
     * @return string
976
     */
977 1
    protected function getClassToExtendName()
978
    {
979 1
        $refl = new \ReflectionClass($this->getClassToExtend());
980
981 1
        return '\\' . $refl->getName();
982
    }
983
984
    /**
985
     * @param ClassMetadataInfo $metadata
986
     *
987
     * @return string
988
     */
989 46
    protected function getClassName(ClassMetadataInfo $metadata)
990
    {
991 46
        return ($pos = strrpos($metadata->name, '\\'))
992 46
            ? substr($metadata->name, $pos + 1, strlen($metadata->name)) : $metadata->name;
993
    }
994
995
    /**
996
     * @param ClassMetadataInfo $metadata
997
     *
998
     * @return string
999
     */
1000 45
    protected function getNamespace(ClassMetadataInfo $metadata)
1001
    {
1002 45
        return substr($metadata->name, 0, strrpos($metadata->name, '\\'));
1003
    }
1004
1005
    /**
1006
     * @param ClassMetadataInfo $metadata
1007
     *
1008
     * @return string
1009
     */
1010 45
    protected function generateEntityDocBlock(ClassMetadataInfo $metadata)
1011
    {
1012 45
        $lines = [];
1013 45
        $lines[] = '/**';
1014 45
        $lines[] = ' * ' . $this->getClassName($metadata);
1015
1016 45
        if ($this->generateAnnotations) {
1017 45
            $lines[] = ' *';
1018
1019
            $methods = [
1020 45
                'generateTableAnnotation',
1021
                'generateInheritanceAnnotation',
1022
                'generateDiscriminatorColumnAnnotation',
1023
                'generateDiscriminatorMapAnnotation',
1024
                'generateEntityAnnotation',
1025
                'generateEntityListenerAnnotation',
1026
            ];
1027
1028 45
            foreach ($methods as $method) {
1029 45
                if ($code = $this->$method($metadata)) {
1030 45
                    $lines[] = ' * ' . $code;
1031
                }
1032
            }
1033
1034 45
            if (isset($metadata->lifecycleCallbacks) && $metadata->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata->lifecycleCallbacks of type array<mixed,array> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1035 11
                $lines[] = ' * @' . $this->annotationsPrefix . 'HasLifecycleCallbacks';
1036
            }
1037
        }
1038
1039 45
        $lines[] = ' */';
1040
1041 45
        return implode("\n", $lines);
1042
    }
1043
1044
    /**
1045
     * @param ClassMetadataInfo $metadata
1046
     *
1047
     * @return string
1048
     */
1049 45
    protected function generateEntityAnnotation(ClassMetadataInfo $metadata)
1050
    {
1051 45
        $prefix = '@' . $this->annotationsPrefix;
1052
1053 45
        if ($metadata->isEmbeddedClass) {
1054 12
            return $prefix . 'Embeddable';
1055
        }
1056
1057 41
        $customRepository = $metadata->customRepositoryClassName
1058 12
            ? '(repositoryClass="' . $metadata->customRepositoryClassName . '")'
1059 41
            : '';
1060
1061 41
        return $prefix . ($metadata->isMappedSuperclass ? 'MappedSuperclass' : 'Entity') . $customRepository;
1062
    }
1063
1064
    /**
1065
     * @param ClassMetadataInfo $metadata
1066
     *
1067
     * @return string
1068
     */
1069 45
    protected function generateTableAnnotation(ClassMetadataInfo $metadata)
1070
    {
1071 45
        if ($metadata->isEmbeddedClass) {
1072 12
            return '';
1073
        }
1074
1075 41
        $table = [];
1076
1077 41
        if (isset($metadata->table['schema'])) {
1078
            $table[] = 'schema="' . $metadata->table['schema'] . '"';
1079
        }
1080
1081 41
        if (isset($metadata->table['name'])) {
1082 26
            $table[] = 'name="' . $metadata->table['name'] . '"';
1083
        }
1084
1085 41
        if (isset($metadata->table['options']) && $metadata->table['options']) {
1086 1
            $table[] = 'options={' . $this->exportTableOptions((array) $metadata->table['options']) . '}';
1087
        }
1088
1089 41
        if (isset($metadata->table['uniqueConstraints']) && $metadata->table['uniqueConstraints']) {
1090 10
            $constraints = $this->generateTableConstraints('UniqueConstraint', $metadata->table['uniqueConstraints']);
1091 10
            $table[] = 'uniqueConstraints={' . $constraints . '}';
1092
        }
1093
1094 41
        if (isset($metadata->table['indexes']) && $metadata->table['indexes']) {
1095 10
            $constraints = $this->generateTableConstraints('Index', $metadata->table['indexes']);
1096 10
            $table[] = 'indexes={' . $constraints . '}';
1097
        }
1098
1099 41
        return '@' . $this->annotationsPrefix . 'Table(' . implode(', ', $table) . ')';
1100
    }
1101
1102
    /**
1103
     * @param string $constraintName
1104
     * @param array  $constraints
1105
     *
1106
     * @return string
1107
     */
1108 10
    protected function generateTableConstraints($constraintName, array $constraints)
1109
    {
1110 10
        $annotations = [];
1111 10
        foreach ($constraints as $name => $constraint) {
1112 10
            $columns = [];
1113 10
            foreach ($constraint['columns'] as $column) {
1114 10
                $columns[] = '"' . $column . '"';
1115
            }
1116 10
            $annotations[] = '@' . $this->annotationsPrefix . $constraintName . '(name="' . $name . '", columns={' . implode(', ', $columns) . '})';
1117
        }
1118
1119 10
        return implode(', ', $annotations);
1120
    }
1121
1122
    /**
1123
     * @param ClassMetadataInfo $metadata
1124
     *
1125
     * @return string
1126
     */
1127 45
    protected function generateInheritanceAnnotation(ClassMetadataInfo $metadata)
1128
    {
1129 45
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1130 45
            return '';
1131
        }
1132
1133
        return '@' . $this->annotationsPrefix . 'InheritanceType("'.$this->getInheritanceTypeString($metadata->inheritanceType).'")';
1134
    }
1135
1136
    /**
1137
     * @param ClassMetadataInfo $metadata
1138
     *
1139
     * @return string
1140
     */
1141 45
    protected function generateDiscriminatorColumnAnnotation(ClassMetadataInfo $metadata)
1142
    {
1143 45
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1144 45
            return '';
1145
        }
1146
1147
        $discrColumn = $metadata->discriminatorColumn;
1148
        $columnDefinition = 'name="' . $discrColumn['name']
1149
            . '", type="' . $discrColumn['type']
1150
            . '", length=' . $discrColumn['length'];
1151
1152
        return '@' . $this->annotationsPrefix . 'DiscriminatorColumn(' . $columnDefinition . ')';
1153
    }
1154
1155
    /**
1156
     * @param ClassMetadataInfo $metadata
1157
     *
1158
     * @return string
1159
     */
1160 45
    protected function generateDiscriminatorMapAnnotation(ClassMetadataInfo $metadata)
1161
    {
1162 45
        if ($metadata->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_NONE) {
1163 45
            return null;
1164
        }
1165
1166
        $inheritanceClassMap = [];
1167
1168
        foreach ($metadata->discriminatorMap as $type => $class) {
1169
            $inheritanceClassMap[] .= '"' . $type . '" = "' . $class . '"';
1170
        }
1171
1172
        return '@' . $this->annotationsPrefix . 'DiscriminatorMap({' . implode(', ', $inheritanceClassMap) . '})';
1173
    }
1174
1175
    /**
1176
     * @param ClassMetadataInfo $metadata
1177
     *
1178
     * @return string
1179
     */
1180 45
    protected function generateEntityStubMethods(ClassMetadataInfo $metadata)
1181
    {
1182 45
        $methods = [];
1183
1184 45
        foreach ($metadata->fieldMappings as $fieldMapping) {
1185 44
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']])) {
1186
                continue;
1187
            }
1188
1189 44
            $nullableField = $this->nullableFieldExpression($fieldMapping);
1190
1191 44
            if ((!$metadata->isEmbeddedClass || !$this->embeddablesImmutable)
1192 44
                && (!isset($fieldMapping['id']) || ! $fieldMapping['id'] || $metadata->generatorType === ClassMetadataInfo::GENERATOR_TYPE_NONE)
1193 44
                && $code = $this->generateEntityStubMethod($metadata, 'set', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)
1194
            ) {
1195 41
                $methods[] = $code;
1196
            }
1197
1198 44
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldMapping['fieldName'], $fieldMapping['type'], $nullableField)) {
1199 44
                $methods[] = $code;
1200
            }
1201
        }
1202
1203 45
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1204 11
            if (isset($embeddedClass['declaredField'])) {
1205 1
                continue;
1206
            }
1207
1208 11
            if ( ! $metadata->isEmbeddedClass || ! $this->embeddablesImmutable) {
1209 10
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $fieldName, $embeddedClass['class'])) {
1210 10
                    $methods[] = $code;
1211
                }
1212
            }
1213
1214 11
            if ($code = $this->generateEntityStubMethod($metadata, 'get', $fieldName, $embeddedClass['class'])) {
1215 11
                $methods[] = $code;
1216
            }
1217
        }
1218
1219 45
        foreach ($metadata->associationMappings as $associationMapping) {
1220 13
            if ($associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
1221 12
                $nullable = $this->isAssociationIsNullable($associationMapping) ? 'null' : null;
1222 12
                if ($code = $this->generateEntityStubMethod($metadata, 'set', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1223 10
                    $methods[] = $code;
1224
                }
1225 12
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], $associationMapping['targetEntity'], $nullable)) {
1226 12
                    $methods[] = $code;
1227
                }
1228 11
            } elseif ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1229 11
                if ($code = $this->generateEntityStubMethod($metadata, 'add', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1230 11
                    $methods[] = $code;
1231
                }
1232 11
                if ($code = $this->generateEntityStubMethod($metadata, 'remove', $associationMapping['fieldName'], $associationMapping['targetEntity'])) {
1233 11
                    $methods[] = $code;
1234
                }
1235 11
                if ($code = $this->generateEntityStubMethod($metadata, 'get', $associationMapping['fieldName'], Collection::class)) {
1236 13
                    $methods[] = $code;
1237
                }
1238
            }
1239
        }
1240
1241 45
        return implode("\n\n", $methods);
1242
    }
1243
1244
    /**
1245
     * @param array $associationMapping
1246
     *
1247
     * @return bool
1248
     */
1249 12
    protected function isAssociationIsNullable(array $associationMapping)
1250
    {
1251 12
        if (isset($associationMapping['id']) && $associationMapping['id']) {
1252
            return false;
1253
        }
1254
1255 12
        if (isset($associationMapping['joinColumns'])) {
1256 2
            $joinColumns = $associationMapping['joinColumns'];
1257
        } else {
1258
            //@todo there is no way to retrieve targetEntity metadata
1259 10
            $joinColumns = [];
1260
        }
1261
1262 12
        foreach ($joinColumns as $joinColumn) {
1263 2
            if (isset($joinColumn['nullable']) && !$joinColumn['nullable']) {
1264 2
                return false;
1265
            }
1266
        }
1267
1268 12
        return true;
1269
    }
1270
1271
    /**
1272
     * @param ClassMetadataInfo $metadata
1273
     *
1274
     * @return string
1275
     */
1276 46
    protected function generateEntityLifecycleCallbackMethods(ClassMetadataInfo $metadata)
1277
    {
1278 46
        if (empty($metadata->lifecycleCallbacks)) {
1279 43
            return '';
1280
        }
1281
1282 11
        $methods = [];
1283
1284 11
        foreach ($metadata->lifecycleCallbacks as $name => $callbacks) {
1285 11
            foreach ($callbacks as $callback) {
1286 11
                $methods[] = $this->generateLifecycleCallbackMethod($name, $callback, $metadata);
1287
            }
1288
        }
1289
1290 11
        return implode("\n\n", array_filter($methods));
1291
    }
1292
1293
    /**
1294
     * @param ClassMetadataInfo $metadata
1295
     *
1296
     * @return string
1297
     */
1298 46
    protected function generateEntityAssociationMappingProperties(ClassMetadataInfo $metadata)
1299
    {
1300 46
        $lines = [];
1301
1302 46
        foreach ($metadata->associationMappings as $associationMapping) {
1303 14
            if ($this->hasProperty($associationMapping['fieldName'], $metadata)) {
1304 5
                continue;
1305
            }
1306
1307 12
            $lines[] = $this->generateAssociationMappingPropertyDocBlock($associationMapping, $metadata);
1308 12
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $associationMapping['fieldName']
1309 12
                     . ($associationMapping['type'] == 'manyToMany' ? ' = array()' : null) . ";\n";
1310
        }
1311
1312 46
        return implode("\n", $lines);
1313
    }
1314
1315
    /**
1316
     * @param ClassMetadataInfo $metadata
1317
     *
1318
     * @return string
1319
     */
1320 46
    protected function generateEntityFieldMappingProperties(ClassMetadataInfo $metadata)
1321
    {
1322 46
        $lines = [];
1323
1324 46
        foreach ($metadata->fieldMappings as $fieldMapping) {
1325 45
            if (isset($fieldMapping['declaredField'], $metadata->embeddedClasses[$fieldMapping['declaredField']]) ||
1326 45
                $this->hasProperty($fieldMapping['fieldName'], $metadata) ||
1327 45
                $metadata->isInheritedField($fieldMapping['fieldName'])
1328
            ) {
1329 5
                continue;
1330
            }
1331
1332 43
            $defaultValue = '';
1333 43
            if (isset($fieldMapping['options']['default'])) {
1334 14
                if ($fieldMapping['type'] === 'boolean' && $fieldMapping['options']['default'] === '1') {
1335 1
                    $defaultValue = ' = true';
1336
                } else {
1337 14
                    $defaultValue = ' = ' . var_export($fieldMapping['options']['default'], true);
1338
                }
1339
            }
1340
1341 43
            $lines[] = $this->generateFieldMappingPropertyDocBlock($fieldMapping, $metadata);
1342 43
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldMapping['fieldName'] . $defaultValue . ";\n";
1343
        }
1344
1345 46
        return implode("\n", $lines);
1346
    }
1347
1348
    /**
1349
     * @param ClassMetadataInfo $metadata
1350
     *
1351
     * @return string
1352
     */
1353 46
    protected function generateEntityEmbeddedProperties(ClassMetadataInfo $metadata)
1354
    {
1355 46
        $lines = [];
1356
1357 46
        foreach ($metadata->embeddedClasses as $fieldName => $embeddedClass) {
1358 11
            if (isset($embeddedClass['declaredField']) || $this->hasProperty($fieldName, $metadata)) {
1359 3
                continue;
1360
            }
1361
1362 11
            $lines[] = $this->generateEmbeddedPropertyDocBlock($embeddedClass);
1363 11
            $lines[] = $this->spaces . $this->fieldVisibility . ' $' . $fieldName . ";\n";
1364
        }
1365
1366 46
        return implode("\n", $lines);
1367
    }
1368
1369
    /**
1370
     * @param ClassMetadataInfo $metadata
1371
     * @param string            $type
1372
     * @param string            $fieldName
1373
     * @param string|null       $typeHint
1374
     * @param string|null       $defaultValue
1375
     *
1376
     * @return string
1377
     */
1378 44
    protected function generateEntityStubMethod(ClassMetadataInfo $metadata, $type, $fieldName, $typeHint = null, $defaultValue = null)
1379
    {
1380 44
        $methodName = $type . Inflector::classify($fieldName);
1381 44
        $variableName = Inflector::camelize($fieldName);
1382 44
        if (in_array($type, ["add", "remove"])) {
1383 11
            $methodName = Inflector::singularize($methodName);
1384 11
            $variableName = Inflector::singularize($variableName);
1385
        }
1386
1387 44
        if ($this->hasMethod($methodName, $metadata)) {
1388 6
            return '';
1389
        }
1390 44
        $this->staticReflection[$metadata->name]['methods'][] = strtolower($methodName);
1391
1392 44
        $var = sprintf('%sMethodTemplate', $type);
1393 44
        $template = static::$$var;
1394
1395 44
        $methodTypeHint = null;
1396 44
        $types          = Type::getTypesMap();
1397 44
        $variableType   = $typeHint ? $this->getType($typeHint) : null;
1398
1399 44
        if ($typeHint && ! isset($types[$typeHint])) {
1400 15
            $variableType   =  '\\' . ltrim($variableType, '\\');
1401 15
            $methodTypeHint =  '\\' . $typeHint . ' ';
1402
        }
1403
1404
        $replacements = [
1405 44
          '<description>'       => ucfirst($type) . ' ' . $variableName . '.',
1406 44
          '<methodTypeHint>'    => $methodTypeHint,
1407 44
          '<variableType>'      => $variableType . (null !== $defaultValue ? ('|' . $defaultValue) : ''),
1408 44
          '<variableName>'      => $variableName,
1409 44
          '<methodName>'        => $methodName,
1410 44
          '<fieldName>'         => $fieldName,
1411 44
          '<variableDefault>'   => ($defaultValue !== null ) ? (' = ' . $defaultValue) : '',
1412 44
          '<entity>'            => $this->getClassName($metadata)
1413
        ];
1414
1415 44
        $method = str_replace(
1416 44
            array_keys($replacements),
1417 44
            array_values($replacements),
1418 44
            $template
1419
        );
1420
1421 44
        return $this->prefixCodeWithSpaces($method);
1422
    }
1423
1424
    /**
1425
     * @param string            $name
1426
     * @param string            $methodName
1427
     * @param ClassMetadataInfo $metadata
1428
     *
1429
     * @return string
1430
     */
1431 11
    protected function generateLifecycleCallbackMethod($name, $methodName, ClassMetadataInfo $metadata)
1432
    {
1433 11
        if ($this->hasMethod($methodName, $metadata)) {
1434 3
            return '';
1435
        }
1436
1437 11
        $this->staticReflection[$metadata->name]['methods'][] = $methodName;
1438
1439
        $replacements = [
1440 11
            '<name>'        => $this->annotationsPrefix . ucfirst($name),
1441 11
            '<methodName>'  => $methodName,
1442
        ];
1443
1444 11
        $method = str_replace(
1445 11
            array_keys($replacements),
1446 11
            array_values($replacements),
1447 11
            static::$lifecycleCallbackMethodTemplate
1448
        );
1449
1450 11
        return $this->prefixCodeWithSpaces($method);
1451
    }
1452
1453
    /**
1454
     * @param array $joinColumn
1455
     *
1456
     * @return string
1457
     */
1458 12
    protected function generateJoinColumnAnnotation(array $joinColumn)
1459
    {
1460 12
        $joinColumnAnnot = [];
1461
1462 12
        if (isset($joinColumn['name'])) {
1463 12
            $joinColumnAnnot[] = 'name="' . $joinColumn['name'] . '"';
1464
        }
1465
1466 12
        if (isset($joinColumn['referencedColumnName'])) {
1467 12
            $joinColumnAnnot[] = 'referencedColumnName="' . $joinColumn['referencedColumnName'] . '"';
1468
        }
1469
1470 12
        if (isset($joinColumn['unique']) && $joinColumn['unique']) {
1471 1
            $joinColumnAnnot[] = 'unique=' . ($joinColumn['unique'] ? 'true' : 'false');
1472
        }
1473
1474 12
        if (isset($joinColumn['nullable'])) {
1475 1
            $joinColumnAnnot[] = 'nullable=' . ($joinColumn['nullable'] ? 'true' : 'false');
1476
        }
1477
1478 12
        if (isset($joinColumn['onDelete'])) {
1479 1
            $joinColumnAnnot[] = 'onDelete="' . ($joinColumn['onDelete'] . '"');
1480
        }
1481
1482 12
        if (isset($joinColumn['columnDefinition'])) {
1483 1
            $joinColumnAnnot[] = 'columnDefinition="' . $joinColumn['columnDefinition'] . '"';
1484
        }
1485
1486 12
        return '@' . $this->annotationsPrefix . 'JoinColumn(' . implode(', ', $joinColumnAnnot) . ')';
1487
    }
1488
1489
    /**
1490
     * @param array             $associationMapping
1491
     * @param ClassMetadataInfo $metadata
1492
     *
1493
     * @return string
1494
     */
1495 12
    protected function generateAssociationMappingPropertyDocBlock(array $associationMapping, ClassMetadataInfo $metadata)
1496
    {
1497 12
        $lines = [];
1498 12
        $lines[] = $this->spaces . '/**';
1499
1500 12
        if ($associationMapping['type'] & ClassMetadataInfo::TO_MANY) {
1501 12
            $lines[] = $this->spaces . ' * @var \Doctrine\Common\Collections\Collection';
1502
        } else {
1503 11
            $lines[] = $this->spaces . ' * @var \\' . ltrim($associationMapping['targetEntity'], '\\');
1504
        }
1505
1506 12
        if ($this->generateAnnotations) {
1507 12
            $lines[] = $this->spaces . ' *';
1508
1509 12
            if (isset($associationMapping['id']) && $associationMapping['id']) {
1510
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1511
1512
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1513
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1514
                }
1515
            }
1516
1517 12
            $type = null;
1518 12
            switch ($associationMapping['type']) {
1519 12
                case ClassMetadataInfo::ONE_TO_ONE:
1520 11
                    $type = 'OneToOne';
1521 11
                    break;
1522 12
                case ClassMetadataInfo::MANY_TO_ONE:
1523 1
                    $type = 'ManyToOne';
1524 1
                    break;
1525 12
                case ClassMetadataInfo::ONE_TO_MANY:
1526 1
                    $type = 'OneToMany';
1527 1
                    break;
1528 12
                case ClassMetadataInfo::MANY_TO_MANY:
1529 12
                    $type = 'ManyToMany';
1530 12
                    break;
1531
            }
1532 12
            $typeOptions = [];
1533
1534 12
            if (isset($associationMapping['targetEntity'])) {
1535 12
                $typeOptions[] = 'targetEntity="' . $associationMapping['targetEntity'] . '"';
1536
            }
1537
1538 12
            if (isset($associationMapping['inversedBy'])) {
1539 1
                $typeOptions[] = 'inversedBy="' . $associationMapping['inversedBy'] . '"';
1540
            }
1541
1542 12
            if (isset($associationMapping['mappedBy'])) {
1543 11
                $typeOptions[] = 'mappedBy="' . $associationMapping['mappedBy'] . '"';
1544
            }
1545
1546 12
            if ($associationMapping['cascade']) {
1547 1
                $cascades = [];
1548
1549 1
                if ($associationMapping['isCascadePersist']) $cascades[] = '"persist"';
1550 1
                if ($associationMapping['isCascadeRemove']) $cascades[] = '"remove"';
1551 1
                if ($associationMapping['isCascadeDetach']) $cascades[] = '"detach"';
1552 1
                if ($associationMapping['isCascadeMerge']) $cascades[] = '"merge"';
1553 1
                if ($associationMapping['isCascadeRefresh']) $cascades[] = '"refresh"';
1554
1555 1
                if (count($cascades) === 5) {
1556 1
                    $cascades = ['"all"'];
1557
                }
1558
1559 1
                $typeOptions[] = 'cascade={' . implode(',', $cascades) . '}';
1560
            }
1561
1562 12
            if (isset($associationMapping['orphanRemoval']) && $associationMapping['orphanRemoval']) {
1563 1
                $typeOptions[] = 'orphanRemoval=' . ($associationMapping['orphanRemoval'] ? 'true' : 'false');
1564
            }
1565
1566 12
            if (isset($associationMapping['fetch']) && $associationMapping['fetch'] !== ClassMetadataInfo::FETCH_LAZY) {
1567
                $fetchMap = [
1568 11
                    ClassMetadataInfo::FETCH_EXTRA_LAZY => 'EXTRA_LAZY',
1569
                    ClassMetadataInfo::FETCH_EAGER      => 'EAGER',
1570
                ];
1571
1572 11
                $typeOptions[] = 'fetch="' . $fetchMap[$associationMapping['fetch']] . '"';
1573
            }
1574
1575 12
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . '' . $type . '(' . implode(', ', $typeOptions) . ')';
1576
1577 12
            if (isset($associationMapping['joinColumns']) && $associationMapping['joinColumns']) {
1578 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinColumns({';
1579
1580 1
                $joinColumnsLines = [];
1581
1582 1
                foreach ($associationMapping['joinColumns'] as $joinColumn) {
1583 1
                    if ($joinColumnAnnot = $this->generateJoinColumnAnnotation($joinColumn)) {
1584 1
                        $joinColumnsLines[] = $this->spaces . ' *   ' . $joinColumnAnnot;
1585
                    }
1586
                }
1587
1588 1
                $lines[] = implode(",\n", $joinColumnsLines);
1589 1
                $lines[] = $this->spaces . ' * })';
1590
            }
1591
1592 12
            if (isset($associationMapping['joinTable']) && $associationMapping['joinTable']) {
1593 12
                $joinTable = [];
1594 12
                $joinTable[] = 'name="' . $associationMapping['joinTable']['name'] . '"';
1595
1596 12
                if (isset($associationMapping['joinTable']['schema'])) {
1597
                    $joinTable[] = 'schema="' . $associationMapping['joinTable']['schema'] . '"';
1598
                }
1599
1600 12
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'JoinTable(' . implode(', ', $joinTable) . ',';
1601 12
                $lines[] = $this->spaces . ' *   joinColumns={';
1602
1603 12
                $joinColumnsLines = [];
1604
1605 12
                foreach ($associationMapping['joinTable']['joinColumns'] as $joinColumn) {
1606 12
                    $joinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1607
                }
1608
1609 12
                $lines[] = implode(",". PHP_EOL, $joinColumnsLines);
1610 12
                $lines[] = $this->spaces . ' *   },';
1611 12
                $lines[] = $this->spaces . ' *   inverseJoinColumns={';
1612
1613 12
                $inverseJoinColumnsLines = [];
1614
1615 12
                foreach ($associationMapping['joinTable']['inverseJoinColumns'] as $joinColumn) {
1616 12
                    $inverseJoinColumnsLines[] = $this->spaces . ' *     ' . $this->generateJoinColumnAnnotation($joinColumn);
1617
                }
1618
1619 12
                $lines[] = implode(",". PHP_EOL, $inverseJoinColumnsLines);
1620 12
                $lines[] = $this->spaces . ' *   }';
1621 12
                $lines[] = $this->spaces . ' * )';
1622
            }
1623
1624 12
            if (isset($associationMapping['orderBy'])) {
1625 1
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'OrderBy({';
1626
1627 1
                foreach ($associationMapping['orderBy'] as $name => $direction) {
1628 1
                    $lines[] = $this->spaces . ' *     "' . $name . '"="' . $direction . '",';
1629
                }
1630
1631 1
                $lines[count($lines) - 1] = substr($lines[count($lines) - 1], 0, strlen($lines[count($lines) - 1]) - 1);
1632 1
                $lines[] = $this->spaces . ' * })';
1633
            }
1634
        }
1635
1636 12
        $lines[] = $this->spaces . ' */';
1637
1638 12
        return implode("\n", $lines);
1639
    }
1640
1641
    /**
1642
     * @param array             $fieldMapping
1643
     * @param ClassMetadataInfo $metadata
1644
     *
1645
     * @return string
1646
     */
1647 43
    protected function generateFieldMappingPropertyDocBlock(array $fieldMapping, ClassMetadataInfo $metadata)
1648
    {
1649 43
        $lines = [];
1650 43
        $lines[] = $this->spaces . '/**';
1651 43
        $lines[] = $this->spaces . ' * @var '
1652 43
            . $this->getType($fieldMapping['type'])
1653 43
            . ($this->nullableFieldExpression($fieldMapping) ? '|null' : '');
1654
1655 43
        if ($this->generateAnnotations) {
1656 43
            $lines[] = $this->spaces . ' *';
1657
1658 43
            $column = [];
1659 43
            if (isset($fieldMapping['columnName'])) {
1660 43
                $column[] = 'name="' . $fieldMapping['columnName'] . '"';
1661
            }
1662
1663 43
            if (isset($fieldMapping['type'])) {
1664 43
                $column[] = 'type="' . $fieldMapping['type'] . '"';
1665
            }
1666
1667 43
            if (isset($fieldMapping['length'])) {
1668 11
                $column[] = 'length=' . $fieldMapping['length'];
1669
            }
1670
1671 43
            if (isset($fieldMapping['precision'])) {
1672 4
                $column[] = 'precision=' .  $fieldMapping['precision'];
1673
            }
1674
1675 43
            if (isset($fieldMapping['scale'])) {
1676 4
                $column[] = 'scale=' . $fieldMapping['scale'];
1677
            }
1678
1679 43
            if (isset($fieldMapping['nullable'])) {
1680 11
                $column[] = 'nullable=' .  var_export($fieldMapping['nullable'], true);
1681
            }
1682
1683 43
            $options = [];
1684
1685 43
            if (isset($fieldMapping['options']['default']) && $fieldMapping['options']['default']) {
1686 14
                $options[] = '"default"="' . $fieldMapping['options']['default'] .'"';
1687
            }
1688
1689 43
            if (isset($fieldMapping['options']['unsigned']) && $fieldMapping['options']['unsigned']) {
1690 3
                $options[] = '"unsigned"=true';
1691
            }
1692
1693 43
            if (isset($fieldMapping['options']['fixed']) && $fieldMapping['options']['fixed']) {
1694 2
                $options[] = '"fixed"=true';
1695
            }
1696
1697 43
            if (isset($fieldMapping['options']['comment']) && $fieldMapping['options']['comment']) {
1698 5
                $options[] = '"comment"="' . str_replace('"', '""', $fieldMapping['options']['comment']) . '"';
1699
            }
1700
1701 43
            if (isset($fieldMapping['options']['collation']) && $fieldMapping['options']['collation']) {
1702 2
                $options[] = '"collation"="' . $fieldMapping['options']['collation'] .'"';
1703
            }
1704
1705 43
            if (isset($fieldMapping['options']['check']) && $fieldMapping['options']['check']) {
1706 4
                $options[] = '"check"="' . $fieldMapping['options']['check'] .'"';
1707
            }
1708
1709 43
            if ($options) {
1710 23
                $column[] = 'options={'.implode(',', $options).'}';
1711
            }
1712
1713 43
            if (isset($fieldMapping['columnDefinition'])) {
1714 1
                $column[] = 'columnDefinition="' . $fieldMapping['columnDefinition'] . '"';
1715
            }
1716
1717 43
            if (isset($fieldMapping['unique'])) {
1718 4
                $column[] = 'unique=' . var_export($fieldMapping['unique'], true);
1719
            }
1720
1721 43
            $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Column(' . implode(', ', $column) . ')';
1722
1723 43
            if (isset($fieldMapping['id']) && $fieldMapping['id']) {
1724 39
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Id';
1725
1726 39
                if ($generatorType = $this->getIdGeneratorTypeString($metadata->generatorType)) {
1727 39
                    $lines[] = $this->spaces.' * @' . $this->annotationsPrefix . 'GeneratedValue(strategy="' . $generatorType . '")';
1728
                }
1729
1730 39
                if ($metadata->sequenceGeneratorDefinition) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $metadata->sequenceGeneratorDefinition of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1731 1
                    $sequenceGenerator = [];
1732
1733 1
                    if (isset($metadata->sequenceGeneratorDefinition['sequenceName'])) {
1734 1
                        $sequenceGenerator[] = 'sequenceName="' . $metadata->sequenceGeneratorDefinition['sequenceName'] . '"';
1735
                    }
1736
1737 1
                    if (isset($metadata->sequenceGeneratorDefinition['allocationSize'])) {
1738 1
                        $sequenceGenerator[] = 'allocationSize=' . $metadata->sequenceGeneratorDefinition['allocationSize'];
1739
                    }
1740
1741 1
                    if (isset($metadata->sequenceGeneratorDefinition['initialValue'])) {
1742 1
                        $sequenceGenerator[] = 'initialValue=' . $metadata->sequenceGeneratorDefinition['initialValue'];
1743
                    }
1744
1745 1
                    $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'SequenceGenerator(' . implode(', ', $sequenceGenerator) . ')';
1746
                }
1747
            }
1748
1749 43
            if (isset($fieldMapping['version']) && $fieldMapping['version']) {
1750
                $lines[] = $this->spaces . ' * @' . $this->annotationsPrefix . 'Version';
1751
            }
1752
        }
1753
1754 43
        $lines[] = $this->spaces . ' */';
1755
1756 43
        return implode("\n", $lines);
1757
    }
1758
1759
    /**
1760
     * @param array $embeddedClass
1761
     *
1762
     * @return string
1763
     */
1764 11
    protected function generateEmbeddedPropertyDocBlock(array $embeddedClass)
1765
    {
1766 11
        $lines = [];
1767 11
        $lines[] = $this->spaces . '/**';
1768 11
        $lines[] = $this->spaces . ' * @var \\' . ltrim($embeddedClass['class'], '\\');
1769
1770 11
        if ($this->generateAnnotations) {
1771 11
            $lines[] = $this->spaces . ' *';
1772
1773 11
            $embedded = ['class="' . $embeddedClass['class'] . '"'];
1774
1775 11
            if (isset($embeddedClass['columnPrefix'])) {
1776 9
                if (is_string($embeddedClass['columnPrefix'])) {
1777 1
                    $embedded[] = 'columnPrefix="' . $embeddedClass['columnPrefix'] . '"';
1778
                } else {
1779 8
                    $embedded[] = 'columnPrefix=' . var_export($embeddedClass['columnPrefix'], true);
1780
                }
1781
            }
1782
1783 11
            $lines[] = $this->spaces . ' * @' .
1784 11
                $this->annotationsPrefix . 'Embedded(' . implode(', ', $embedded) . ')';
1785
        }
1786
1787 11
        $lines[] = $this->spaces . ' */';
1788
1789 11
        return implode("\n", $lines);
1790
    }
1791
1792 45
    private function generateEntityListenerAnnotation(ClassMetadataInfo $metadata): string
1793
    {
1794 45
        if (0 === \count($metadata->entityListeners)) {
1795 44
            return '';
1796
        }
1797
1798 1
        $processedClasses = [];
1799 1
        foreach ($metadata->entityListeners as $event => $eventListeners) {
1800 1
            foreach ($eventListeners as $eventListener) {
1801 1
                $processedClasses[] = '"' . $eventListener['class'] . '"';
1802
            }
1803
        }
1804
1805 1
        return \sprintf(
1806 1
            '%s%s({%s})',
1807 1
            '@' . $this->annotationsPrefix,
1808 1
            'EntityListeners',
1809 1
            \implode(',', \array_unique($processedClasses))
1810
        );
1811
    }
1812
1813
    /**
1814
     * @param string $code
1815
     * @param int    $num
1816
     *
1817
     * @return string
1818
     */
1819 45
    protected function prefixCodeWithSpaces($code, $num = 1)
1820
    {
1821 45
        $lines = explode("\n", $code);
1822
1823 45
        foreach ($lines as $key => $value) {
1824 45
            if ( ! empty($value)) {
1825 45
                $lines[$key] = str_repeat($this->spaces, $num) . $lines[$key];
1826
            }
1827
        }
1828
1829 45
        return implode("\n", $lines);
1830
    }
1831
1832
    /**
1833
     * @param integer $type The inheritance type used by the class and its subclasses.
1834
     *
1835
     * @return string The literal string for the inheritance type.
1836
     *
1837
     * @throws \InvalidArgumentException When the inheritance type does not exist.
1838
     */
1839 1
    protected function getInheritanceTypeString($type)
1840
    {
1841 1
        if ( ! isset(static::$inheritanceTypeMap[$type])) {
1842 1
            throw new \InvalidArgumentException(sprintf('Invalid provided InheritanceType: %s', $type));
1843
        }
1844
1845 1
        return static::$inheritanceTypeMap[$type];
1846
    }
1847
1848
    /**
1849
     * @param integer $type The policy used for change-tracking for the mapped class.
1850
     *
1851
     * @return string The literal string for the change-tracking type.
1852
     *
1853
     * @throws \InvalidArgumentException When the change-tracking type does not exist.
1854
     */
1855 1
    protected function getChangeTrackingPolicyString($type)
1856
    {
1857 1
        if ( ! isset(static::$changeTrackingPolicyMap[$type])) {
1858 1
            throw new \InvalidArgumentException(sprintf('Invalid provided ChangeTrackingPolicy: %s', $type));
1859
        }
1860
1861 1
        return static::$changeTrackingPolicyMap[$type];
1862
    }
1863
1864
    /**
1865
     * @param integer $type The generator to use for the mapped class.
1866
     *
1867
     * @return string The literal string for the generator type.
1868
     *
1869
     * @throws \InvalidArgumentException    When the generator type does not exist.
1870
     */
1871 40
    protected function getIdGeneratorTypeString($type)
1872
    {
1873 40
        if ( ! isset(static::$generatorStrategyMap[$type])) {
1874 1
            throw new \InvalidArgumentException(sprintf('Invalid provided IdGeneratorType: %s', $type));
1875
        }
1876
1877 40
        return static::$generatorStrategyMap[$type];
1878
    }
1879
1880
    /**
1881
     * @param array $fieldMapping
1882
     *
1883
     * @return string|null
1884
     */
1885 45
    private function nullableFieldExpression(array $fieldMapping)
1886
    {
1887 45
        if (isset($fieldMapping['nullable']) && true === $fieldMapping['nullable']) {
1888 8
            return 'null';
1889
        }
1890
1891 45
        return null;
1892
    }
1893
1894
    /**
1895
     * Exports (nested) option elements.
1896
     *
1897
     * @param array $options
1898
     *
1899
     * @return string
1900
     */
1901 1
    private function exportTableOptions(array $options)
1902
    {
1903 1
        $optionsStr = [];
1904
1905 1
        foreach ($options as $name => $option) {
1906 1
            if (is_array($option)) {
1907 1
                $optionsStr[] = '"' . $name . '"={' . $this->exportTableOptions($option) . '}';
1908
            } else {
1909 1
                $optionsStr[] = '"' . $name . '"="' . (string) $option . '"';
1910
            }
1911
        }
1912
1913 1
        return implode(',', $optionsStr);
1914
    }
1915
}
1916