BeanDescriptor::generateFindByDaoCodeForIndex()   F
last analyzed

Complexity

Conditions 25
Paths 1228

Size

Total Lines 149
Code Lines 89

Duplication

Lines 0
Ratio 0 %

Importance

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

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