BeanDescriptor::generateDaoPhpCode()   F
last analyzed

Complexity

Conditions 20
Paths > 20000

Size

Total Lines 406
Code Lines 288

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 20
eloc 288
c 6
b 0
f 0
nc 24608
nop 0
dl 0
loc 406
rs 0

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
declare(strict_types=1);
4
5
namespace TheCodingMachine\TDBM\Utils;
6
7
use Doctrine\DBAL\Schema\Column;
8
use Doctrine\DBAL\Schema\Index;
9
use Doctrine\DBAL\Schema\Schema;
10
use Doctrine\DBAL\Schema\Table;
11
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
12
use Doctrine\DBAL\Types\Types;
13
use JsonSerializable;
14
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
15
use Ramsey\Uuid\Uuid;
16
use TheCodingMachine\TDBM\AbstractTDBMObject;
17
use TheCodingMachine\TDBM\AlterableResultIterator;
18
use TheCodingMachine\TDBM\ConfigurationInterface;
19
use TheCodingMachine\TDBM\ResultIterator;
20
use TheCodingMachine\TDBM\Schema\ForeignKey;
21
use TheCodingMachine\TDBM\Schema\ForeignKeys;
22
use TheCodingMachine\TDBM\TDBMException;
23
use TheCodingMachine\TDBM\TDBMSchemaAnalyzer;
24
use TheCodingMachine\TDBM\TDBMService;
25
use TheCodingMachine\TDBM\Utils\Annotation\AbstractTraitAnnotation;
26
use TheCodingMachine\TDBM\Utils\Annotation\AddInterfaceOnDao;
27
use TheCodingMachine\TDBM\Utils\Annotation\AddTrait;
28
use TheCodingMachine\TDBM\Utils\Annotation\AddTraitOnDao;
29
use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser;
30
use TheCodingMachine\TDBM\Utils\Annotation\AddInterface;
31
use Laminas\Code\Generator\AbstractMemberGenerator;
32
use Laminas\Code\Generator\ClassGenerator;
33
use Laminas\Code\Generator\DocBlock\Tag;
34
use Laminas\Code\Generator\DocBlock\Tag\GenericTag;
35
use Laminas\Code\Generator\DocBlock\Tag\ParamTag;
36
use Laminas\Code\Generator\DocBlock\Tag\ReturnTag;
37
use Laminas\Code\Generator\DocBlock\Tag\ThrowsTag;
38
use Laminas\Code\Generator\DocBlock\Tag\VarTag;
39
use Laminas\Code\Generator\DocBlockGenerator;
40
use Laminas\Code\Generator\FileGenerator;
41
use Laminas\Code\Generator\MethodGenerator;
42
use Laminas\Code\Generator\ParameterGenerator;
43
use Laminas\Code\Generator\PropertyGenerator;
44
45
use function array_combine;
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()), $this->resultIteratorNamespace);
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, $this->resultIteratorNamespace);
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->annotationParser, $this->beanNamespace, $this->resultIteratorNamespace);
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->annotationParser, $this->beanNamespace, $this->resultIteratorNamespace);
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
        $method->setReturnType("mixed");
518
519
        if ($parentFk !== null) {
520
            $body = '$array = parent::jsonSerialize($stopRecursion);';
521
        } else {
522
            $body = '$array = [];';
523
        }
524
525
        foreach ($this->getExposedProperties() as $beanPropertyDescriptor) {
526
            $propertyCode = $beanPropertyDescriptor->getJsonSerializeCode();
527
            if (!empty($propertyCode)) {
528
                $body .= PHP_EOL . $propertyCode;
529
            }
530
        }
531
532
        // Many2many relationships
533
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
534
            $methodCode = $methodDescriptor->getJsonSerializeCode();
535
            if (!empty($methodCode)) {
536
                $body .= PHP_EOL . $methodCode;
537
            }
538
        }
539
540
        $body .= PHP_EOL . 'return $array;';
541
542
        $method->setBody($body);
543
544
        return $method;
545
    }
546
547
    /**
548
     * Returns as an array the class we need to extend from and the list of use statements.
549
     *
550
     * @param ForeignKeyConstraint|null $parentFk
551
     * @return string[]
552
     */
553
    private function generateExtendsAndUseStatements(ForeignKeyConstraint $parentFk = null): array
554
    {
555
        $classes = [];
556
        if ($parentFk !== null) {
557
            $extends = $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName());
558
            $classes[] = $extends;
559
        }
560
561
        foreach ($this->getBeanPropertyDescriptors() as $beanPropertyDescriptor) {
562
            $className = $beanPropertyDescriptor->getClassName();
563
            if (null !== $className) {
564
                $classes[] = $className;
565
            }
566
        }
567
568
        foreach ($this->getMethodDescriptors() as $descriptor) {
569
            $classes = array_merge($classes, $descriptor->getUsedClasses());
570
        }
571
572
        $classes = array_unique($classes);
573
574
        return $classes;
575
    }
576
577
    /**
578
     * Returns the representation of the PHP bean file with all getters and setters.
579
     *
580
     * @return ?FileGenerator
581
     */
582
    public function generatePhpCode(): ?FileGenerator
