Failed Conditions
Pull Request — master (#6593)
by Thomas
20:49
created

EntityGenerator::generateEntityDocBlock()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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