Passed
Pull Request — master (#251)
by
unknown
08:40
created

BeanDescriptor::generateResultIteratorPhpCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 35
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 25
c 0
b 0
f 0
dl 0
loc 35
rs 9.52
cc 1
nc 1
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace TheCodingMachine\TDBM\Utils;
5
6
use Doctrine\DBAL\Schema\Column;
7
use Doctrine\DBAL\Schema\Index;
8
use Doctrine\DBAL\Schema\Schema;
9
use Doctrine\DBAL\Schema\Table;
10
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
11
use Doctrine\DBAL\Types\Type;
12
use JsonSerializable;
13
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
14
use PhpParser\Comment\Doc;
15
use Ramsey\Uuid\Uuid;
16
use TheCodingMachine\TDBM\AbstractTDBMObject;
17
use TheCodingMachine\TDBM\AlterableResultIterator;
18
use TheCodingMachine\TDBM\ConfigurationInterface;
19
use TheCodingMachine\TDBM\InnerResultIterator;
20
use TheCodingMachine\TDBM\ResultIterator;
21
use TheCodingMachine\TDBM\SafeFunctions;
22
use TheCodingMachine\TDBM\Schema\ForeignKey;
23
use TheCodingMachine\TDBM\Schema\ForeignKeys;
24
use TheCodingMachine\TDBM\TDBMException;
25
use TheCodingMachine\TDBM\TDBMSchemaAnalyzer;
26
use TheCodingMachine\TDBM\TDBMService;
27
use TheCodingMachine\TDBM\Utils\Annotation\AbstractTraitAnnotation;
28
use TheCodingMachine\TDBM\Utils\Annotation\AddInterfaceOnDao;
29
use TheCodingMachine\TDBM\Utils\Annotation\AddTrait;
30
use TheCodingMachine\TDBM\Utils\Annotation\AddTraitOnDao;
31
use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser;
32
use TheCodingMachine\TDBM\Utils\Annotation\AddInterface;
33
use Laminas\Code\Generator\AbstractMemberGenerator;
34
use Laminas\Code\Generator\ClassGenerator;
35
use Laminas\Code\Generator\DocBlock\Tag;
36
use Laminas\Code\Generator\DocBlock\Tag\GenericTag;
37
use Laminas\Code\Generator\DocBlock\Tag\ParamTag;
38
use Laminas\Code\Generator\DocBlock\Tag\ReturnTag;
39
use Laminas\Code\Generator\DocBlock\Tag\ThrowsTag;
40
use Laminas\Code\Generator\DocBlock\Tag\VarTag;
41
use Laminas\Code\Generator\DocBlockGenerator;
42
use Laminas\Code\Generator\FileGenerator;
43
use Laminas\Code\Generator\MethodGenerator;
44
use Laminas\Code\Generator\ParameterGenerator;
45
use Laminas\Code\Generator\PropertyGenerator;
46
use function implode;
47
use function strtolower;
48
use function var_export;
49
50
/**
51
 * This class represents a bean.
52
 */
53
class BeanDescriptor implements BeanDescriptorInterface
54
{
55
    /**
56
     * @var Table
57
     */
58
    private $table;
59
60
    /**
61
     * @var SchemaAnalyzer
62
     */
63
    private $schemaAnalyzer;
64
65
    /**
66
     * @var Schema
67
     */
68
    private $schema;
69
70
    /**
71
     * @var AbstractBeanPropertyDescriptor[]
72
     */
73
    private $beanPropertyDescriptors = [];
74
75
    /**
76
     * @var TDBMSchemaAnalyzer
77
     */
78
    private $tdbmSchemaAnalyzer;
79
80
    /**
81
     * @var NamingStrategyInterface
82
     */
83
    private $namingStrategy;
84
    /**
85
     * @var string
86
     */
87
    private $beanNamespace;
88
    /**
89
     * @var string
90
     */
91
    private $generatedBeanNamespace;
92
    /**
93
     * @var AnnotationParser
94
     */
95
    private $annotationParser;
96
    /**
97
     * @var string
98
     */
99
    private $daoNamespace;
100
    /**
101
     * @var string
102
     */
103
    private $generatedDaoNamespace;
104
    /**
105
     * @var string
106
     */
107
    private $resultIteratorNamespace;
108
    /**
109
     * @var string
110
     */
111
    private $generatedResultIteratorNamespace;
112
    /**
113
     * @var CodeGeneratorListenerInterface
114
     */
115
    private $codeGeneratorListener;
116
    /**
117
     * @var ConfigurationInterface
118
     */
119
    private $configuration;
120
    /**
121
     * @var BeanRegistry
122
     */
123
    private $registry;
124
    /**
125
     * @var MethodDescriptorInterface[][]
126
     */
127
    private $descriptorsByMethodName = [];
128
    /**
129
     * @var DirectForeignKeyMethodDescriptor[]|null
130
     */
131
    private $directForeignKeysDescriptors = null;
132
    /**
133
     * @var PivotTableMethodsDescriptor[]|null
134
     */
135
    private $pivotTableDescriptors = null;
136
137
    public function __construct(
138
        Table $table,
139
        string $beanNamespace,
140
        string $generatedBeanNamespace,
141
        string $daoNamespace,
142
        string $generatedDaoNamespace,
143
        string $resultIteratorNamespace,
144
        string $generatedResultIteratorNamespace,
145
        SchemaAnalyzer $schemaAnalyzer,
146
        Schema $schema,
147
        TDBMSchemaAnalyzer $tdbmSchemaAnalyzer,
148
        NamingStrategyInterface $namingStrategy,
149
        AnnotationParser $annotationParser,
150
        CodeGeneratorListenerInterface $codeGeneratorListener,
151
        ConfigurationInterface $configuration,
152
        BeanRegistry $registry
153
    ) {
154
        $this->table = $table;
155
        $this->beanNamespace = $beanNamespace;
156
        $this->generatedBeanNamespace = $generatedBeanNamespace;
157
        $this->daoNamespace = $daoNamespace;
158
        $this->generatedDaoNamespace = $generatedDaoNamespace;
159
        $this->resultIteratorNamespace = $resultIteratorNamespace;
160
        $this->generatedResultIteratorNamespace = $generatedResultIteratorNamespace;
161
        $this->schemaAnalyzer = $schemaAnalyzer;
162
        $this->schema = $schema;
163
        $this->tdbmSchemaAnalyzer = $tdbmSchemaAnalyzer;
164
        $this->namingStrategy = $namingStrategy;
165
        $this->annotationParser = $annotationParser;
166
        $this->codeGeneratorListener = $codeGeneratorListener;
167
        $this->configuration = $configuration;
168
        $this->registry = $registry;
169
    }
170
171
    public function initBeanPropertyDescriptors(): void
172
    {
173
        $this->beanPropertyDescriptors = $this->getProperties($this->table);
174
175
        //init the list of method names with regular properties names
176
        foreach ($this->beanPropertyDescriptors as $beanPropertyDescriptor) {
177
            $this->checkForDuplicate($beanPropertyDescriptor);
178
        }
179
    }
180
181
    /**
182
     * Returns the foreign-key the column is part of, if any. null otherwise.
183
     *
184
     * @param Table  $table
185
     * @param Column $column
186
     *
187
     * @return ForeignKeyConstraint|null
188
     */
189
    private function isPartOfForeignKey(Table $table, Column $column) : ?ForeignKeyConstraint
190
    {
191
        $localColumnName = $column->getName();
192
        foreach ($table->getForeignKeys() as $foreignKey) {
193
            foreach ($foreignKey->getUnquotedLocalColumns() as $columnName) {
194
                if ($columnName === $localColumnName) {
195
                    return $foreignKey;
196
                }
197
            }
198
        }
199
200
        return null;
201
    }
202
203
    /**
204
     * @return AbstractBeanPropertyDescriptor[]
205
     */
206
    public function getBeanPropertyDescriptors(): array
207
    {
208
        return $this->beanPropertyDescriptors;
209
    }
210
211
    /**
212
     * Returns the list of columns that are not nullable and not autogenerated for a given table and its parent.
213
     *
214
     * @return AbstractBeanPropertyDescriptor[]
215
     */
216
    public function getConstructorProperties(): array
217
    {
218
        $constructorProperties = array_filter($this->beanPropertyDescriptors, static function (AbstractBeanPropertyDescriptor $property) {
219
            return !$property instanceof InheritanceReferencePropertyDescriptor && $property->isCompulsory() && !$property->isReadOnly();
220
        });
221
222
        return $constructorProperties;
223
    }
224
225
    /**
226
     * Returns the list of columns that have default values for a given table.
227
     *
228
     * @return AbstractBeanPropertyDescriptor[]
229
     */
230
    public function getPropertiesWithDefault(): array
231
    {
232
        $properties = $this->getPropertiesForTable($this->table);
233
        $defaultProperties = array_filter($properties, function (AbstractBeanPropertyDescriptor $property) {
234
            return $property->hasDefault();
235
        });
236
237
        return $defaultProperties;
238
    }
239
240
    /**
241
     * Returns the list of properties exposed as getters and setters in this class.
242
     *
243
     * @return AbstractBeanPropertyDescriptor[]
244
     */
245
    public function getExposedProperties(): array
246
    {
247
        $exposedProperties = array_filter($this->beanPropertyDescriptors, function (AbstractBeanPropertyDescriptor $property) {
248
            return !$property instanceof InheritanceReferencePropertyDescriptor && $property->getTable()->getName() === $this->table->getName();
249
        });
250
251
        return $exposedProperties;
252
    }
253
254
    /**
255
     * Returns the list of properties for this table (including parent tables).
256
     *
257
     * @param Table $table
258
     *
259
     * @return AbstractBeanPropertyDescriptor[]
260
     */
261
    private function getProperties(Table $table): array
262
    {
263
        // Security check: a table MUST have a primary key
264
        TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($table);
265
266
        $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
267
        if ($parentRelationship) {
268
            $parentTable = $this->schema->getTable($parentRelationship->getForeignTableName());
269
            $properties = $this->getProperties($parentTable);
270
            // we merge properties by overriding property names.
271
            $localProperties = $this->getPropertiesForTable($table);
272
            foreach ($localProperties as $name => $property) {
273
                // We do not override properties if this is a primary key!
274
                if (!$property instanceof InheritanceReferencePropertyDescriptor && $property->isPrimaryKey()) {
275
                    continue;
276
                }
277
                $properties[$name] = $property;
278
            }
279
        } else {
280
            $properties = $this->getPropertiesForTable($table);
281
        }
282
283
        return $properties;
284
    }
285
286
    /**
287
     * Returns the list of properties for this table (ignoring parent tables).
288
     *
289
     * @param Table $table
290
     *
291
     * @return AbstractBeanPropertyDescriptor[]
292
     */
293
    private function getPropertiesForTable(Table $table): array
294
    {
295
        $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
296
        if ($parentRelationship) {
297
            $ignoreColumns = $parentRelationship->getUnquotedForeignColumns();
298
        } else {
299
            $ignoreColumns = [];
300
        }
301
302
        $beanPropertyDescriptors = [];
303
        foreach ($table->getColumns() as $column) {
304
            if (in_array($column->getName(), $ignoreColumns, true)) {
305
                continue;
306
            }
307
308
            $fk = $this->isPartOfForeignKey($table, $column);
309
            if ($fk !== null) {
310
                // Check that previously added descriptors are not added on same FK (can happen with multi key FK).
311
                foreach ($beanPropertyDescriptors as $beanDescriptor) {
312
                    if ($beanDescriptor instanceof ObjectBeanPropertyDescriptor && $beanDescriptor->getForeignKey() === $fk) {
313
                        continue 2;
314
                    }
315
                }
316
                $propertyDescriptor = new ObjectBeanPropertyDescriptor($table, $fk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser, $this->registry->getBeanForTableName($fk->getForeignTableName()));
317
                // Check that this property is not an inheritance relationship
318
                $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
319
                if ($parentRelationship !== null && $parentRelationship->getName() === $fk->getName()) {
320
                    $beanPropertyDescriptors[] = new InheritanceReferencePropertyDescriptor(
321
                        $table,
322
                        $column,
323
                        $this->namingStrategy,
324
                        $this->annotationParser,
325
                        $propertyDescriptor
326
                    );
327
                } else {
328
                    $beanPropertyDescriptors[] = $propertyDescriptor;
329
                }
330
            } else {
331
                $beanPropertyDescriptors[] = new ScalarBeanPropertyDescriptor($table, $column, $this->namingStrategy, $this->annotationParser);
332
            }
333
        }
334
335
        // Now, let's get the name of all properties and let's check there is no duplicate.
336
        /* @var $names AbstractBeanPropertyDescriptor[] */
337
        $names = [];
338
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
339
            $name = $beanDescriptor->getGetterName();
340
            if (isset($names[$name])) {
341
                $names[$name]->useAlternativeName();
342
                $beanDescriptor->useAlternativeName();
343
            } else {
344
                $names[$name] = $beanDescriptor;
345
            }
346
        }
347
348
        // Final check (throw exceptions if problem arises)
349
        $names = [];
350
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
351
            $name = $beanDescriptor->getGetterName();
352
            if (isset($names[$name])) {
353
                throw new TDBMException('Unsolvable name conflict while generating method name "' . $name . '"');
354
            } else {
355
                $names[$name] = $beanDescriptor;
356
            }
357
        }
358
359
        // Last step, let's rebuild the list with a map:
360
        $beanPropertyDescriptorsMap = [];
361
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
362
            $beanPropertyDescriptorsMap[$beanDescriptor->getVariableName()] = $beanDescriptor;
363
        }