583
    {
584
        $file = new FileGenerator();
585
        $class = new ClassGenerator();
586
        $class->setAbstract(true);
587
        $file->setClass($class);
588
        $file->setNamespace($this->generatedBeanNamespace);
589
590
        $tableName = $this->table->getName();
591
        $baseClassName = $this->namingStrategy->getBaseBeanClassName($tableName);
592
        $className = $this->namingStrategy->getBeanClassName($tableName);
593
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
594
595
        $classes = $this->generateExtendsAndUseStatements($parentFk);
596
597
        foreach ($classes as $useClass) {
598
            $file->setUse($this->beanNamespace.'\\'.$useClass);
599
        }
600
601
        /*$uses = array_map(function ($className) {
602
            return 'use '.$this->beanNamespace.'\\'.$className.";\n";
603
        }, $classes);
604
        $use = implode('', $uses);*/
605
606
        $extends = $this->getExtendedBeanClassName();
607
        if ($extends === null) {
608
            $class->setExtendedClass(AbstractTDBMObject::class);
609
            $file->setUse(AbstractTDBMObject::class);
610
        } else {
611
            /** @var class-string $extends */
612
            $class->setExtendedClass($extends);
613
        }
614
615
        $file->setUse(ResultIterator::class);
616
        $file->setUse(AlterableResultIterator::class);
617
        $file->setUse(Uuid::class);
618
        $file->setUse(JsonSerializable::class);
619
        $file->setUse(ForeignKeys::class);
620
621
        $class->setName($baseClassName);
622
623
        $file->setDocBlock(new DocBlockGenerator(
624
            'This file has been automatically generated by TDBM.',
625
            <<<EOF
626
DO NOT edit this file, as it might be overwritten.
627
If you need to perform changes, edit the $className class instead!
628
EOF
629
        ));
630
631
        $class->setDocBlock(new DocBlockGenerator("The $baseClassName class maps the '$tableName' table in database."));
632
633
        /** @var AddInterface[] $addInterfaceAnnotations */
634
        $addInterfaceAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations(AddInterface::class);
635
636
        $interfaces = [ JsonSerializable::class ];
637
        foreach ($addInterfaceAnnotations as $annotation) {
638
            /** @phpstan-var class-string $className */
639
            $className =  $annotation->getName();
640
            $interfaces[] = $className;
641
        }
642
643
        $class->setImplementedInterfaces($interfaces);
644
645
        $this->registerTraits($class, AddTrait::class);
646
647
        $method = $this->generateBeanConstructor();
648
        $method = $this->codeGeneratorListener->onBaseBeanConstructorGenerated($method, $this, $this->configuration, $class);
649
        if ($method) {
650
            $class->addMethodFromGenerator($this->generateBeanConstructor());
651
        }
652
653
        $fks = [];
654
        foreach ($this->getExposedProperties() as $property) {
655
            if ($property instanceof ObjectBeanPropertyDescriptor) {
656
                $fks[] = $property->getForeignKey();
657
            }
658
            [$getter, $setter] = $property->getGetterSetterCode();
659
            [$getter, $setter] = $this->codeGeneratorListener->onBaseBeanPropertyGenerated($getter, $setter, $property, $this, $this->configuration, $class);
660
            if ($getter !== null) {
661
                $class->addMethodFromGenerator($getter);
662
            }
663
            if ($setter !== null) {
664
                $class->addMethodFromGenerator($setter);
665
            }
666
        }
667
668
        $pivotTableMethodsDescriptors = [];
669
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
670
            if ($methodDescriptor instanceof DirectForeignKeyMethodDescriptor) {
671
                [$method] = $methodDescriptor->getCode();
672
                $method = $this->codeGeneratorListener->onBaseBeanOneToManyGenerated($method, $methodDescriptor, $this, $this->configuration, $class);
673
                if ($method) {
674
                    $class->addMethodFromGenerator($method);
675
                }
676
            } elseif ($methodDescriptor instanceof PivotTableMethodsDescriptor) {
677
                $pivotTableMethodsDescriptors[] = $methodDescriptor;
678
                [ $getter, $adder, $remover, $has, $setter ] = $methodDescriptor->getCode();
679
                $methods = $this->codeGeneratorListener->onBaseBeanManyToManyGenerated($getter, $adder, $remover, $has, $setter, $methodDescriptor, $this, $this->configuration, $class);
680
                foreach ($methods as $method) {
681
                    if ($method) {
682
                        $class->addMethodFromGenerator($method);
683
                    }
684
                }
685
            } else {
686
                throw new \RuntimeException('Unexpected instance'); // @codeCoverageIgnore
687
            }
688
        }
689
690
        $manyToManyRelationshipCode = $this->generateGetManyToManyRelationshipDescriptorCode($pivotTableMethodsDescriptors);
691
        if ($manyToManyRelationshipCode !== null) {
692
            $class->addMethodFromGenerator($manyToManyRelationshipCode);
693
        }
694
        $manyToManyRelationshipKeysCode = $this->generateGetManyToManyRelationshipDescriptorKeysCode($pivotTableMethodsDescriptors);
