Failed Conditions
Pull Request — 2.6 (#7180)
by Ben
11:16
created

EntityGenerator::generateEntityStubMethods()   F

Complexity

Conditions 25
Paths 864

Size

Total Lines 62
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 25.0146

Importance

Changes 0
Metric Value
cc 25
eloc 34
nop 1
dl 0
loc 62
ccs 34
cts 35
cp 0.9714
crap 25.0146
rs 3.2258
c 0
b 0
f 0
nc 864

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

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1504 10
                    $type = 'OneToOne';
1505 10
                    break;
1506 11
                case ClassMetadataInfo::MANY_TO_ONE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

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