364
365
        return $beanPropertyDescriptorsMap;
366
    }
367
368
    private function generateBeanConstructor() : MethodGenerator
369
    {
370
        $constructorProperties = $this->getConstructorProperties();
371
372
        $constructor = new MethodGenerator('__construct', [], MethodGenerator::FLAG_PUBLIC);
373
        $constructorDocBlock = new DocBlockGenerator('The constructor takes all compulsory arguments.');
374
        $constructorDocBlock->setWordWrap(false);
375
        $constructor->setDocBlock($constructorDocBlock);
376
377
        $assigns = [];
378
        $parentConstructorArguments = [];
379
380
        foreach ($constructorProperties as $property) {
381
            $parameter = new ParameterGenerator(ltrim($property->getSafeVariableName(), '$'));
382
            if ($property->isTypeHintable()) {
383
                $parameter->setType($property->getPhpType());
384
            }
385
            $constructor->setParameter($parameter);
386
387
            $constructorDocBlock->setTag($property->getParamAnnotation());
388
389
            if ($property->getTable()->getName() === $this->table->getName()) {
390
                $assigns[] = $property->getConstructorAssignCode()."\n";
391
            } else {
392
                $parentConstructorArguments[] = $property->getSafeVariableName();
393
            }
394
        }
395
396
        $parentConstructorCode = sprintf("parent::__construct(%s);\n", implode(', ', $parentConstructorArguments));
397
398
        foreach ($this->getPropertiesWithDefault() as $property) {
399
            $assigns[] = $property->assignToDefaultCode()."\n";
400
        }
401
402
        $body = $parentConstructorCode . implode('', $assigns);
403
404
        $constructor->setBody($body);
405
406
        return $constructor;
407
    }
408
409
    /**
410
     * Returns the descriptors of one-to-many relationships (the foreign keys pointing on this beans)
411
     *
412
     * @return DirectForeignKeyMethodDescriptor[]
413
     */
414
    private function getDirectForeignKeysDescriptors(): array
415
    {
416
        if ($this->directForeignKeysDescriptors !== null) {
417
            return $this->directForeignKeysDescriptors;
418
        }
419
        $fks = $this->tdbmSchemaAnalyzer->getIncomingForeignKeys($this->table->getName());
420
421
        $descriptors = [];
422
423
        foreach ($fks as $fk) {
424
            $desc = new DirectForeignKeyMethodDescriptor($fk, $this->table, $this->namingStrategy, $this->annotationParser, $this->beanNamespace);
425
            $this->checkForDuplicate($desc);
426
            $descriptors[] = $desc;
427
        }
428
429
        $this->directForeignKeysDescriptors = $descriptors;
430
        return $this->directForeignKeysDescriptors;
431
    }
432
433
    /**
434
     * @return PivotTableMethodsDescriptor[]
435
     */
436
    private function getPivotTableDescriptors(): array
437
    {
438
        if ($this->pivotTableDescriptors !== null) {
439
            return $this->pivotTableDescriptors;
440
        }
441
        $descs = [];
442
        foreach ($this->schemaAnalyzer->detectJunctionTables(true) as $table) {
443
            // There are exactly 2 FKs since this is a pivot table.
444
            $fks = array_values($table->getForeignKeys());
445
446
            if ($fks[0]->getForeignTableName() === $this->table->getName()) {
447
                list($localFk, $remoteFk) = $fks;
448
                $desc = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser);
449
                $this->checkForDuplicate($desc);
450
                $descs[] = $desc;
451
            }
452
            if ($fks[1]->getForeignTableName() === $this->table->getName()) {
453
                list($remoteFk, $localFk) = $fks;
454
                $desc = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser);
455
                $this->checkForDuplicate($desc);
456
                $descs[] = $desc;
457
            }
458
        }
459
460
        $this->pivotTableDescriptors = $descs;
461
        return $this->pivotTableDescriptors;
462
    }
463
464
    /**
465
     * Check the method name isn't already used and flag the associated descriptors to use their alternative names if it is the case
466
     */
467
    private function checkForDuplicate(MethodDescriptorInterface $descriptor): void
468
    {
469
        $name = strtolower($descriptor->getName());
470
        if (!isset($this->descriptorsByMethodName[$name])) {
471
            $this->descriptorsByMethodName[$name] = [];
472
        }
473
        $this->descriptorsByMethodName[$name][] = $descriptor;
474
        $descriptors = $this->descriptorsByMethodName[$name];
475
        if (count($descriptors) > 1) {
476
            $properties = array_filter($descriptors, function ($descriptor) {
477
                return $descriptor instanceof AbstractBeanPropertyDescriptor;
478
            });
479
            $renameProperties = count($properties) > 1;
480
481
            foreach ($descriptors as $descriptor) {
482
                if ($renameProperties || !$descriptor instanceof AbstractBeanPropertyDescriptor) {
483
                    $descriptor->useAlternativeName();
484
                }
485
            }
486
        }
487
    }