695
        if ($manyToManyRelationshipKeysCode !== null) {
696
            $class->addMethodFromGenerator($manyToManyRelationshipKeysCode);
697
        }
698
699
        $foreignKeysProperty = new PropertyGenerator('foreignKeys');
700
        $foreignKeysProperty->setStatic(true);
701
        $foreignKeysProperty->setVisibility(AbstractMemberGenerator::VISIBILITY_PRIVATE);
702
        $foreignKeysProperty->setDocBlock(new DocBlockGenerator(null, null, [new VarTag(null, ['\\'.ForeignKeys::class])]));
703
        $class->addPropertyFromGenerator($foreignKeysProperty);
704
705
        $method = $this->generateGetForeignKeys($fks);
706
        $class->addMethodFromGenerator($method);
707
708
        $method = $this->generateJsonSerialize();
709
        $method = $this->codeGeneratorListener->onBaseBeanJsonSerializeGenerated($method, $this, $this->configuration, $class);
710
        if ($method !== null) {
711
            $class->addMethodFromGenerator($method);
712
        }
713
714
        $class->addMethodFromGenerator($this->generateGetUsedTablesCode());
715
        $onDeleteCode = $this->generateOnDeleteCode();
716
        if ($onDeleteCode) {
717
            $class->addMethodFromGenerator($onDeleteCode);
718
        }
719
        $cloneCode = $this->generateCloneCode($pivotTableMethodsDescriptors);
720
        $cloneCode = $this->codeGeneratorListener->onBaseBeanCloneGenerated($cloneCode, $this, $this->configuration, $class);
721
        if ($cloneCode) {
722
            $class->addMethodFromGenerator($cloneCode);
723
        }
724
725
        $file = $this->codeGeneratorListener->onBaseBeanGenerated($file, $this, $this->configuration);
726
727
        return $file;
728
    }
729
730
    private function registerTraits(ClassGenerator $class, string $annotationClass): void
731
    {
732
        /** @var AbstractTraitAnnotation[] $addTraitAnnotations */
733
        $addTraitAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations($annotationClass);
734
735
        foreach ($addTraitAnnotations as $annotation) {
736
            $class->addTrait($annotation->getName());
737
        }
738
739
        foreach ($addTraitAnnotations as $annotation) {
740
            foreach ($annotation->getInsteadOf() as $method => $replacedTrait) {
741
                $class->addTraitOverride($method, $replacedTrait);
742
            }
743
            foreach ($annotation->getAs() as $method => $replacedMethod) {
744
                $class->addTraitAlias($method, $replacedMethod);
745
            }
746
        }
747
    }
748
749
    /**
750
     * Writes the representation of the PHP DAO file.
751
     *
752
     * @return ?FileGenerator
753
     */
754
    public function generateDaoPhpCode(): ?FileGenerator
755
    {
756
        $file = new FileGenerator();
757
        $class = new ClassGenerator();
758
        $class->setAbstract(true);
759
        $file->setClass($class);
760
        $file->setNamespace($this->generatedDaoNamespace);
761
762
        $tableName = $this->table->getName();
763
764
        $primaryKeyColumns = TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($this->table);
765
766
        list($defaultSort, $defaultSortDirection) = $this->getDefaultSortColumnFromAnnotation($this->table);
767
768
        $className = $this->namingStrategy->getDaoClassName($tableName);
769
        $baseClassName = $this->namingStrategy->getBaseDaoClassName($tableName);
770
        $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName);
771
        $beanClassName = $this->beanNamespace.'\\'.$beanClassWithoutNameSpace;
772
        $resultIteratorClassWithoutNameSpace = $this->getResultIteratorClassName();
773
        $resultIteratorClass = $this->resultIteratorNamespace.'\\'.$resultIteratorClassWithoutNameSpace;
774
775
        $findByDaoCodeMethods = $this->generateFindByDaoCode($this->beanNamespace, $beanClassWithoutNameSpace, $class);
776
777
        $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...
778
        // Let's suppress duplicates in used beans (if any)
779
        $usedBeans = array_flip(array_flip($usedBeans));
780
        foreach ($usedBeans as $usedBean) {
781
            $class->addUse($usedBean);
782
        }
783
784
        $file->setDocBlock(new DocBlockGenerator(
785
            <<<EOF
786
This file has been automatically generated by TDBM.
787
DO NOT edit this file, as it might be overwritten.
788
If you need to perform changes, edit the $className class instead!
789
EOF
790
        ));
791
792
        $file->setNamespace($this->generatedDaoNamespace);
793
794
        $class->addUse(TDBMService::class);
795
        $class->addUse(ResultIterator::class);
796
        $class->addUse(TDBMException::class);
797
798
        $class->setName($baseClassName);
799
800
        $class->setDocBlock(new DocBlockGenerator("The $baseClassName class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table."));
801
802
        /** @var AddInterfaceOnDao[] $addInterfaceOnDaoAnnotations */
803
        $addInterfaceOnDaoAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations(AddInterfaceOnDao::class);
804
805
        $interfaces = [];
806
        foreach ($addInterfaceOnDaoAnnotations as $annotation) {
807
            /** @phpstan-var class-string $className */
808
            $className =  $annotation->getName();
809
            $interfaces[] = $className;
810
        }
811
812
        $class->setImplementedInterfaces($interfaces);
813
814
        $this->registerTraits($class, AddTraitOnDao::class);
815
816
        $tdbmServiceProperty = new PropertyGenerator('tdbmService');
817
        $tdbmServiceProperty->setDocBlock(new DocBlockGenerator(null, null, [new VarTag(null, ['\\'.TDBMService::class])]));
818
        $class->addPropertyFromGenerator($tdbmServiceProperty);
819
820
        $defaultSortProperty = new PropertyGenerator('defaultSort', $defaultSort);
821
        $defaultSortProperty->setDocBlock(new DocBlockGenerator('The default sort column.', null, [new VarTag(null, ['string', 'null'])]));
822
        $class->addPropertyFromGenerator($defaultSortProperty);
823
824
        $defaultSortPropertyDirection = new PropertyGenerator('defaultDirection', $defaultSort && $defaultSortDirection ? $defaultSortDirection : 'asc');
825
        $defaultSortPropertyDirection->setDocBlock(new DocBlockGenerator('The default sort direction.', null, [new VarTag(null, ['string'])]));
826
        $class->addPropertyFromGenerator($defaultSortPropertyDirection);
827
828
        $constructorMethod = new MethodGenerator(
829
            '__construct',
830
            [ new ParameterGenerator('tdbmService', TDBMService::class) ],
831
            MethodGenerator::FLAG_PUBLIC,
832
            '$this->tdbmService = $tdbmService;',
833
            'Sets the TDBM service used by this DAO.'
834
        );
835
        $constructorMethod = $this->codeGeneratorListener->onBaseDaoConstructorGenerated($constructorMethod, $this, $this->configuration, $class);
836
        if ($constructorMethod !== null) {
837
            $class->addMethodFromGenerator($constructorMethod);
838
        }
839
840
        $saveMethod = new MethodGenerator(
841
            'save',
842
            [ new ParameterGenerator('obj', $beanClassName) ],
843
            MethodGenerator::FLAG_PUBLIC,
844
            '$this->tdbmService->save($obj);',
845
            (new DocBlockGenerator(
846
                "Persist the $beanClassWithoutNameSpace instance.",
847
                null,
848
                [
849
                    new ParamTag('obj', [$beanClassWithoutNameSpace], 'The bean to save.')
850
                ]
851
            ))->setWordWrap(false)
852
        );
853
        $saveMethod->setReturnType('void');
854
855
        $saveMethod = $this->codeGeneratorListener->onBaseDaoSaveGenerated($saveMethod, $this, $this->configuration, $class);
856
        if ($saveMethod !== null) {
857
            $class->addMethodFromGenerator($saveMethod);
858
        }
859
860
        $findAllBody = <<<EOF
861
if (\$this->defaultSort) {
862
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
863
} else {
864
    \$orderBy = null;
865
}
866
return \$this->tdbmService->findObjects('$tableName', null, [], \$orderBy, [], null, \\$beanClassName::class, \\$resultIteratorClass::class);
867
EOF;
868
869
        $findAllMethod = new MethodGenerator(
870
            'findAll',
871
            [],
872
            MethodGenerator::FLAG_PUBLIC,
873
            $findAllBody,
874
            (new DocBlockGenerator("Get all $beanClassWithoutNameSpace records."))->setWordWrap(false)
875
        );
876
        $findAllMethod->setReturnType($resultIteratorClass);
877
        $findAllMethod = $this->codeGeneratorListener->onBaseDaoFindAllGenerated($findAllMethod, $this, $this->configuration, $class);
878
        if ($findAllMethod !== null) {
879
            $class->addMethodFromGenerator($findAllMethod);
880
        }
881
882
        if (count($primaryKeyColumns) > 0) {
883
            $lazyLoadingParameterName = 'lazyLoading';
884
            $parameters = [];
885
            $parametersTag = [];
886
            $primaryKeyFilter = [];
887
888
            foreach ($primaryKeyColumns as $primaryKeyColumn) {
889
                if ($primaryKeyColumn === $lazyLoadingParameterName) {
890
                    throw new TDBMException('Primary Column name `' . $lazyLoadingParameterName . '` is not allowed.');
891
                }
892
                $phpType = TDBMDaoGenerator::dbalTypeToPhpType($this->table->getColumn($primaryKeyColumn)->getType());
893
                $parameters[] = new ParameterGenerator($primaryKeyColumn, $phpType);
894
                $parametersTag[] = new ParamTag($primaryKeyColumn, [$phpType]);
895
                $primaryKeyFilter[] = "'$primaryKeyColumn' => \$$primaryKeyColumn";
896
            }
897
            $parameters[] = new ParameterGenerator($lazyLoadingParameterName, 'bool', false);
898
            $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.');
899
            $parametersTag[] = new ReturnTag(['\\'.$beanClassName]);
900
            $parametersTag[] = new ThrowsTag('\\'.TDBMException::class);
901
902
            $getByIdMethod = new MethodGenerator(
903
                'getById',
904
                $parameters,
905
                MethodGenerator::FLAG_PUBLIC,
906
                "return \$this->tdbmService->findObjectByPk('$tableName', [" . implode(', ', $primaryKeyFilter) . "], [], \$$lazyLoadingParameterName, \\$beanClassName::class, \\$resultIteratorClass::class);",
907
                (new DocBlockGenerator(
908
                    "Get $beanClassWithoutNameSpace specified by its ID (its primary key).",
909
                    'If the primary key does not exist, an exception is thrown.',
910
                    $parametersTag
911
                ))->setWordWrap(false)
912
            );
913
            $getByIdMethod->setReturnType($beanClassName);
914
            $getByIdMethod = $this->codeGeneratorListener->onBaseDaoGetByIdGenerated($getByIdMethod, $this, $this->configuration, $class);
915
            if ($getByIdMethod) {
916
                $class->addMethodFromGenerator($getByIdMethod);
917
            }
918
        }