488
489
    /**
490
     * Returns the list of method descriptors (and applies the alternative name if needed).
491
     *
492
     * @return RelationshipMethodDescriptorInterface[]
493
     */
494
    public function getMethodDescriptors(): array
495
    {
496
        $directForeignKeyDescriptors = $this->getDirectForeignKeysDescriptors();
497
        $pivotTableDescriptors = $this->getPivotTableDescriptors();
498
499
        return array_merge($directForeignKeyDescriptors, $pivotTableDescriptors);
500
    }
501
502
    public function generateJsonSerialize(): MethodGenerator
503
    {
504
        $tableName = $this->table->getName();
505
        $parentFk = $this->schemaAnalyzer->getParentRelationship($tableName);
506
507
        $method = new MethodGenerator('jsonSerialize');
508
        $method->setDocBlock(new DocBlockGenerator(
509
            'Serializes the object for JSON encoding.',
510
            null,
511
            [
512
                new ParamTag('$stopRecursion', ['bool'], 'Parameter used internally by TDBM to stop embedded objects from embedding other objects.'),
513
                new ReturnTag(['array'])
514
            ]
515
        ));
516
        $method->setParameter(new ParameterGenerator('stopRecursion', 'bool', false));
517
518
        if ($parentFk !== null) {
519
            $body = '$array = parent::jsonSerialize($stopRecursion);';
520
        } else {
521
            $body = '$array = [];';
522
        }
523
524
        foreach ($this->getExposedProperties() as $beanPropertyDescriptor) {
525
            $propertyCode = $beanPropertyDescriptor->getJsonSerializeCode();
526
            if (!empty($propertyCode)) {
527
                $body .= PHP_EOL . $propertyCode;
528
            }
529
        }
530
531
        // Many2many relationships
532
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
533
            $methodCode = $methodDescriptor->getJsonSerializeCode();
534
            if (!empty($methodCode)) {
535
                $body .= PHP_EOL . $methodCode;
536
            }
537
        }
538
539
        $body .= PHP_EOL . 'return $array;';
540
541
        $method->setBody($body);
542
543
        return $method;
544
    }
545
546
    /**
547
     * Returns as an array the class we need to extend from and the list of use statements.
548
     *
549
     * @param ForeignKeyConstraint|null $parentFk
550
     * @return string[]
551
     */
552
    private function generateExtendsAndUseStatements(ForeignKeyConstraint $parentFk = null): array
553
    {
554
        $classes = [];
555
        if ($parentFk !== null) {
556
            $extends = $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName());
557
            $classes[] = $extends;
558
        }
559
560
        foreach ($this->getBeanPropertyDescriptors() as $beanPropertyDescriptor) {
561
            $className = $beanPropertyDescriptor->getClassName();
562
            if (null !== $className) {
563
                $classes[] = $className;
564
            }
565
        }
566
567
        foreach ($this->getMethodDescriptors() as $descriptor) {
568
            $classes = array_merge($classes, $descriptor->getUsedClasses());
569
        }
570
571
        $classes = array_unique($classes);
572
573
        return $classes;
574
    }
575
576
    /**
577
     * Returns the representation of the PHP bean file with all getters and setters.
578
     *
579
     * @return ?FileGenerator
580
     */
581
    public function generatePhpCode(): ?FileGenerator
582
    {
583
        $file = new FileGenerator();
584
        $class = new ClassGenerator();
585
        $class->setAbstract(true);
586
        $file->setClass($class);
587
        $file->setNamespace($this->generatedBeanNamespace);
588
589
        $tableName = $this->table->getName();
590
        $baseClassName = $this->namingStrategy->getBaseBeanClassName($tableName);
591
        $className = $this->namingStrategy->getBeanClassName($tableName);
592
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
593
594
        $classes = $this->generateExtendsAndUseStatements($parentFk);
595
596
        foreach ($classes as $useClass) {
597
            $file->setUse($this->beanNamespace.'\\'.$useClass);
598
        }
599
600
        /*$uses = array_map(function ($className) {
601
            return 'use '.$this->beanNamespace.'\\'.$className.";\n";
602
        }, $classes);
603
        $use = implode('', $uses);*/
604
605
        $extends = $this->getExtendedBeanClassName();
606
        if ($extends === null) {
607
            $class->setExtendedClass(AbstractTDBMObject::class);
608
            $file->setUse(AbstractTDBMObject::class);
609
        } else {
610
            $class->setExtendedClass($extends);
611
        }
612
613
        $file->setUse(ResultIterator::class);
614
        $file->setUse(AlterableResultIterator::class);
615
        $file->setUse(Uuid::class);
616
        $file->setUse(JsonSerializable::class);
617
        $file->setUse(ForeignKeys::class);
618
619
        $class->setName($baseClassName);
620
621
        $file->setDocBlock(new DocBlockGenerator(
622
            'This file has been automatically generated by TDBM.',
623
            <<<EOF
624
DO NOT edit this file, as it might be overwritten.
625
If you need to perform changes, edit the $className class instead!
626
EOF
627
        ));
628
629
        $class->setDocBlock(new DocBlockGenerator("The $baseClassName class maps the '$tableName' table in database."));
630
631
        /** @var AddInterface[] $addInterfaceAnnotations */
632
        $addInterfaceAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations(AddInterface::class);
633
634
        $interfaces = [ JsonSerializable::class ];
635
        foreach ($addInterfaceAnnotations as $annotation) {
636
            $interfaces[] = $annotation->getName();
637
        }
638
639
        $class->setImplementedInterfaces($interfaces);
640
641
        $this->registerTraits($class, AddTrait::class);
642
643
        $method = $this->generateBeanConstructor();
644
        $method = $this->codeGeneratorListener->onBaseBeanConstructorGenerated($method, $this, $this->configuration, $class);
645
        if ($method) {
646
            $class->addMethodFromGenerator($this->generateBeanConstructor());
647
        }
648
649
        $fks = [];
650
        foreach ($this->getExposedProperties() as $property) {
651
            if ($property instanceof ObjectBeanPropertyDescriptor) {
652
                $fks[] = $property->getForeignKey();
653
            }
654
            [$getter, $setter] = $property->getGetterSetterCode();
655
            [$getter, $setter] = $this->codeGeneratorListener->onBaseBeanPropertyGenerated($getter, $setter, $property, $this, $this->configuration, $class);
656
            if ($getter !== null) {
657
                $class->addMethodFromGenerator($getter);
658
            }
659
            if ($setter !== null) {
660
                $class->addMethodFromGenerator($setter);
661
            }
662
        }
663
664
        $pivotTableMethodsDescriptors = [];
665
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
666
            if ($methodDescriptor instanceof DirectForeignKeyMethodDescriptor) {
667
                [$method] = $methodDescriptor->getCode();
668
                $method = $this->codeGeneratorListener->onBaseBeanOneToManyGenerated($method, $methodDescriptor, $this, $this->configuration, $class);
669
                if ($method) {
670
                    $class->addMethodFromGenerator($method);
671
                }
672
            } elseif ($methodDescriptor instanceof PivotTableMethodsDescriptor) {
673
                $pivotTableMethodsDescriptors[] = $methodDescriptor;
674
                [ $getter, $adder, $remover, $has, $setter ] = $methodDescriptor->getCode();
675
                $methods = $this->codeGeneratorListener->onBaseBeanManyToManyGenerated($getter, $adder, $remover, $has, $setter, $methodDescriptor, $this, $this->configuration, $class);
676
                foreach ($methods as $method) {
677
                    if ($method) {
678
                        $class->addMethodFromGenerator($method);
679
                    }
680
                }
681
            } else {
682
                throw new \RuntimeException('Unexpected instance'); // @codeCoverageIgnore
683
            }
684
        }
685
686
        $manyToManyRelationshipCode = $this->generateGetManyToManyRelationshipDescriptorCode($pivotTableMethodsDescriptors);
687
        if ($manyToManyRelationshipCode !== null) {
688
            $class->addMethodFromGenerator($manyToManyRelationshipCode);
689
        }
690
        $manyToManyRelationshipKeysCode = $this->generateGetManyToManyRelationshipDescriptorKeysCode($pivotTableMethodsDescriptors);
691
        if ($manyToManyRelationshipKeysCode !== null) {
692
            $class->addMethodFromGenerator($manyToManyRelationshipKeysCode);
693
        }
694
695
        $foreignKeysProperty = new PropertyGenerator('foreignKeys');
696
        $foreignKeysProperty->setStatic(true);
697
        $foreignKeysProperty->setVisibility(AbstractMemberGenerator::VISIBILITY_PRIVATE);
698
        $foreignKeysProperty->setDocBlock(new DocBlockGenerator(null, null, [new VarTag(null, ['\\'.ForeignKeys::class])]));
699
        $class->addPropertyFromGenerator($foreignKeysProperty);
700
701
        $method = $this->generateGetForeignKeys($fks);
702
        $class->addMethodFromGenerator($method);
703
704
        $method = $this->generateJsonSerialize();
705
        $method = $this->codeGeneratorListener->onBaseBeanJsonSerializeGenerated($method, $this, $this->configuration, $class);
706
        if ($method !== null) {
707
            $class->addMethodFromGenerator($method);
708
        }
709
710
        $class->addMethodFromGenerator($this->generateGetUsedTablesCode());
711
        $onDeleteCode = $this->generateOnDeleteCode();
712
        if ($onDeleteCode) {
713
            $class->addMethodFromGenerator($onDeleteCode);
714
        }
715
        $cloneCode = $this->generateCloneCode($pivotTableMethodsDescriptors);
716
        $cloneCode = $this->codeGeneratorListener->onBaseBeanCloneGenerated($cloneCode, $this, $this->configuration, $class);
717
        if ($cloneCode) {
718
            $class->addMethodFromGenerator($cloneCode);
719
        }
720
721
        $file = $this->codeGeneratorListener->onBaseBeanGenerated($file, $this, $this->configuration);
722
723
        return $file;
724
    }
725
726
    private function registerTraits(ClassGenerator $class, string $annotationClass): void
727
    {
728
        /** @var AbstractTraitAnnotation[] $addTraitAnnotations */
729
        $addTraitAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations($annotationClass);
730
731
        foreach ($addTraitAnnotations as $annotation) {
732
            $class->addTrait($annotation->getName());
733
        }
734
735
        foreach ($addTraitAnnotations as $annotation) {
736
            foreach ($annotation->getInsteadOf() as $method => $replacedTrait) {
737
                $class->addTraitOverride($method, $replacedTrait);
738
            }
739
            foreach ($annotation->getAs() as $method => $replacedMethod) {
740
                $class->addTraitAlias($method, $replacedMethod);
741
            }
742
        }
743
    }
744
745
    /**
746
     * Writes the representation of the PHP DAO file.
747
     *
748
     * @return ?FileGenerator
749
     */
750
    public function generateDaoPhpCode(): ?FileGenerator
751
    {
752
        $file = new FileGenerator();
753
        $class = new ClassGenerator();
754
        $class->setAbstract(true);
755
        $file->setClass($class);
756
        $file->setNamespace($this->generatedDaoNamespace);
757
758
        $tableName = $this->table->getName();
759
760
        $primaryKeyColumns = TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($this->table);
761
762
        list($defaultSort, $defaultSortDirection) = $this->getDefaultSortColumnFromAnnotation($this->table);
763
764
        $className = $this->namingStrategy->getDaoClassName($tableName);
765
        $baseClassName = $this->namingStrategy->getBaseDaoClassName($tableName);
766
        $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName);
767
        $beanClassName = $this->beanNamespace.'\\'.$beanClassWithoutNameSpace;
768
769
        $findByDaoCodeMethods = $this->generateFindByDaoCode($this->beanNamespace, $beanClassWithoutNameSpace, $class);
770
771
        $usedBeans[] = $beanClassName;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$usedBeans was never initialized. Although not strictly required by PHP, it is generally a good practice to add $usedBeans = array(); before regardless.
Loading history...
772
        // Let's suppress duplicates in used beans (if any)
773
        $usedBeans = array_flip(array_flip($usedBeans));
774
        foreach ($usedBeans as $usedBean) {
775
            $class->addUse($usedBean);
776
        }
777
778
        $file->setDocBlock(new DocBlockGenerator(
779
            <<<EOF
780
This file has been automatically generated by TDBM.
781
DO NOT edit this file, as it might be overwritten.
782
If you need to perform changes, edit the $className class instead!
783
EOF
784
        ));
785
786
        $file->setNamespace($this->generatedDaoNamespace);
787
788
        $class->addUse(TDBMService::class);
789
        $class->addUse(ResultIterator::class);
790
        $class->addUse(TDBMException::class);
791
792
        $class->setName($baseClassName);
793
794
        $class->setDocBlock(new DocBlockGenerator("The $baseClassName class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table."));
795
796
        /** @var AddInterfaceOnDao[] $addInterfaceOnDaoAnnotations */
797
        $addInterfaceOnDaoAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations(AddInterfaceOnDao::class);
798
799
        $interfaces = [];
800
        foreach ($addInterfaceOnDaoAnnotations as $annotation) {
801
            $interfaces[] = $annotation->getName();
802
        }
803
804
        $class->setImplementedInterfaces($interfaces);
805
806
        $this->registerTraits($class, AddTraitOnDao::class);
807
808
        $tdbmServiceProperty = new PropertyGenerator('tdbmService');
809
        $tdbmServiceProperty->setDocBlock(new DocBlockGenerator(null, null, [new VarTag(null, ['\\'.TDBMService::class])]));
810
        $class->addPropertyFromGenerator($tdbmServiceProperty);
811
812
        $defaultSortProperty = new PropertyGenerator('defaultSort', $defaultSort);
813
        $defaultSortProperty->setDocBlock(new DocBlockGenerator('The default sort column.', null, [new VarTag(null, ['string', 'null'])]));
814
        $class->addPropertyFromGenerator($defaultSortProperty);
815
816
        $defaultSortPropertyDirection = new PropertyGenerator('defaultDirection', $defaultSort && $defaultSortDirection ? $defaultSortDirection : 'asc');
817
        $defaultSortPropertyDirection->setDocBlock(new DocBlockGenerator('The default sort direction.', null, [new VarTag(null, ['string'])]));
818
        $class->addPropertyFromGenerator($defaultSortPropertyDirection);
819
820
        $constructorMethod = new MethodGenerator(
821
            '__construct',
822
            [ new ParameterGenerator('tdbmService', TDBMService::class) ],
823
            MethodGenerator::FLAG_PUBLIC,
824
            '$this->tdbmService = $tdbmService;',
825
            'Sets the TDBM service used by this DAO.'
826
        );
827
        $constructorMethod = $this->codeGeneratorListener->onBaseDaoConstructorGenerated($constructorMethod, $this, $this->configuration, $class);
828
        if ($constructorMethod !== null) {
829
            $class->addMethodFromGenerator($constructorMethod);
830
        }
831
832
        $saveMethod = new MethodGenerator(
833
            'save',
834
            [ new ParameterGenerator('obj', $beanClassName) ],
835
            MethodGenerator::FLAG_PUBLIC,
836
            '$this->tdbmService->save($obj);',
837
            (new DocBlockGenerator(
838
                "Persist the $beanClassWithoutNameSpace instance.",
839
                null,
840
                [
841
                    new ParamTag('obj', [$beanClassWithoutNameSpace], 'The bean to save.')
842
                ]
843
            ))->setWordWrap(false)
844
        );
845
        $saveMethod->setReturnType('void');
846
847
        $saveMethod = $this->codeGeneratorListener->onBaseDaoSaveGenerated($saveMethod, $this, $this->configuration, $class);
848
        if ($saveMethod !== null) {
849
            $class->addMethodFromGenerator($saveMethod);
850
        }
851
852
        $findAllBody = <<<EOF
853
if (\$this->defaultSort) {
854
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
855
} else {
856
    \$orderBy = null;
857
}
858
return \$this->tdbmService->findObjects('$tableName', null, [], \$orderBy, [], null, null, \\$this->resultIteratorNamespace\\{$this->getResultIteratorClassName()}::class);
859
EOF;
860
861
        $findAllMethod = new MethodGenerator(
862
            'findAll',
863
            [],
864
            MethodGenerator::FLAG_PUBLIC,
865
            $findAllBody,
866
            (new DocBlockGenerator("Get all $beanClassWithoutNameSpace records."))->setWordWrap(false)
867
        );
868
        $findAllMethod->setReturnType($this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName());
869
        $findAllMethod = $this->codeGeneratorListener->onBaseDaoFindAllGenerated($findAllMethod, $this, $this->configuration, $class);
870
        if ($findAllMethod !== null) {
871
            $class->addMethodFromGenerator($findAllMethod);
872
        }