919
920
        $deleteMethodBody = <<<EOF
921
if (\$cascade === true) {
922
    \$this->tdbmService->deleteCascade(\$obj);
923
} else {
924
    \$this->tdbmService->delete(\$obj);
925
}
926
EOF;
927
928
929
        $deleteMethod = new MethodGenerator(
930
            'delete',
931
            [
932
                new ParameterGenerator('obj', $beanClassName),
933
                new ParameterGenerator('cascade', 'bool', false)
934
            ],
935
            MethodGenerator::FLAG_PUBLIC,
936
            $deleteMethodBody,
937
            (new DocBlockGenerator(
938
                "Get all $beanClassWithoutNameSpace records.",
939
                null,
940
                [
941
                    new ParamTag('obj', ['\\'.$beanClassName], 'The object to delete'),
942
                    new ParamTag('cascade', ['bool'], 'If true, it will delete all objects linked to $obj'),
943
                ]
944
            ))->setWordWrap(false)
945
        );
946
        $deleteMethod->setReturnType('void');
947
        $deleteMethod = $this->codeGeneratorListener->onBaseDaoDeleteGenerated($deleteMethod, $this, $this->configuration, $class);
948
        if ($deleteMethod !== null) {
949
            $class->addMethodFromGenerator($deleteMethod);
950
        }
951
952
        $findMethodBody = <<<EOF
953
if (\$this->defaultSort && \$orderBy == null) {
954
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
955
}
956
return \$this->tdbmService->findObjects('$tableName', \$filter, \$parameters, \$orderBy, \$additionalTablesFetch, \$mode, \\$beanClassName::class, \\$resultIteratorClass::class);
957
EOF;
958
959
960
        $findMethod = new MethodGenerator(
961
            'find',
962
            [
963
                (new ParameterGenerator('filter'))->setDefaultValue(null),
964
                new ParameterGenerator('parameters', 'array', []),
965
                (new ParameterGenerator('orderBy'))->setDefaultValue(null),
966
                new ParameterGenerator('additionalTablesFetch', 'array', []),
967
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
968
            ],
969
            MethodGenerator::FLAG_PROTECTED,
970
            $findMethodBody,
971
            (new DocBlockGenerator(
972
                "Get all $beanClassWithoutNameSpace records.",
973
                null,
974
                [
975
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
976
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
977
                    new ParamTag('orderBy', ['mixed'], 'The order string'),
978
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
979
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.')
980
                ]
981
            ))->setWordWrap(false)
982
        );
983
        $findMethod->setReturnType($resultIteratorClass);
984
        $findMethod = $this->codeGeneratorListener->onBaseDaoFindGenerated($findMethod, $this, $this->configuration, $class);
985
        if ($findMethod !== null) {
986
            $class->addMethodFromGenerator($findMethod);
987
        }
988
989
        $findFromSqlMethodBody = <<<EOF
990
if (\$this->defaultSort && \$orderBy == null) {
991
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
992
}
993
return \$this->tdbmService->findObjectsFromSql('$tableName', \$from, \$filter, \$parameters, \$orderBy, \$mode, \\$beanClassName::class, \\$resultIteratorClass::class);
994
EOF;
995
996
        $findFromSqlMethod = new MethodGenerator(
997
            'findFromSql',
998
            [
999
                new ParameterGenerator('from', 'string'),
1000
                (new ParameterGenerator('filter'))->setDefaultValue(null),
1001
                new ParameterGenerator('parameters', 'array', []),
1002
                (new ParameterGenerator('orderBy'))->setDefaultValue(null),
1003
                new ParameterGenerator('additionalTablesFetch', 'array', []),
1004
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
1005
            ],
1006
            MethodGenerator::FLAG_PROTECTED,
1007
            $findFromSqlMethodBody,
1008
            (new DocBlockGenerator(
1009
                "Get a list of $beanClassWithoutNameSpace specified by its filters.",
1010
                "Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part.
1011
1012
You should not put an alias on the main table name. So your \$from variable should look like:
1013
1014
   \"$tableName JOIN ... ON ...\"",
1015
                [
1016
                    new ParamTag('from', ['string'], 'The sql from statement'),
1017
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
1018
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
1019
                    new ParamTag('orderBy', ['mixed'], 'The order string'),
1020
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
1021
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.')
1022
                ]
1023
            ))->setWordWrap(false)
1024
        );