873
874
        if (count($primaryKeyColumns) > 0) {
875
            $lazyLoadingParameterName = 'lazyLoading';
876
            $parameters = [];
877
            $parametersTag = [];
878
            $primaryKeyFilter = [];
879
880
            foreach ($primaryKeyColumns as $primaryKeyColumn) {
881
                if ($primaryKeyColumn === $lazyLoadingParameterName) {
882
                    throw new TDBMException('Primary Column name `' . $lazyLoadingParameterName . '` is not allowed.');
883
                }
884
                $phpType = TDBMDaoGenerator::dbalTypeToPhpType($this->table->getColumn($primaryKeyColumn)->getType());
885
                $parameters[] = new ParameterGenerator($primaryKeyColumn, $phpType);
886
                $parametersTag[] = new ParamTag($primaryKeyColumn, [$phpType]);
887
                $primaryKeyFilter[] = "'$primaryKeyColumn' => \$$primaryKeyColumn";
888
            }
889
            $parameters[] = new ParameterGenerator($lazyLoadingParameterName, 'bool', false);
890
            $parametersTag[] = new ParamTag($lazyLoadingParameterName, ['bool'], 'If set to true, the object will not be loaded right away. Instead, it will be loaded when you first try to access a method of the object.');
891
            $parametersTag[] = new ReturnTag(['\\'.$beanClassName]);
892
            $parametersTag[] = new ThrowsTag('\\'.TDBMException::class);
893
894
            $getByIdMethod = new MethodGenerator(
895
                'getById',
896
                $parameters,
897
                MethodGenerator::FLAG_PUBLIC,
898
                "return \$this->tdbmService->findObjectByPk('$tableName', [" . implode(', ', $primaryKeyFilter) . "], [], \$$lazyLoadingParameterName);",
899
                (new DocBlockGenerator(
900
                    "Get $beanClassWithoutNameSpace specified by its ID (its primary key).",
901
                    'If the primary key does not exist, an exception is thrown.',
902
                    $parametersTag
903
                ))->setWordWrap(false)
904
            );
905
            $getByIdMethod->setReturnType($beanClassName);
906
            $getByIdMethod = $this->codeGeneratorListener->onBaseDaoGetByIdGenerated($getByIdMethod, $this, $this->configuration, $class);
907
            if ($getByIdMethod) {
908
                $class->addMethodFromGenerator($getByIdMethod);
909
            }
910
        }
911
912
        $deleteMethodBody = <<<EOF
913
if (\$cascade === true) {
914
    \$this->tdbmService->deleteCascade(\$obj);
915
} else {
916
    \$this->tdbmService->delete(\$obj);
917
}
918
EOF;
919
920
921
        $deleteMethod = new MethodGenerator(
922
            'delete',
923
            [
924
                new ParameterGenerator('obj', $beanClassName),
925
                new ParameterGenerator('cascade', 'bool', false)
926
            ],
927
            MethodGenerator::FLAG_PUBLIC,
928
            $deleteMethodBody,
929
            (new DocBlockGenerator(
930
                "Get all $beanClassWithoutNameSpace records.",
931
                null,
932
                [
933
                    new ParamTag('obj', ['\\'.$beanClassName], 'The object to delete'),
934
                    new ParamTag('cascade', ['bool'], 'If true, it will delete all objects linked to $obj'),
935
                ]
936
            ))->setWordWrap(false)
937
        );
938
        $deleteMethod->setReturnType('void');
939
        $deleteMethod = $this->codeGeneratorListener->onBaseDaoDeleteGenerated($deleteMethod, $this, $this->configuration, $class);
940
        if ($deleteMethod !== null) {
941
            $class->addMethodFromGenerator($deleteMethod);
942
        }
943
944
        $findMethodBody = <<<EOF
945
if (\$this->defaultSort && \$orderBy == null) {
946
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
947
}
948
return \$this->tdbmService->findObjects('$tableName', \$filter, \$parameters, \$orderBy, \$additionalTablesFetch, \$mode, null, \\$this->resultIteratorNamespace\\{$this->getResultIteratorClassName()}::class);
949
EOF;
950
951
952
        $findMethod = new MethodGenerator(
953
            'find',
954
            [
955
                (new ParameterGenerator('filter'))->setDefaultValue(null),
956
                new ParameterGenerator('parameters', 'array', []),
957
                (new ParameterGenerator('orderBy'))->setDefaultValue(null),
958
                new ParameterGenerator('additionalTablesFetch', 'array', []),
959
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
960
            ],
961
            MethodGenerator::FLAG_PROTECTED,
962
            $findMethodBody,
963
            (new DocBlockGenerator(
964
                "Get all $beanClassWithoutNameSpace records.",
965
                null,
966
                [
967
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
968
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
969
                    new ParamTag('orderBy', ['mixed'], 'The order string'),
970
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
971
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.')
972
                ]
973
            ))->setWordWrap(false)
974
        );
975
        $findMethod->setReturnType($this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName());
976
        $findMethod = $this->codeGeneratorListener->onBaseDaoFindGenerated($findMethod, $this, $this->configuration, $class);
977
        if ($findMethod !== null) {
978
            $class->addMethodFromGenerator($findMethod);
979
        }
980
981
        $findFromSqlMethodBody = <<<EOF
982
if (\$this->defaultSort && \$orderBy == null) {
983
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
984
}
985
return \$this->tdbmService->findObjectsFromSql('$tableName', \$from, \$filter, \$parameters, \$orderBy, \$mode, null, \\$this->resultIteratorNamespace\\{$this->getResultIteratorClassName()}::class);
986
EOF;
987
988
        $findFromSqlMethod = new MethodGenerator(
989
            'findFromSql',
990
            [
991
                new ParameterGenerator('from', 'string'),
992
                (new ParameterGenerator('filter'))->setDefaultValue(null),
993
                new ParameterGenerator('parameters', 'array', []),
994
                (new ParameterGenerator('orderBy'))->setDefaultValue(null),
995
                new ParameterGenerator('additionalTablesFetch', 'array', []),
996
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
997
            ],
998
            MethodGenerator::FLAG_PROTECTED,
999
            $findFromSqlMethodBody,
1000
            (new DocBlockGenerator(
1001
                "Get a list of $beanClassWithoutNameSpace specified by its filters.",
1002
                "Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part.
1003
1004
You should not put an alias on the main table name. So your \$from variable should look like:
1005
1006
   \"$tableName JOIN ... ON ...\"",
1007
                [
1008
                    new ParamTag('from', ['string'], 'The sql from statement'),
1009
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
1010
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
1011
                    new ParamTag('orderBy', ['mixed'], 'The order string'),
1012
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
1013
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.')
1014
                ]
1015
            ))->setWordWrap(false)
1016
        );
1017
        $findFromSqlMethod->setReturnType($this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName());
1018
        $findFromSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromSqlGenerated($findFromSqlMethod, $this, $this->configuration, $class);
1019
        if ($findFromSqlMethod !== null) {
1020
            $class->addMethodFromGenerator($findFromSqlMethod);
1021
        }
1022
1023
        $findFromRawSqlMethodBody = <<<EOF
1024
return \$this->tdbmService->findObjectsFromRawSql('$tableName', \$sql, \$parameters, \$mode, null, \$countSql, \\$this->resultIteratorNamespace\\{$this->getResultIteratorClassName()}::class);
1025
EOF;
1026
1027
        $findFromRawSqlMethod = new MethodGenerator(
1028
            'findFromRawSql',
1029
            [
1030
                new ParameterGenerator('sql', 'string'),
1031
                new ParameterGenerator('parameters', 'array', []),
1032
                (new ParameterGenerator('countSql', '?string'))->setDefaultValue(null),
1033
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
1034
            ],
1035
            MethodGenerator::FLAG_PROTECTED,
1036
            $findFromRawSqlMethodBody,
1037
            (new DocBlockGenerator(
1038
                "Get a list of $beanClassWithoutNameSpace from a SQL query.",
1039
                "Unlike the `find` and `findFromSql` methods, here you can pass the whole \$sql query.
1040
1041
You should not put an alias on the main table name, and select its columns using `*`. So the SELECT part of you \$sql should look like:
1042
1043
   \"SELECT $tableName .* FROM ...\"",
1044
                [
1045
                    new ParamTag('sql', ['string'], 'The sql query'),
1046
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the query'),
1047
                    new ParamTag('countSql', ['string', 'null'], 'The sql query that provides total count of rows (automatically computed if not provided)'),
1048
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.')
1049
                ]
1050
            ))->setWordWrap(false)
1051
        );
1052
        $findFromRawSqlMethod->setReturnType($this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName());
1053
        $findFromRawSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromRawSqlGenerated($findFromRawSqlMethod, $this, $this->configuration, $class);
1054
        if ($findFromRawSqlMethod !== null) {
1055
            $class->addMethodFromGenerator($findFromRawSqlMethod);
1056
        }
1057
1058
        $findOneMethodBody = <<<EOF
1059
return \$this->tdbmService->findObject('$tableName', \$filter, \$parameters, \$additionalTablesFetch);
1060
EOF;
1061
1062
1063
        $findOneMethod = new MethodGenerator(
1064
            'findOne',
1065
            [
1066
                (new ParameterGenerator('filter'))->setDefaultValue(null),
1067
                new ParameterGenerator('parameters', 'array', []),
1068
                new ParameterGenerator('additionalTablesFetch', 'array', []),
1069
            ],
1070
            MethodGenerator::FLAG_PROTECTED,
1071
            $findOneMethodBody,
1072
            (new DocBlockGenerator(
1073
                "Get a single $beanClassWithoutNameSpace specified by its filters.",
1074
                null,
1075
                [
1076
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
1077
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
1078
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
1079
                    new ReturnTag(['\\'.$beanClassName, 'null'])
1080
                ]
1081
            ))->setWordWrap(false)
1082
        );
1083
        $findOneMethod->setReturnType("?$beanClassName");
1084
        $findOneMethod = $this->codeGeneratorListener->onBaseDaoFindOneGenerated($findOneMethod, $this, $this->configuration, $class);
1085
        if ($findOneMethod !== null) {
1086
            $class->addMethodFromGenerator($findOneMethod);
1087
        }
1088
1089
        $findOneFromSqlMethodBody = <<<EOF
1090
return \$this->tdbmService->findObjectFromSql('$tableName', \$from, \$filter, \$parameters);
1091
EOF;
1092
1093
        $findOneFromSqlMethod = new MethodGenerator(
1094
            'findOneFromSql',
1095
            [
1096
                new ParameterGenerator('from', 'string'),
1097
                (new ParameterGenerator('filter'))->setDefaultValue(null),
1098
                new ParameterGenerator('parameters', 'array', []),
1099
            ],
1100
            MethodGenerator::FLAG_PROTECTED,
1101
            $findOneFromSqlMethodBody,
1102
            (new DocBlockGenerator(
1103
                "Get a single $beanClassWithoutNameSpace specified by its filters.",
1104
                "Unlike the `findOne` method that guesses the FROM part of the statement, here you can pass the \$from part.
1105
1106
You should not put an alias on the main table name. So your \$from variable should look like:
1107
1108
    \"$tableName JOIN ... ON ...\"",
1109
                [
1110
                    new ParamTag('from', ['string'], 'The sql from statement'),
1111
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
1112
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
1113
                    new ReturnTag(['\\'.$beanClassName, 'null'])
1114
                ]
1115
            ))->setWordWrap(false)
1116
        );
1117
        $findOneFromSqlMethod->setReturnType("?$beanClassName");
1118
        $findOneFromSqlMethod = $this->codeGeneratorListener->onBaseDaoFindOneFromSqlGenerated($findOneFromSqlMethod, $this, $this->configuration, $class);
1119
        if ($findOneFromSqlMethod !== null) {
1120
            $class->addMethodFromGenerator($findOneFromSqlMethod);
1121
        }
1122
1123
1124
        $setDefaultSortMethod = new MethodGenerator(
1125
            'setDefaultSort',
1126
            [
1127
                new ParameterGenerator('defaultSort', 'string'),
1128
            ],
1129
            MethodGenerator::FLAG_PUBLIC,
1130
            '$this->defaultSort = $defaultSort;',
1131
            new DocBlockGenerator(
1132
                "Sets the default column for default sorting.",
1133
                null,
1134
                [
1135
                    new ParamTag('defaultSort', ['string']),
1136
                ]
1137
            )
1138
        );
1139
        $setDefaultSortMethod->setReturnType('void');
1140
        $setDefaultSortMethod = $this->codeGeneratorListener->onBaseDaoSetDefaultSortGenerated($setDefaultSortMethod, $this, $this->configuration, $class);
1141
        if ($setDefaultSortMethod !== null) {
1142
            $class->addMethodFromGenerator($setDefaultSortMethod);
1143
        }
1144
1145
        foreach ($findByDaoCodeMethods as $method) {
1146
            $class->addMethodFromGenerator($method);
1147
        }
1148
1149
        $file = $this->codeGeneratorListener->onBaseDaoGenerated($file, $this, $this->configuration);
1150
1151
        return $file;
1152
    }
1153
1154
    /**
1155
     * Writes the representation of the PHP ResultIterator file.
1156
     */
1157
    public function generateResultIteratorPhpCode(): ?FileGenerator
1158
    {
1159
        $file = new FileGenerator();
1160
        $class = new ClassGenerator();
1161
        $class->setAbstract(true);
1162
        $file->setClass($class);
1163
        $file->setNamespace($this->generatedResultIteratorNamespace);
1164
1165
        $tableName = $this->table->getName();
1166
1167
        $className = $this->namingStrategy->getResultIteratorClassName($tableName);
1168
        $baseClassName = $this->namingStrategy->getBaseResultIteratorClassName($tableName);
1169
        $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName);
1170
        $beanClassName = $this->beanNamespace.'\\'.$beanClassWithoutNameSpace;
1171
1172
        $file->setDocBlock(new DocBlockGenerator(
1173
            <<<EOF
1174
This file has been automatically generated by TDBM.
1175
DO NOT edit this file, as it might be overwritten.
1176
If you need to perform changes, edit the $className class instead!
1177
EOF
1178
        ));
1179
        $class->addUse(ResultIterator::class);
1180
        $class->setName($baseClassName);
1181
        $class->setExtendedClass(ResultIterator::class);
1182
1183
        $class->setDocBlock((new DocBlockGenerator(
1184
            "The $baseClassName class will iterate over results of $beanClassWithoutNameSpace class.",
1185
            null,
1186
            [new Tag\MethodTag('getIterator', ['\\' . $beanClassName . '[]'])]
1187
        ))->setWordWrap(false));
1188
1189
        $file = $this->codeGeneratorListener->onBaseResultIteratorGenerated($file, $this, $this->configuration);
1190
1191
        return $file;
1192
    }
1193
1194
    /**
1195
     * Tries to find a @defaultSort annotation in one of the columns.
1196
     *
1197
     * @param Table $table
1198
     *
1199
     * @return mixed[] First item: column name, Second item: column order (asc/desc)
1200
     */
1201
    private function getDefaultSortColumnFromAnnotation(Table $table): array
1202
    {
1203
        $defaultSort = null;
1204
        $defaultSortDirection = null;
1205
        foreach ($table->getColumns() as $column) {
1206
            $comments = $column->getComment();
1207
            $matches = [];
1208
            if ($comments !== null && preg_match('/@defaultSort(\((desc|asc)\))*/', $comments, $matches) != 0) {
1209
                $defaultSort = $column->getName();
1210
                if (count($matches) === 3) {
1211
                    $defaultSortDirection = $matches[2];
1212
                } else {
1213
                    $defaultSortDirection = 'ASC';
1214
                }
1215
            }
1216
        }
1217
1218
        return [$defaultSort, $defaultSortDirection];
1219
    }
1220
1221
    /**
1222
     * @param string $beanNamespace
1223
     * @param string $beanClassName
1224
     *
1225
     * @return MethodGenerator[]
1226
     */
1227
    private function generateFindByDaoCode(string $beanNamespace, string $beanClassName, ClassGenerator $class): array
1228
    {
1229
        $methods = [];
1230
        foreach ($this->removeDuplicateIndexes($this->table->getIndexes()) as $index) {
1231
            if (!$index->isPrimary()) {
1232
                $method = $this->generateFindByDaoCodeForIndex($index, $beanNamespace, $beanClassName);
1233
1234
                if ($method !== null) {
1235
                    $method = $this->codeGeneratorListener->onBaseDaoFindByIndexGenerated($method, $index, $this, $this->configuration, $class);
1236
                    if ($method !== null) {
1237
                        $methods[] = $method;
1238
                    }
1239
                }
1240
            }
1241
        }
1242
        usort($methods, static function (MethodGenerator $methodA, MethodGenerator $methodB) {
1243
            return $methodA->getName() <=> $methodB->getName();
1244
        });
1245
1246
        return $methods;
1247
    }
1248
1249
    /**
1250
     * Remove identical indexes (indexes on same columns)
1251
     *
1252
     * @param Index[] $indexes
1253
     * @return Index[]
1254
     */
1255
    private function removeDuplicateIndexes(array $indexes): array
1256
    {
1257
        $indexesByKey = [];
1258
        foreach ($indexes as $index) {
1259
            $key = implode('__`__', $index->getUnquotedColumns());
1260
            // Unique Index have precedence over non unique one
1261
            if (!isset($indexesByKey[$key]) || $index->isUnique()) {
1262
                $indexesByKey[$key] = $index;
1263
            }
1264
        }
1265
1266
        return array_values($indexesByKey);
1267
    }
1268
1269
    /**
1270
     * @param Index  $index
1271
     * @param string $beanNamespace
1272
     * @param string $beanClassName
1273
     *
1274
     * @return MethodGenerator|null
1275
     */
1276
    private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespace, string $beanClassName): ?MethodGenerator