1025
        $findFromSqlMethod->setReturnType($resultIteratorClass);
1026
        $findFromSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromSqlGenerated($findFromSqlMethod, $this, $this->configuration, $class);
1027
        if ($findFromSqlMethod !== null) {
1028
            $class->addMethodFromGenerator($findFromSqlMethod);
1029
        }
1030
1031
        $findFromRawSqlMethodBody = <<<EOF
1032
return \$this->tdbmService->findObjectsFromRawSql('$tableName', \$sql, \$parameters, \$mode, \\$beanClassName::class, \$countSql, \\$resultIteratorClass::class);
1033
EOF;
1034
1035
        $findFromRawSqlMethod = new MethodGenerator(
1036
            'findFromRawSql',
1037
            [
1038
                new ParameterGenerator('sql', 'string'),
1039
                new ParameterGenerator('parameters', 'array', []),
1040
                (new ParameterGenerator('countSql', '?string'))->setDefaultValue(null),
1041
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
1042
            ],
1043
            MethodGenerator::FLAG_PROTECTED,
1044
            $findFromRawSqlMethodBody,
1045
            (new DocBlockGenerator(
1046
                "Get a list of $beanClassWithoutNameSpace from a SQL query.",
1047
                "Unlike the `find` and `findFromSql` methods, here you can pass the whole \$sql query.
1048
1049
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:
1050
1051
   \"SELECT $tableName .* FROM ...\"",
1052
                [
1053
                    new ParamTag('sql', ['string'], 'The sql query'),
1054
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the query'),
1055
                    new ParamTag('countSql', ['string', 'null'], 'The sql query that provides total count of rows (automatically computed if not provided)'),
1056
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.')
1057
                ]
1058
            ))->setWordWrap(false)
1059
        );
1060
        $findFromRawSqlMethod->setReturnType($resultIteratorClass);
1061
        $findFromRawSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromRawSqlGenerated($findFromRawSqlMethod, $this, $this->configuration, $class);
1062
        if ($findFromRawSqlMethod !== null) {
1063
            $class->addMethodFromGenerator($findFromRawSqlMethod);
1064
        }
1065
1066
        $findOneMethodBody = <<<EOF
1067
return \$this->tdbmService->findObject('$tableName', \$filter, \$parameters, \$additionalTablesFetch, \\$beanClassName::class, \\$resultIteratorClass::class);
1068
EOF;
1069
1070
1071
        $findOneMethod = new MethodGenerator(
1072
            'findOne',
1073
            [
1074
                (new ParameterGenerator('filter'))->setDefaultValue(null),
1075
                new ParameterGenerator('parameters', 'array', []),
1076
                new ParameterGenerator('additionalTablesFetch', 'array', []),
1077
            ],
1078
            MethodGenerator::FLAG_PROTECTED,
1079
            $findOneMethodBody,
1080
            (new DocBlockGenerator(
1081
                "Get a single $beanClassWithoutNameSpace specified by its filters.",
1082
                null,
1083
                [
1084
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
1085
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
1086
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
1087
                    new ReturnTag(['\\'.$beanClassName, 'null'])
1088
                ]
1089
            ))->setWordWrap(false)
1090
        );
1091
        $findOneMethod->setReturnType("?$beanClassName");
1092
        $findOneMethod = $this->codeGeneratorListener->onBaseDaoFindOneGenerated($findOneMethod, $this, $this->configuration, $class);
1093
        if ($findOneMethod !== null) {
1094
            $class->addMethodFromGenerator($findOneMethod);
1095
        }
1096
1097
        $findOneFromSqlMethodBody = <<<EOF
1098
return \$this->tdbmService->findObjectFromSql('$tableName', \$from, \$filter, \$parameters, \\$beanClassName::class, \\$resultIteratorClass::class);
1099
EOF;
1100
1101
        $findOneFromSqlMethod = new MethodGenerator(
1102
            'findOneFromSql',
1103
            [
1104
                new ParameterGenerator('from', 'string'),
1105
                (new ParameterGenerator('filter'))->setDefaultValue(null),
1106
                new ParameterGenerator('parameters', 'array', []),
1107
            ],
1108
            MethodGenerator::FLAG_PROTECTED,
1109
            $findOneFromSqlMethodBody,
1110
            (new DocBlockGenerator(
1111
                "Get a single $beanClassWithoutNameSpace specified by its filters.",
1112
                "Unlike the `findOne` method that guesses the FROM part of the statement, here you can pass the \$from part.
1113
1114
You should not put an alias on the main table name. So your \$from variable should look like:
1115
1116
    \"$tableName JOIN ... ON ...\"",
1117
                [
1118
                    new ParamTag('from', ['string'], 'The sql from statement'),
1119
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
1120
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
1121
                    new ReturnTag(['\\'.$beanClassName, 'null'])
1122
                ]
1123
            ))->setWordWrap(false)
1124
        );
1125
        $findOneFromSqlMethod->setReturnType("?$beanClassName");
1126
        $findOneFromSqlMethod = $this->codeGeneratorListener->onBaseDaoFindOneFromSqlGenerated($findOneFromSqlMethod, $this, $this->configuration, $class);