1277
    {
1278
        $columns = $index->getColumns();
1279
        $usedBeans = [];
1280
1281
        /**
1282
         * The list of elements building this index (expressed as columns or foreign keys)
1283
         * @var AbstractBeanPropertyDescriptor[]
1284
         */
1285
        $elements = [];
1286
1287
        foreach ($columns as $column) {
1288
            $fk = $this->isPartOfForeignKey($this->table, $this->table->getColumn($column));
1289
            if ($fk !== null) {
1290
                if (!isset($elements[$fk->getName()])) {
1291
                    $elements[$fk->getName()] = new ObjectBeanPropertyDescriptor($this->table, $fk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser, $this->registry->getBeanForTableName($fk->getForeignTableName()));
1292
                }
1293
            } else {
1294
                $elements[] = new ScalarBeanPropertyDescriptor($this->table, $this->table->getColumn($column), $this->namingStrategy, $this->annotationParser);
1295
            }
1296
        }
1297
        $elements = array_values($elements);
1298
1299
        // If the index is actually only a foreign key, let's bypass it entirely.
1300
        if (count($elements) === 1 && $elements[0] instanceof ObjectBeanPropertyDescriptor) {
1301
            return null;
1302
        }
1303
1304
        $parameters = [];
1305
        //$functionParameters = [];
1306
        $first = true;
1307
        /** @var AbstractBeanPropertyDescriptor $element */
1308
        foreach ($elements as $element) {
1309
            $parameter = new ParameterGenerator(ltrim($element->getSafeVariableName(), '$'));
1310
            if (!$first && !($element->isCompulsory() && $index->isUnique())) {
1311
                $parameterType = '?';
1312
            //$functionParameter = '?';
1313
            } else {
1314
                $parameterType = '';
1315
                //$functionParameter = '';
1316
            }
1317
            $parameterType .= $element->getPhpType();
1318
            $parameter->setType($parameterType);
1319
            if (!$first && !($element->isCompulsory() && $index->isUnique())) {
1320
                $parameter->setDefaultValue(null);
1321
            }
1322
            //$functionParameter .= $element->getPhpType();
1323
            $elementClassName = $element->getClassName();
1324
            if ($elementClassName) {
1325
                $usedBeans[] = $beanNamespace.'\\'.$elementClassName;
1326
            }
1327
            //$functionParameter .= ' '.$element->getVariableName();
1328
            if ($first) {
1329
                $first = false;
1330
            } /*else {
1331
                $functionParameter .= ' = null';
1332
            }*/
1333
            //$functionParameters[] = $functionParameter;
1334
            $parameters[] = $parameter;
1335
        }
1336
1337
        //$functionParametersString = implode(', ', $functionParameters);
1338
1339
        $count = 0;
1340
1341
        $params = [];
1342
        $filterArrayCode = '';
1343
        $commentArguments = [];
1344
        $first = true;
1345
        foreach ($elements as $element) {
1346
            $params[] = $element->getParamAnnotation();
1347
            if ($element instanceof ScalarBeanPropertyDescriptor) {
1348
                $typeName = $element->getDatabaseType()->getName();
1349
                if ($typeName === Type::DATETIME_IMMUTABLE) {
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\DBAL\Types\Type::DATETIME_IMMUTABLE has been deprecated: Use {@see Types::DATETIME_IMMUTABLE} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1349
                if ($typeName === /** @scrutinizer ignore-deprecated */ Type::DATETIME_IMMUTABLE) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
1350
                    $filterArrayCode .= sprintf(
1351
                        "            %s => \$this->tdbmService->getConnection()->convertToDatabaseValue(%s, %s),\n",
1352
                        var_export($element->getColumnName(), true),
1353
                        $element->getSafeVariableName(),
1354
                        var_export($typeName, true)
1355
                    );
1356
                } else {
1357
                    $filterArrayCode .= '            '.var_export($element->getColumnName(), true).' => '.$element->getSafeVariableName().",\n";
1358
                }
1359
            } elseif ($element instanceof ObjectBeanPropertyDescriptor) {
1360
                $foreignKey = $element->getForeignKey();
1361
                $columns = SafeFunctions::arrayCombine($foreignKey->getUnquotedLocalColumns(), $foreignKey->getUnquotedForeignColumns());
1362
                ++$count;
1363
                $foreignTable = $this->schema->getTable($foreignKey->getForeignTableName());
1364
                foreach ($columns as $localColumn => $foreignColumn) {
1365
                    // TODO: a foreign key could point to another foreign key. In this case, there is no getter for the pointed column. We don't support this case.
1366
                    $targetedElement = new ScalarBeanPropertyDescriptor($foreignTable, $foreignTable->getColumn($foreignColumn), $this->namingStrategy, $this->annotationParser);
1367
                    if ($first || $element->isCompulsory() && $index->isUnique()) {
1368
                        // First parameter for index is not nullable
1369
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => '.$element->getSafeVariableName().'->'.$targetedElement->getGetterName()."(),\n";
1370
                    } else {
1371
                        // Other parameters for index is not nullable
1372
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => ('.$element->getSafeVariableName().' !== null) ? '.$element->getSafeVariableName().'->'.$targetedElement->getGetterName()."() : null,\n";
1373
                    }
1374
                }
1375
            }
1376
            $commentArguments[] = substr($element->getSafeVariableName(), 1);
1377
            if ($first) {
1378
                $first = false;
1379
            }
1380
        }
1381
1382
        //$paramsString = implode("\n", $params);
1383
1384
1385
        $methodName = $this->namingStrategy->getFindByIndexMethodName($index, $elements);
1386
1387
        $method = new MethodGenerator($methodName);
1388
1389
        if ($index->isUnique()) {
1390
            $parameters[] = new ParameterGenerator('additionalTablesFetch', 'array', []);
1391
            $params[] = new ParamTag('additionalTablesFetch', [ 'string[]' ], 'A list of additional tables to fetch (for performance improvement)');
1392
            $params[] = new ReturnTag([ '\\'.$beanNamespace.'\\'.$beanClassName, 'null' ]);
1393
            $method->setReturnType('?\\'.$beanNamespace.'\\'.$beanClassName);
1394
1395
            $docBlock = new DocBlockGenerator("Get a $beanClassName filtered by ".implode(', ', $commentArguments). '.', null, $params);
1396
            $docBlock->setWordWrap(false);
1397
1398
            $body = "\$filter = [
1399
".$filterArrayCode."        ];
1400
return \$this->findOne(\$filter, [], \$additionalTablesFetch);
1401
";
1402
        } else {
1403
            $parameters[] = (new ParameterGenerator('orderBy'))->setDefaultValue(null);
1404
            $params[] = new ParamTag('orderBy', [ 'mixed' ], 'The order string');
1405
            $parameters[] = new ParameterGenerator('additionalTablesFetch', 'array', []);
1406
            $params[] = new ParamTag('additionalTablesFetch', [ 'string[]' ], 'A list of additional tables to fetch (for performance improvement)');
1407
            $parameters[] = (new ParameterGenerator('mode', '?int'))->setDefaultValue(null);
1408
            $params[] = new ParamTag('mode', [ 'int', 'null' ], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.');
1409
            $method->setReturnType($this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName());
1410
1411
            $docBlock = new DocBlockGenerator("Get a list of $beanClassName filtered by ".implode(', ', $commentArguments).".", null, $params);
1412
            $docBlock->setWordWrap(false);
1413
1414
            $body = "\$filter = [
1415
".$filterArrayCode."        ];
1416
return \$this->find(\$filter, [], \$orderBy, \$additionalTablesFetch, \$mode);
1417
";
1418
        }
1419
1420
        $method->setParameters($parameters);
1421
        $method->setDocBlock($docBlock);
1422
        $method->setBody($body);
1423
1424
        return $method;
1425
    }
1426
1427
    /**
1428
     * Generates the code for the getUsedTable protected method.
1429
     *
1430
     * @return MethodGenerator
1431
     */
1432
    private function generateGetUsedTablesCode(): MethodGenerator
1433
    {
1434
        $hasParentRelationship = $this->schemaAnalyzer->getParentRelationship($this->table->getName()) !== null;
1435
        if ($hasParentRelationship) {
1436
            $code = sprintf('$tables = parent::getUsedTables();
1437
$tables[] = %s;
1438
1439
return $tables;', var_export($this->table->getName(), true));
1440
        } else {
1441
            $code = sprintf('        return [ %s ];', var_export($this->table->getName(), true));
1442
        }
1443
1444
        $method = new MethodGenerator('getUsedTables');
1445
        $method->setDocBlock(new DocBlockGenerator(
1446
            'Returns an array of used tables by this bean (from parent to child relationship).',
1447
            null,
1448
            [new ReturnTag(['string[]'])]
1449
        ));
1450
        $method->setReturnType('array');
1451
        $method->setBody($code);
1452
1453
        return $method;
1454
    }
1455
1456
    private function generateOnDeleteCode(): ?MethodGenerator
1457
    {
1458
        $code = '';
1459
        $relationships = $this->getPropertiesForTable($this->table);
1460
        foreach ($relationships as $relationship) {
1461
            if ($relationship instanceof ObjectBeanPropertyDescriptor) {
1462
                $tdbmFk = ForeignKey::createFromFk($relationship->getForeignKey());
1463
                $code .= '$this->setRef('.var_export($tdbmFk->getCacheKey(), true).', null, '.var_export($this->table->getName(), true).");\n";
1464
            }
1465
        }
1466
1467
        if (!$code) {
1468
            return null;
1469
        }
1470
1471
        $method = new MethodGenerator('onDelete');
1472
        $method->setDocBlock(new DocBlockGenerator('Method called when the bean is removed from database.'));
1473
        $method->setReturnType('void');
1474
        $method->setBody('parent::onDelete();
1475
'.$code);
1476
1477
        return $method;
1478
    }
1479
1480
    /**
1481
     * @param PivotTableMethodsDescriptor[] $pivotTableMethodsDescriptors
1482
     * @return MethodGenerator
1483
     */
1484
    private function generateGetManyToManyRelationshipDescriptorCode(array $pivotTableMethodsDescriptors): ?MethodGenerator
1485
    {
1486
        if (empty($pivotTableMethodsDescriptors)) {
1487
            return null;
1488
        }
1489
1490
        $method = new MethodGenerator('_getManyToManyRelationshipDescriptor');
1491
        $method->setVisibility(AbstractMemberGenerator::VISIBILITY_PUBLIC);
1492
        $method->setDocBlock(new DocBlockGenerator(
1493
            'Get the paths used for many to many relationships methods.',
1494
            null,
1495
            [new GenericTag('internal')]
1496
        ));
1497
        $method->setReturnType(ManyToManyRelationshipPathDescriptor::class);
1498
1499
        $parameter = new ParameterGenerator('pathKey');
1500
        $parameter->setType('string');
1501
        $method->setParameter($parameter);
1502
1503
        $code = 'switch ($pathKey) {'."\n";
1504
        foreach ($pivotTableMethodsDescriptors as $pivotTableMethodsDescriptor) {
1505
            $code .= '    case '.var_export($pivotTableMethodsDescriptor->getManyToManyRelationshipKey(), true).":\n";
1506
            $code .= '        return '.$pivotTableMethodsDescriptor->getManyToManyRelationshipInstantiationCode().";\n";
1507
        }
1508
        $code .= "    default:\n";
1509
        $code .= "        return parent::_getManyToManyRelationshipDescriptor(\$pathKey);\n";
1510
        $code .= "}\n";
1511
1512
        $method->setBody($code);
1513
1514
        return $method;
1515
    }
1516
1517
    /**
1518
     * @param PivotTableMethodsDescriptor[] $pivotTableMethodsDescriptors
1519
     * @return MethodGenerator
1520
     */
1521
    private function generateGetManyToManyRelationshipDescriptorKeysCode(array $pivotTableMethodsDescriptors): ?MethodGenerator
1522
    {
1523
        if (empty($pivotTableMethodsDescriptors)) {
1524
            return null;
1525
        }
1526
1527
        $method = new MethodGenerator('_getManyToManyRelationshipDescriptorKeys');
1528
        $method->setVisibility(AbstractMemberGenerator::VISIBILITY_PUBLIC);
1529
        $method->setReturnType('array');
1530
        $method->setDocBlock(new DocBlockGenerator(
1531
            'Returns the list of keys supported for many to many relationships',
1532
            null,
1533
            [new GenericTag('internal'), new ReturnTag('string[]')]
1534
        ));
1535
1536
        $keys = [];
1537
        foreach ($pivotTableMethodsDescriptors as $pivotTableMethodsDescriptor) {
1538
            $keys[] = var_export($pivotTableMethodsDescriptor->getManyToManyRelationshipKey(), true);
1539
        }
1540
1541
        $code = 'return array_merge(parent::_getManyToManyRelationshipDescriptorKeys(), ['.implode(', ', $keys).']);';
1542
1543
        $method->setBody($code);
1544
1545
        return $method;
1546
    }
1547
1548
    /**
1549
     * @param PivotTableMethodsDescriptor[] $pivotTableMethodsDescriptors
1550
     * @return MethodGenerator
1551
     */
1552
    private function generateCloneCode(array $pivotTableMethodsDescriptors): MethodGenerator
1553
    {
1554
        $precode = '';
1555
        $postcode = '';
1556
1557
        foreach ($this->beanPropertyDescriptors as $beanPropertyDescriptor) {
1558
            $postcode .= $beanPropertyDescriptor->getCloneRule();
1559
        }
1560
1561
        //cloning many to many relationships
1562
        foreach ($pivotTableMethodsDescriptors as $beanMethodDescriptor) {
1563
            $precode .= $beanMethodDescriptor->getCloneRule()."\n";
1564
        }
1565
1566
        $method = new MethodGenerator('__clone');
1567
        $method->setBody($precode."parent::__clone();\n".$postcode);
1568
1569
        return $method;
1570
    }
1571
1572
    /**
1573
     * Returns the bean class name (without the namespace).
1574
     *
1575
     * @return string
1576
     */
1577
    public function getBeanClassName() : string
1578
    {
1579
        return $this->namingStrategy->getBeanClassName($this->table->getName());
1580
    }
1581
1582
    /**
1583
     * Returns the base bean class name (without the namespace).
1584
     *
1585
     * @return string
1586
     */
1587
    public function getBaseBeanClassName() : string
1588
    {
1589
        return $this->namingStrategy->getBaseBeanClassName($this->table->getName());
1590
    }
1591
1592
    /**
1593
     * Returns the DAO class name (without the namespace).
1594
     *
1595
     * @return string
1596
     */
1597
    public function getDaoClassName() : string
1598
    {
1599
        return $this->namingStrategy->getDaoClassName($this->table->getName());
1600
    }
1601
1602
    /**
1603
     * Returns the base DAO class name (without the namespace).
1604
     *
1605
     * @return string
1606
     */
1607
    public function getBaseDaoClassName() : string
1608
    {
1609
        return $this->namingStrategy->getBaseDaoClassName($this->table->getName());
1610
    }
1611
1612
    /**
1613
     * Returns the ResultIterator class name (without the namespace).
1614
     *
1615
     * @return string
1616
     */
1617
    public function getResultIteratorClassName() : string
1618
    {
1619
        return $this->namingStrategy->getResultIteratorClassName($this->table->getName());
1620
    }
1621
1622
    /**
1623
     * Returns the base ResultIterator class name (without the namespace).
1624
     *
1625
     * @return string
1626
     */
1627
    public function getBaseResultIteratorClassName() : string
1628
    {
1629
        return $this->namingStrategy->getBaseResultIteratorClassName($this->table->getName());
1630
    }
1631
1632
    /**
1633
     * Returns the table used to build this bean.
1634
     *
1635
     * @return Table
1636
     */
1637
    public function getTable(): Table
1638
    {
1639
        return $this->table;
1640
    }
1641
1642
    /**
1643
     * Returns the extended bean class name (without the namespace), or null if the bean is not extended.
1644
     *
1645
     * @return string
1646
     */
1647
    public function getExtendedBeanClassName(): ?string
1648
    {
1649
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
1650
        if ($parentFk !== null) {
1651
            return $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName());
1652
        } else {
1653
            return null;
1654
        }
1655
    }
1656
1657
    /**
1658
     * @return string
1659
     */
1660
    public function getBeanNamespace(): string
1661
    {
1662
        return $this->beanNamespace;
1663
    }
1664
1665
    /**
1666
     * @return string
1667
     */
1668
    public function getGeneratedBeanNamespace(): string
1669
    {
1670
        return $this->generatedBeanNamespace;
1671
    }
1672
1673
    /**
1674
     * @param ForeignKeyConstraint[] $fks
1675
     */
1676
    private function generateGetForeignKeys(array $fks): MethodGenerator
1677
    {
1678
        $fkArray = [];
1679
1680
        foreach ($fks as $fk) {
1681
            $tdbmFk = ForeignKey::createFromFk($fk);
1682
            $fkArray[$tdbmFk->getCacheKey()] = [
1683
                ForeignKey::FOREIGN_TABLE => $fk->getForeignTableName(),
1684
                ForeignKey::LOCAL_COLUMNS => $fk->getUnquotedLocalColumns(),
1685
                ForeignKey::FOREIGN_COLUMNS => $fk->getUnquotedForeignColumns(),
1686
            ];
1687
        }
1688
1689
        ksort($fkArray);
1690
        foreach ($fkArray as $tableFks) {
1691
            ksort($tableFks);
1692
        }
1693
1694
        $code = <<<EOF
1695
if (\$tableName === %s) {
1696
    if (self::\$foreignKeys === null) {
1697
        self::\$foreignKeys = new ForeignKeys(%s);
1698
    }
1699
    return self::\$foreignKeys;
1700
}
1701
return parent::getForeignKeys(\$tableName);
1702
EOF;
1703
        $code = sprintf($code, var_export($this->getTable()->getName(), true), $this->psr2VarExport($fkArray, '        '));
1704
1705
        $method = new MethodGenerator('getForeignKeys');
1706
        $method->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED);
1707
        $method->setStatic(true);
1708
        $method->setDocBlock(new DocBlockGenerator('Internal method used to retrieve the list of foreign keys attached to this bean.'));
1709
        $method->setReturnType(ForeignKeys::class);
1710
1711
        $parameter = new ParameterGenerator('tableName');
1712
        $parameter->setType('string');
1713
        $method->setParameter($parameter);
1714
1715
1716
        $method->setBody($code);
1717
1718
        return $method;
1719
    }
1720
1721
    /**
1722
     * @param mixed $var
1723
     * @param string $indent
1724
     * @return string
1725
     */
1726
    private function psr2VarExport($var, string $indent=''): string
1727
    {
1728
        if (is_array($var)) {
1729
            $indexed = array_keys($var) === range(0, count($var) - 1);
1730
            $r = [];
1731
            foreach ($var as $key => $value) {
1732
                $r[] = "$indent    "
1733
                    . ($indexed ? '' : $this->psr2VarExport($key) . ' => ')
1734
                    . $this->psr2VarExport($value, "$indent    ");
1735
            }
1736
            return "[\n" . implode(",\n", $r) . "\n" . $indent . ']';
1737
        }
1738
        return var_export($var, true);
1739
    }
1740
}
1741