1127
        if ($findOneFromSqlMethod !== null) {
1128
            $class->addMethodFromGenerator($findOneFromSqlMethod);
1129
        }
1130
1131
1132
        $setDefaultSortMethod = new MethodGenerator(
1133
            'setDefaultSort',
1134
            [
1135
                new ParameterGenerator('defaultSort', 'string'),
1136
            ],
1137
            MethodGenerator::FLAG_PUBLIC,
1138
            '$this->defaultSort = $defaultSort;',
1139
            new DocBlockGenerator(
1140
                "Sets the default column for default sorting.",
1141
                null,
1142
                [
1143
                    new ParamTag('defaultSort', ['string']),
1144
                ]
1145
            )
1146
        );
1147
        $setDefaultSortMethod->setReturnType('void');
1148
        $setDefaultSortMethod = $this->codeGeneratorListener->onBaseDaoSetDefaultSortGenerated($setDefaultSortMethod, $this, $this->configuration, $class);
1149
        if ($setDefaultSortMethod !== null) {
1150
            $class->addMethodFromGenerator($setDefaultSortMethod);
1151
        }
1152
1153
        foreach ($findByDaoCodeMethods as $method) {
1154
            $class->addMethodFromGenerator($method);
1155
        }
1156
1157
        $file = $this->codeGeneratorListener->onBaseDaoGenerated($file, $this, $this->configuration);
1158
1159
        return $file;
1160
    }
1161
1162
    /**
1163
     * Writes the representation of the PHP ResultIterator file.
1164
     */
1165
    public function generateResultIteratorPhpCode(): ?FileGenerator
1166
    {
1167
        $file = new FileGenerator();
1168
        $class = new ClassGenerator();
1169
        $class->setAbstract(true);
1170
        $file->setClass($class);
1171
        $file->setNamespace($this->generatedResultIteratorNamespace);
1172
1173
        $tableName = $this->table->getName();
1174
1175
        $classNameWithoutNamespace = $this->namingStrategy->getResultIteratorClassName($tableName);
1176
        $className = $this->namingStrategy->getResultIteratorClassName($tableName);
0 ignored issues
show
Unused Code introduced by
The assignment to $className is dead and can be removed.
Loading history...
1177
        $baseClassName = $this->namingStrategy->getBaseResultIteratorClassName($tableName);
1178
        $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName);
1179
        $beanClassName = $this->beanNamespace.'\\'.$beanClassWithoutNameSpace;
1180
1181
        $file->setDocBlock(new DocBlockGenerator(
1182
            <<<EOF
1183
This file has been automatically generated by TDBM.
1184
DO NOT edit this file, as it might be overwritten.
1185
If you need to perform changes, edit the $classNameWithoutNamespace class instead!
1186
EOF
1187
        ));
1188
        $class->setName($baseClassName);
1189
        $extends = $this->getExtendedResultIteratorClassName();
1190
        if ($extends === null) {
1191
            $class->addUse(ResultIterator::class);
1192
            $class->setExtendedClass(ResultIterator::class);
1193
        } else {
1194
            $class->addUse($this->resultIteratorNamespace . '\\' . $extends);
1195
            /** @var class-string $extends */
1196
            $class->setExtendedClass($extends);
1197
        }
1198
1199
        $class->setDocBlock((new DocBlockGenerator(
1200
            "The $baseClassName class will iterate over results of $beanClassWithoutNameSpace class.",
1201
            null,
1202
            [new Tag\MethodTag('getIterator', ['\\' . $beanClassName . '[]']), new Tag\MethodTag('toArray', ['\\' . $beanClassName . '[]'])]
1203
        ))->setWordWrap(false));
1204
1205
        $file = $this->codeGeneratorListener->onBaseResultIteratorGenerated($file, $this, $this->configuration);
1206
1207
        return $file;
1208
    }
1209
1210
    /**
1211
     * Tries to find a @defaultSort annotation in one of the columns.
1212
     *
1213
     * @param Table $table
1214
     *
1215
     * @return mixed[] First item: column name, Second item: column order (asc/desc)
1216
     */
1217
    private function getDefaultSortColumnFromAnnotation(Table $table): array
1218
    {
1219
        $defaultSort = null;
1220
        $defaultSortDirection = null;
1221
        foreach ($table->getColumns() as $column) {
1222
            $comments = $column->getComment();
1223
            $matches = [];
1224
            if ($comments !== null && preg_match('/@defaultSort(\((desc|asc)\))*/', $comments, $matches) != 0) {
1225
                $defaultSort = $column->getName();
1226
                if (count($matches) === 3) {
1227
                    $defaultSortDirection = $matches[2];
1228
                } else {
1229
                    $defaultSortDirection = 'ASC';
1230
                }
1231
            }
1232
        }
1233
1234
        return [$defaultSort, $defaultSortDirection];
1235
    }
1236
1237
    /**
1238
     * @param string $beanNamespace
1239
     * @param string $beanClassName
1240
     *
1241
     * @return MethodGenerator[]
1242
     */
1243
    private function generateFindByDaoCode(string $beanNamespace, string $beanClassName, ClassGenerator $class): array
1244
    {
1245
        $methods = [];
1246
        foreach ($this->removeDuplicateIndexes($this->table->getIndexes()) as $index) {
1247
            if (!$index->isPrimary()) {
1248
                $method = $this->generateFindByDaoCodeForIndex($index, $beanNamespace, $beanClassName);
1249
1250
                if ($method !== null) {
1251
                    $method = $this->codeGeneratorListener->onBaseDaoFindByIndexGenerated($method, $index, $this, $this->configuration, $class);
1252
                    if ($method !== null) {
1253
                        $methods[] = $method;
1254
                    }
1255
                }
1256
            }
1257
        }
1258
        usort($methods, static function (MethodGenerator $methodA, MethodGenerator $methodB) {
1259
            return $methodA->getName() <=> $methodB->getName();
1260
        });
1261
1262
        return $methods;
1263
    }
1264
1265
    /**
1266
     * Remove identical indexes (indexes on same columns)
1267
     *
1268
     * @param Index[] $indexes
1269
     * @return Index[]
1270
     */
1271
    private function removeDuplicateIndexes(array $indexes): array
1272
    {
1273
        $indexesByKey = [];
1274
        foreach ($indexes as $index) {
1275
            $key = implode('__`__', $index->getUnquotedColumns());
1276
            // Unique Index have precedence over non unique one
1277
            if (!isset($indexesByKey[$key]) || $index->isUnique()) {
1278
                $indexesByKey[$key] = $index;
1279
            }
1280
        }
1281
1282
        return array_values($indexesByKey);
1283
    }
1284
1285
    /**
1286
     * @param Index  $index
1287
     * @param string $beanNamespace
1288
     * @param string $beanClassName
1289
     *
1290
     * @return MethodGenerator|null
1291
     */
1292
    private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespace, string $beanClassName): ?MethodGenerator
1293
    {
1294
        $columns = $index->getColumns();
1295
        $usedBeans = [];
1296
1297
        /**
1298
         * The list of elements building this index (expressed as columns or foreign keys)
1299
         * @var AbstractBeanPropertyDescriptor[]
1300
         */
1301
        $elements = [];
1302
1303
        foreach ($columns as $column) {
1304
            $fk = $this->isPartOfForeignKey($this->table, $this->table->getColumn($column));
1305
            if ($fk !== null) {
1306
                if (!isset($elements[$fk->getName()])) {
1307
                    $elements[$fk->getName()] = new ObjectBeanPropertyDescriptor($this->table, $fk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser, $this->registry->getBeanForTableName($fk->getForeignTableName()), $this->resultIteratorNamespace);
1308
                }
1309
            } else {
1310
                $elements[] = new ScalarBeanPropertyDescriptor($this->table, $this->table->getColumn($column), $this->namingStrategy, $this->annotationParser);
1311
            }
1312
        }
1313
        $elements = array_values($elements);
1314
1315
        // If the index is actually only a foreign key, let's bypass it entirely.
1316
        if (count($elements) === 1 && $elements[0] instanceof ObjectBeanPropertyDescriptor) {
1317
            return null;
1318
        }
1319
1320
        $parameters = [];
1321
        //$functionParameters = [];
1322
        $first = true;
1323
        /** @var AbstractBeanPropertyDescriptor $element */
1324
        foreach ($elements as $element) {
1325
            $parameter = new ParameterGenerator(ltrim($element->getSafeVariableName(), '$'));
1326
            if (!$first && !($element->isCompulsory() && $index->isUnique())) {
1327
                $parameterType = '?';
1328
                //$functionParameter = '?';
1329
            } else {
1330
                $parameterType = '';
1331
                //$functionParameter = '';
1332
            }
1333
            $parameterType .= $element->getPhpType();
1334
            $parameter->setType($parameterType);
1335
            if (!$first && !($element->isCompulsory() && $index->isUnique())) {
1336
                $parameter->setDefaultValue(null);
1337
            }
1338
            //$functionParameter .= $element->getPhpType();
1339
            $elementClassName = $element->getClassName();
1340
            if ($elementClassName) {
1341
                $usedBeans[] = $beanNamespace.'\\'.$elementClassName;
1342
            }
1343
            //$functionParameter .= ' '.$element->getVariableName();
1344
            if ($first) {
1345
                $first = false;
1346
            } /*else {
1347
                $functionParameter .= ' = null';
1348
            }*/
1349
            //$functionParameters[] = $functionParameter;
1350
            $parameters[] = $parameter;
1351
        }
1352
1353
        //$functionParametersString = implode(', ', $functionParameters);
1354
1355
        $count = 0;
1356
1357
        $params = [];
1358
        $filterArrayCode = '';
1359
        $commentArguments = [];
1360
        $first = true;
1361
        foreach ($elements as $element) {
1362
            $params[] = $element->getParamAnnotation();
1363
            if ($element instanceof ScalarBeanPropertyDescriptor) {
1364
                $typeName = $element->getDatabaseType()->getName();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Types\Type::getName() has been deprecated: this method will be removed in Doctrine DBAL 4.0, use {@see TypeRegistry::lookupName()} instead. ( Ignorable by Annotation )

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

1364
                $typeName = /** @scrutinizer ignore-deprecated */ $element->getDatabaseType()->getName();

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

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

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