Passed
Pull Request — master (#181)
by
unknown
05:45
created

BeanDescriptor::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 32
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
c 0
b 0
f 0
dl 0
loc 32
rs 9.7666
cc 1
nc 1
nop 15

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
declare(strict_types=1);
3
4
namespace TheCodingMachine\TDBM\Utils;
5
6
use Doctrine\DBAL\ParameterType;
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 JsonSerializable;
13
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
14
use PhpParser\Comment\Doc;
15
use Ramsey\Uuid\Uuid;
16
use TheCodingMachine\TDBM\AbstractTDBMObject;
17
use TheCodingMachine\TDBM\AlterableResultIterator;
18
use TheCodingMachine\TDBM\ConfigurationInterface;
19
use TheCodingMachine\TDBM\InnerResultIterator;
20
use TheCodingMachine\TDBM\ResultIterator;
21
use TheCodingMachine\TDBM\SafeFunctions;
22
use TheCodingMachine\TDBM\Schema\ForeignKey;
23
use TheCodingMachine\TDBM\Schema\ForeignKeys;
24
use TheCodingMachine\TDBM\TDBMException;
25
use TheCodingMachine\TDBM\TDBMSchemaAnalyzer;
26
use TheCodingMachine\TDBM\TDBMService;
27
use TheCodingMachine\TDBM\Utils\Annotation\AbstractTraitAnnotation;
28
use TheCodingMachine\TDBM\Utils\Annotation\AddInterfaceOnDao;
29
use TheCodingMachine\TDBM\Utils\Annotation\AddTrait;
30
use TheCodingMachine\TDBM\Utils\Annotation\AddTraitOnDao;
31
use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser;
32
use TheCodingMachine\TDBM\Utils\Annotation\AddInterface;
33
use Zend\Code\Generator\AbstractMemberGenerator;
34
use Zend\Code\Generator\ClassGenerator;
35
use Zend\Code\Generator\DocBlock\Tag;
36
use Zend\Code\Generator\DocBlock\Tag\GenericTag;
37
use Zend\Code\Generator\DocBlock\Tag\ParamTag;
38
use Zend\Code\Generator\DocBlock\Tag\ReturnTag;
39
use Zend\Code\Generator\DocBlock\Tag\ThrowsTag;
40
use Zend\Code\Generator\DocBlock\Tag\VarTag;
41
use Zend\Code\Generator\DocBlockGenerator;
42
use Zend\Code\Generator\FileGenerator;
43
use Zend\Code\Generator\MethodGenerator;
44
use Zend\Code\Generator\ParameterGenerator;
45
use Zend\Code\Generator\PropertyGenerator;
46
use Zend\Code\Generator\PropertyValueGenerator;
47
use function implode;
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, function (AbstractBeanPropertyDescriptor $property) {
219
            return $property->isCompulsory();
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->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->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->getUnquotedLocalColumns();
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
                // Check that this property is not an inheritance relationship
317
                $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
318
                if ($parentRelationship === $fk) {
319
                    continue;
320
                }
321
322
                $beanPropertyDescriptors[] = new ObjectBeanPropertyDescriptor($table, $fk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser, $this->registry->getBeanForTableName($fk->getForeignTableName()), $this->resultIteratorNamespace);
323
            } else {
324
                $beanPropertyDescriptors[] = new ScalarBeanPropertyDescriptor($table, $column, $this->namingStrategy, $this->annotationParser);
325
            }
326
        }
327
328
        // Now, let's get the name of all properties and let's check there is no duplicate.
329
        /* @var $names AbstractBeanPropertyDescriptor[] */
330
        $names = [];
331
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
332
            $name = $beanDescriptor->getGetterName();
333
            if (isset($names[$name])) {
334
                $names[$name]->useAlternativeName();
335
                $beanDescriptor->useAlternativeName();
336
            } else {
337
                $names[$name] = $beanDescriptor;
338
            }
339
        }
340
341
        // Final check (throw exceptions if problem arises)
342
        $names = [];
343
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
344
            $name = $beanDescriptor->getGetterName();
345
            if (isset($names[$name])) {
346
                throw new TDBMException('Unsolvable name conflict while generating method name');
347
            } else {
348
                $names[$name] = $beanDescriptor;
349
            }
350
        }
351
352
        // Last step, let's rebuild the list with a map:
353
        $beanPropertyDescriptorsMap = [];
354
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
355
            $beanPropertyDescriptorsMap[$beanDescriptor->getVariableName()] = $beanDescriptor;
356
        }
357
358
        return $beanPropertyDescriptorsMap;
359
    }
360
361
    private function generateBeanConstructor() : MethodGenerator
362
    {
363
        $constructorProperties = $this->getConstructorProperties();
364
365
        $constructor = new MethodGenerator('__construct', [], MethodGenerator::FLAG_PUBLIC);
366
        $constructor->setDocBlock('The constructor takes all compulsory arguments.');
367
        $constructor->getDocBlock()->setWordWrap(false);
368
369
        $assigns = [];
370
        $parentConstructorArguments = [];
371
372
        foreach ($constructorProperties as $property) {
373
            $parameter = new ParameterGenerator(ltrim($property->getVariableName(), '$'));
374
            if ($property->isTypeHintable()) {
375
                $parameter->setType($property->getPhpType());
376
            }
377
            $constructor->setParameter($parameter);
378
379
            $constructor->getDocBlock()->setTag($property->getParamAnnotation());
380
381
            if ($property->getTable()->getName() === $this->table->getName()) {
382
                $assigns[] = $property->getConstructorAssignCode()."\n";
383
            } else {
384
                $parentConstructorArguments[] = $property->getVariableName();
385
            }
386
        }
387
388
        $parentConstructorCode = sprintf("parent::__construct(%s);\n", implode(', ', $parentConstructorArguments));
389
390
        foreach ($this->getPropertiesWithDefault() as $property) {
391
            $assigns[] = $property->assignToDefaultCode()."\n";
392
        }
393
394
        $body = $parentConstructorCode . implode('', $assigns);
395
396
        $constructor->setBody($body);
397
398
        return $constructor;
399
    }
400
401
    /**
402
     * Returns the descriptors of one-to-many relationships (the foreign keys pointing on this beans)
403
     *
404
     * @return DirectForeignKeyMethodDescriptor[]
405
     */
406
    private function getDirectForeignKeysDescriptors(): array
407
    {
408
        if ($this->directForeignKeysDescriptors !== null) {
409
            return $this->directForeignKeysDescriptors;
410
        }
411
        $fks = $this->tdbmSchemaAnalyzer->getIncomingForeignKeys($this->table->getName());
412
413
        $descriptors = [];
414
415
        foreach ($fks as $fk) {
416
            $desc = new DirectForeignKeyMethodDescriptor($fk, $this->table, $this->namingStrategy, $this->annotationParser, $this->beanNamespace, $this->resultIteratorNamespace);
417
            $this->checkForDuplicate($desc);
418
            $descriptors[] = $desc;
419
        }
420
421
        $this->directForeignKeysDescriptors = $descriptors;
422
        return $this->directForeignKeysDescriptors;
423
    }
424
425
    /**
426
     * @return PivotTableMethodsDescriptor[]
427
     */
428
    private function getPivotTableDescriptors(): array
429
    {
430
        if ($this->pivotTableDescriptors !== null) {
431
            return $this->pivotTableDescriptors;
432
        }
433
        $descs = [];
434
        foreach ($this->schemaAnalyzer->detectJunctionTables(true) as $table) {
435
            // There are exactly 2 FKs since this is a pivot table.
436
            $fks = array_values($table->getForeignKeys());
437
438
            if ($fks[0]->getForeignTableName() === $this->table->getName()) {
439
                list($localFk, $remoteFk) = $fks;
440
                $desc = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->annotationParser, $this->beanNamespace, $this->resultIteratorNamespace);
441
                $this->checkForDuplicate($desc);
442
                $descs[] = $desc;
443
            }
444
            if ($fks[1]->getForeignTableName() === $this->table->getName()) {
445
                list($remoteFk, $localFk) = $fks;
446
                $desc = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->annotationParser, $this->beanNamespace, $this->resultIteratorNamespace);
447
                $this->checkForDuplicate($desc);
448
                $descs[] = $desc;
449
            }
450
        }
451
452
        $this->pivotTableDescriptors = $descs;
453
        return $this->pivotTableDescriptors;
454
    }
455
456
    /**
457
     * Check the method name isn't already used and flag the associated descriptors to use their alternative names if it is the case
458
     */
459
    private function checkForDuplicate(MethodDescriptorInterface $descriptor): void
460
    {
461
        $name = $descriptor->getName();
462
        if (!isset($this->descriptorsByMethodName[$name])) {
463
            $this->descriptorsByMethodName[$name] = [];
464
        }
465
        $this->descriptorsByMethodName[$name][] = $descriptor;
466
        if (count($this->descriptorsByMethodName[$name]) > 1) {
467
            foreach ($this->descriptorsByMethodName[$name] as $duplicateDescriptor) {
468
                $duplicateDescriptor->useAlternativeName();
469
            }
470
        }
471
    }
472
473
    /**
474
     * Returns the list of method descriptors (and applies the alternative name if needed).
475
     *
476
     * @return RelationshipMethodDescriptorInterface[]
477
     */
478
    public function getMethodDescriptors(): array
479
    {
480
        $directForeignKeyDescriptors = $this->getDirectForeignKeysDescriptors();
481
        $pivotTableDescriptors = $this->getPivotTableDescriptors();
482
483
        return array_merge($directForeignKeyDescriptors, $pivotTableDescriptors);
484
    }
485
486
    public function generateJsonSerialize(): MethodGenerator
487
    {
488
        $tableName = $this->table->getName();
489
        $parentFk = $this->schemaAnalyzer->getParentRelationship($tableName);
490
491
        $method = new MethodGenerator('jsonSerialize');
492
        $method->setDocBlock('Serializes the object for JSON encoding.');
493
        $method->getDocBlock()->setTag(new ParamTag('$stopRecursion', ['bool'], 'Parameter used internally by TDBM to stop embedded objects from embedding other objects.'));
494
        $method->getDocBlock()->setTag(new ReturnTag(['array']));
495
        $method->setParameter(new ParameterGenerator('stopRecursion', 'bool', false));
496
497
        if ($parentFk !== null) {
498
            $body = '$array = parent::jsonSerialize($stopRecursion);';
499
        } else {
500
            $body = '$array = [];';
501
        }
502
503
        foreach ($this->getExposedProperties() as $beanPropertyDescriptor) {
504
            $propertyCode = $beanPropertyDescriptor->getJsonSerializeCode();
505
            if (!empty($propertyCode)) {
506
                $body .= PHP_EOL . $propertyCode;
507
            }
508
        }
509
510
        // Many2many relationships
511
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
512
            $methodCode = $methodDescriptor->getJsonSerializeCode();
513
            if (!empty($methodCode)) {
514
                $body .= PHP_EOL . $methodCode;
515
            }
516
        }
517
518
        $body .= PHP_EOL . 'return $array;';
519
520
        $method->setBody($body);
521
522
        return $method;
523
    }
524
525
    /**
526
     * Returns as an array the class we need to extend from and the list of use statements.
527
     *
528
     * @param ForeignKeyConstraint|null $parentFk
529
     * @return string[]
530
     */
531
    private function generateExtendsAndUseStatements(ForeignKeyConstraint $parentFk = null): array
532
    {
533
        $classes = [];
534
        if ($parentFk !== null) {
535
            $extends = $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName());
536
            $classes[] = $extends;
537
        }
538
539
        foreach ($this->getBeanPropertyDescriptors() as $beanPropertyDescriptor) {
540
            $className = $beanPropertyDescriptor->getClassName();
541
            if (null !== $className) {
542
                $classes[] = $className;
543
            }
544
        }
545
546
        foreach ($this->getMethodDescriptors() as $descriptor) {
547
            $classes = array_merge($classes, $descriptor->getUsedClasses());
548
        }
549
550
        $classes = array_unique($classes);
551
552
        return $classes;
553
    }
554
555
    /**
556
     * Returns the representation of the PHP bean file with all getters and setters.
557
     *
558
     * @return ?FileGenerator
559
     */
560
    public function generatePhpCode(): ?FileGenerator
561
    {
562
        $file = new FileGenerator();
563
        $class = new ClassGenerator();
564
        $class->setAbstract(true);
565
        $file->setClass($class);
566
        $file->setNamespace($this->generatedBeanNamespace);
567
568
        $tableName = $this->table->getName();
569
        $baseClassName = $this->namingStrategy->getBaseBeanClassName($tableName);
570
        $className = $this->namingStrategy->getBeanClassName($tableName);
571
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
572
573
        $classes = $this->generateExtendsAndUseStatements($parentFk);
574
575
        foreach ($classes as $useClass) {
576
            $file->setUse($this->beanNamespace.'\\'.$useClass);
577
        }
578
579
        /*$uses = array_map(function ($className) {
580
            return 'use '.$this->beanNamespace.'\\'.$className.";\n";
581
        }, $classes);
582
        $use = implode('', $uses);*/
583
584
        $extends = $this->getExtendedBeanClassName();
585
        if ($extends === null) {
586
            $class->setExtendedClass(AbstractTDBMObject::class);
587
            $file->setUse(AbstractTDBMObject::class);
588
        } else {
589
            $class->setExtendedClass($extends);
590
        }
591
592
        $file->setUse(ResultIterator::class);
593
        $file->setUse(AlterableResultIterator::class);
594
        $file->setUse(Uuid::class);
595
        $file->setUse(JsonSerializable::class);
596
        $file->setUse(ForeignKeys::class);
597
598
        $class->setName($baseClassName);
599
600
        $file->setDocBlock(new DocBlockGenerator(
601
            'This file has been automatically generated by TDBM.',
602
            <<<EOF
603
DO NOT edit this file, as it might be overwritten.
604
If you need to perform changes, edit the $className class instead!
605
EOF
606
        ));
607
608
        $class->setDocBlock(new DocBlockGenerator("The $baseClassName class maps the '$tableName' table in database."));
609
610
        /** @var AddInterface[] $addInterfaceAnnotations */
611
        $addInterfaceAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations(AddInterface::class);
612
613
        $interfaces = [ JsonSerializable::class ];
614
        foreach ($addInterfaceAnnotations as $annotation) {
615
            $interfaces[] = $annotation->getName();
616
        }
617
618
        $class->setImplementedInterfaces($interfaces);
619
620
        $this->registerTraits($class, AddTrait::class);
621
622
        $method = $this->generateBeanConstructor();
623
        $method = $this->codeGeneratorListener->onBaseBeanConstructorGenerated($method, $this, $this->configuration, $class);
624
        if ($method) {
625
            $class->addMethodFromGenerator($this->generateBeanConstructor());
626
        }
627
628
        $fks = [];
629
        foreach ($this->getExposedProperties() as $property) {
630
            if ($property instanceof ObjectBeanPropertyDescriptor) {
631
                $fks[] = $property->getForeignKey();
632
            }
633
            [$getter, $setter] = $property->getGetterSetterCode();
634
            [$getter, $setter] = $this->codeGeneratorListener->onBaseBeanPropertyGenerated($getter, $setter, $property, $this, $this->configuration, $class);
635
            if ($getter !== null) {
636
                $class->addMethodFromGenerator($getter);
637
            }
638
            if ($setter !== null) {
639
                $class->addMethodFromGenerator($setter);
640
            }
641
        }
642
643
        $pivotTableMethodsDescriptors = [];
644
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
645
            if ($methodDescriptor instanceof DirectForeignKeyMethodDescriptor) {
646
                [$method] = $methodDescriptor->getCode();
647
                $method = $this->codeGeneratorListener->onBaseBeanOneToManyGenerated($method, $methodDescriptor, $this, $this->configuration, $class);
648
                if ($method) {
649
                    $class->addMethodFromGenerator($method);
650
                }
651
            } elseif ($methodDescriptor instanceof PivotTableMethodsDescriptor) {
652
                $pivotTableMethodsDescriptors[] = $methodDescriptor;
653
                [ $getter, $adder, $remover, $has, $setter ] = $methodDescriptor->getCode();
654
                $methods = $this->codeGeneratorListener->onBaseBeanManyToManyGenerated($getter, $adder, $remover, $has, $setter, $methodDescriptor, $this, $this->configuration, $class);
655
                foreach ($methods as $method) {
656
                    if ($method) {
657
                        $class->addMethodFromGenerator($method);
658
                    }
659
                }
660
            } else {
661
                throw new \RuntimeException('Unexpected instance'); // @codeCoverageIgnore
662
            }
663
        }
664
665
        $manyToManyRelationshipCode = $this->generateGetManyToManyRelationshipDescriptorCode($pivotTableMethodsDescriptors);
666
        if ($manyToManyRelationshipCode !== null) {
667
            $class->addMethodFromGenerator($manyToManyRelationshipCode);
668
        }
669
        $manyToManyRelationshipKeysCode = $this->generateGetManyToManyRelationshipDescriptorKeysCode($pivotTableMethodsDescriptors);
670
        if ($manyToManyRelationshipKeysCode !== null) {
671
            $class->addMethodFromGenerator($manyToManyRelationshipKeysCode);
672
        }
673
674
        $foreignKeysProperty = new PropertyGenerator('foreignKeys');
675
        $foreignKeysProperty->setStatic(true);
676
        $foreignKeysProperty->setVisibility(AbstractMemberGenerator::VISIBILITY_PRIVATE);
677
        $foreignKeysProperty->setDocBlock(new DocBlockGenerator(null, null, [new VarTag(null, ['\\'.ForeignKeys::class])]));
678
        $class->addPropertyFromGenerator($foreignKeysProperty);
679
680
        $method = $this->generateGetForeignKeys($fks);
681
        $class->addMethodFromGenerator($method);
682
683
        $method = $this->generateJsonSerialize();
684
        $method = $this->codeGeneratorListener->onBaseBeanJsonSerializeGenerated($method, $this, $this->configuration, $class);
685
        if ($method !== null) {
686
            $class->addMethodFromGenerator($method);
687
        }
688
689
        $class->addMethodFromGenerator($this->generateGetUsedTablesCode());
690
        $onDeleteCode = $this->generateOnDeleteCode();
691
        if ($onDeleteCode) {
692
            $class->addMethodFromGenerator($onDeleteCode);
693
        }
694
        $cloneCode = $this->generateCloneCode($pivotTableMethodsDescriptors);
695
        $cloneCode = $this->codeGeneratorListener->onBaseBeanCloneGenerated($cloneCode, $this, $this->configuration, $class);
696
        if ($cloneCode) {
697
            $class->addMethodFromGenerator($cloneCode);
698
        }
699
700
        $file = $this->codeGeneratorListener->onBaseBeanGenerated($file, $this, $this->configuration);
701
702
        return $file;
703
    }
704
705
    private function registerTraits(ClassGenerator $class, string $annotationClass): void
706
    {
707
        /** @var AbstractTraitAnnotation[] $addTraitAnnotations */
708
        $addTraitAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations($annotationClass);
709
710
        foreach ($addTraitAnnotations as $annotation) {
711
            $class->addTrait($annotation->getName());
712
        }
713
714
        foreach ($addTraitAnnotations as $annotation) {
715
            foreach ($annotation->getInsteadOf() as $method => $replacedTrait) {
716
                $class->addTraitOverride($method, $replacedTrait);
717
            }
718
            foreach ($annotation->getAs() as $method => $replacedMethod) {
719
                $class->addTraitAlias($method, $replacedMethod);
720
            }
721
        }
722
    }
723
724
    /**
725
     * Writes the representation of the PHP DAO file.
726
     *
727
     * @return ?FileGenerator
728
     */
729
    public function generateDaoPhpCode(): ?FileGenerator
730
    {
731
        $file = new FileGenerator();
732
        $class = new ClassGenerator();
733
        $class->setAbstract(true);
734
        $file->setClass($class);
735
        $file->setNamespace($this->generatedDaoNamespace);
736
737
        $tableName = $this->table->getName();
738
739
        $primaryKeyColumns = TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($this->table);
740
741
        list($defaultSort, $defaultSortDirection) = $this->getDefaultSortColumnFromAnnotation($this->table);
742
743
        $className = $this->namingStrategy->getDaoClassName($tableName);
744
        $baseClassName = $this->namingStrategy->getBaseDaoClassName($tableName);
745
        $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName);
746
        $beanClassName = $this->beanNamespace.'\\'.$beanClassWithoutNameSpace;
747
        $resultIteratorClassWithoutNameSpace = $this->getResultIteratorClassName();
748
        $resultIteratorClass = $this->resultIteratorNamespace.'\\'.$resultIteratorClassWithoutNameSpace;
749
750
        $findByDaoCodeMethods = $this->generateFindByDaoCode($this->beanNamespace, $beanClassWithoutNameSpace, $class);
751
752
        $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...
753
        // Let's suppress duplicates in used beans (if any)
754
        $usedBeans = array_flip(array_flip($usedBeans));
755
        foreach ($usedBeans as $usedBean) {
756
            $class->addUse($usedBean);
757
        }
758
759
        $file->setDocBlock(new DocBlockGenerator(
760
            <<<EOF
761
This file has been automatically generated by TDBM.
762
DO NOT edit this file, as it might be overwritten.
763
If you need to perform changes, edit the $className class instead!
764
EOF
765
        ));
766
767
        $file->setNamespace($this->generatedDaoNamespace);
768
769
        $class->addUse(TDBMService::class);
770
        $class->addUse(ResultIterator::class);
771
        $class->addUse(TDBMException::class);
772
773
        $class->setName($baseClassName);
774
775
        $class->setDocBlock(new DocBlockGenerator("The $baseClassName class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table."));
776
777
        /** @var AddInterfaceOnDao[] $addInterfaceOnDaoAnnotations */
778
        $addInterfaceOnDaoAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations(AddInterfaceOnDao::class);
779
780
        $interfaces = [];
781
        foreach ($addInterfaceOnDaoAnnotations as $annotation) {
782
            $interfaces[] = $annotation->getName();
783
        }
784
785
        $class->setImplementedInterfaces($interfaces);
786
787
        $this->registerTraits($class, AddTraitOnDao::class);
788
789
        $tdbmServiceProperty = new PropertyGenerator('tdbmService');
790
        $tdbmServiceProperty->setDocBlock(new DocBlockGenerator(null, null, [new VarTag(null, ['\\'.TDBMService::class])]));
791
        $class->addPropertyFromGenerator($tdbmServiceProperty);
792
793
        $defaultSortProperty = new PropertyGenerator('defaultSort', $defaultSort);
794
        $defaultSortProperty->setDocBlock(new DocBlockGenerator('The default sort column.', null, [new VarTag(null, ['string', 'null'])]));
795
        $class->addPropertyFromGenerator($defaultSortProperty);
796
797
        $defaultSortPropertyDirection = new PropertyGenerator('defaultDirection', $defaultSort && $defaultSortDirection ? $defaultSortDirection : 'asc');
798
        $defaultSortPropertyDirection->setDocBlock(new DocBlockGenerator('The default sort direction.', null, [new VarTag(null, ['string'])]));
799
        $class->addPropertyFromGenerator($defaultSortPropertyDirection);
800
801
        $constructorMethod = new MethodGenerator(
802
            '__construct',
803
            [ new ParameterGenerator('tdbmService', TDBMService::class) ],
804
            MethodGenerator::FLAG_PUBLIC,
805
            '$this->tdbmService = $tdbmService;',
806
            'Sets the TDBM service used by this DAO.'
807
        );
808
        $constructorMethod = $this->codeGeneratorListener->onBaseDaoConstructorGenerated($constructorMethod, $this, $this->configuration, $class);
809
        if ($constructorMethod !== null) {
810
            $class->addMethodFromGenerator($constructorMethod);
811
        }
812
813
        $saveMethod = new MethodGenerator(
814
            'save',
815
            [ new ParameterGenerator('obj', $beanClassName) ],
816
            MethodGenerator::FLAG_PUBLIC,
817
            '$this->tdbmService->save($obj);',
818
            (new DocBlockGenerator(
819
                "Persist the $beanClassWithoutNameSpace instance.",
820
                null,
821
                [
822
                    new ParamTag('obj', [$beanClassWithoutNameSpace], 'The bean to save.')
823
                ]
824
            ))->setWordWrap(false)
825
        );
826
        $saveMethod->setReturnType('void');
827
828
        $saveMethod = $this->codeGeneratorListener->onBaseDaoSaveGenerated($saveMethod, $this, $this->configuration, $class);
829
        if ($saveMethod !== null) {
830
            $class->addMethodFromGenerator($saveMethod);
831
        }
832
833
        $findAllBody = <<<EOF
834
if (\$this->defaultSort) {
835
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
836
} else {
837
    \$orderBy = null;
838
}
839
return \$this->tdbmService->findObjects('$tableName', null, [], \$orderBy, [], null, \\$beanClassName::class, \\$resultIteratorClass::class);
840
EOF;
841
842
        $findAllMethod = new MethodGenerator(
843
            'findAll',
844
            [],
845
            MethodGenerator::FLAG_PUBLIC,
846
            $findAllBody,
847
            (new DocBlockGenerator("Get all $beanClassWithoutNameSpace records."))->setWordWrap(false)
848
        );
849
        $findAllMethod->setReturnType($resultIteratorClass);
850
        $findAllMethod = $this->codeGeneratorListener->onBaseDaoFindAllGenerated($findAllMethod, $this, $this->configuration, $class);
851
        if ($findAllMethod !== null) {
852
            $class->addMethodFromGenerator($findAllMethod);
853
        }
854
855
        if (count($primaryKeyColumns) > 0) {
856
            $lazyLoadingParameterName = 'lazyLoading';
857
            $parameters = [];
858
            $parametersTag = [];
859
            $primaryKeyFilter = [];
860
861
            foreach ($primaryKeyColumns as $primaryKeyColumn) {
862
                if ($primaryKeyColumn === $lazyLoadingParameterName) {
863
                    throw new TDBMException('Primary Column name `' . $lazyLoadingParameterName . '` is not allowed.');
864
                }
865
                $phpType = TDBMDaoGenerator::dbalTypeToPhpType($this->table->getColumn($primaryKeyColumn)->getType());
866
                $parameters[] = new ParameterGenerator($primaryKeyColumn, $phpType);
867
                $parametersTag[] = new ParamTag($primaryKeyColumn, [$phpType]);
868
                $primaryKeyFilter[] = "'$primaryKeyColumn' => \$$primaryKeyColumn";
869
            }
870
            $parameters[] = new ParameterGenerator($lazyLoadingParameterName, 'bool', false);
871
            $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.');
872
            $parametersTag[] = new ReturnTag(['\\'.$beanClassName]);
873
            $parametersTag[] = new ThrowsTag('\\'.TDBMException::class);
874
875
            $getByIdMethod = new MethodGenerator(
876
                'getById',
877
                $parameters,
878
                MethodGenerator::FLAG_PUBLIC,
879
                "return \$this->tdbmService->findObjectByPk('$tableName', [" . implode(', ', $primaryKeyFilter) . "], [], \$$lazyLoadingParameterName, \\$beanClassName::class, \\$resultIteratorClass::class);",
880
                (new DocBlockGenerator(
881
                    "Get $beanClassWithoutNameSpace specified by its ID (its primary key).",
882
                    'If the primary key does not exist, an exception is thrown.',
883
                    $parametersTag
884
                ))->setWordWrap(false)
885
            );
886
            $getByIdMethod->setReturnType($beanClassName);
887
            $getByIdMethod = $this->codeGeneratorListener->onBaseDaoGetByIdGenerated($getByIdMethod, $this, $this->configuration, $class);
888
            if ($getByIdMethod) {
889
                $class->addMethodFromGenerator($getByIdMethod);
890
            }
891
        }
892
893
        $deleteMethodBody = <<<EOF
894
if (\$cascade === true) {
895
    \$this->tdbmService->deleteCascade(\$obj);
896
} else {
897
    \$this->tdbmService->delete(\$obj);
898
}
899
EOF;
900
901
902
        $deleteMethod = new MethodGenerator(
903
            'delete',
904
            [
905
                new ParameterGenerator('obj', $beanClassName),
906
                new ParameterGenerator('cascade', 'bool', false)
907
            ],
908
            MethodGenerator::FLAG_PUBLIC,
909
            $deleteMethodBody,
910
            (new DocBlockGenerator(
911
                "Get all $beanClassWithoutNameSpace records.",
912
                null,
913
                [
914
                    new ParamTag('obj', ['\\'.$beanClassName], 'The object to delete'),
915
                    new ParamTag('cascade', ['bool'], 'If true, it will delete all objects linked to $obj'),
916
                ]
917
            ))->setWordWrap(false)
918
        );
919
        $deleteMethod->setReturnType('void');
920
        $deleteMethod = $this->codeGeneratorListener->onBaseDaoDeleteGenerated($deleteMethod, $this, $this->configuration, $class);
921
        if ($deleteMethod !== null) {
922
            $class->addMethodFromGenerator($deleteMethod);
923
        }
924
925
        $findMethodBody = <<<EOF
926
if (\$this->defaultSort && \$orderBy == null) {
927
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
928
}
929
return \$this->tdbmService->findObjects('$tableName', \$filter, \$parameters, \$orderBy, \$additionalTablesFetch, \$mode, \\$beanClassName::class, \\$resultIteratorClass::class);
930
EOF;
931
932
933
        $findMethod = new MethodGenerator(
934
            'find',
935
            [
936
                (new ParameterGenerator('filter'))->setDefaultValue(null),
937
                new ParameterGenerator('parameters', 'array', []),
938
                (new ParameterGenerator('orderBy'))->setDefaultValue(null),
939
                new ParameterGenerator('additionalTablesFetch', 'array', []),
940
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
941
            ],
942
            MethodGenerator::FLAG_PROTECTED,
943
            $findMethodBody,
944
            (new DocBlockGenerator(
945
                "Get all $beanClassWithoutNameSpace records.",
946
                null,
947
                [
948
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
949
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
950
                    new ParamTag('orderBy', ['mixed'], 'The order string'),
951
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
952
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.')
953
                ]
954
            ))->setWordWrap(false)
955
        );
956
        $findMethod->setReturnType($resultIteratorClass);
957
        $findMethod = $this->codeGeneratorListener->onBaseDaoFindGenerated($findMethod, $this, $this->configuration, $class);
958
        if ($findMethod !== null) {
959
            $class->addMethodFromGenerator($findMethod);
960
        }
961
962
        $findFromSqlMethodBody = <<<EOF
963
if (\$this->defaultSort && \$orderBy == null) {
964
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
965
}
966
return \$this->tdbmService->findObjectsFromSql('$tableName', \$from, \$filter, \$parameters, \$orderBy, \$mode, \\$beanClassName::class, \\$resultIteratorClass::class);
967
EOF;
968
969
        $findFromSqlMethod = new MethodGenerator(
970
            'findFromSql',
971
            [
972
                new ParameterGenerator('from', 'string'),
973
                (new ParameterGenerator('filter'))->setDefaultValue(null),
974
                new ParameterGenerator('parameters', 'array', []),
975
                (new ParameterGenerator('orderBy'))->setDefaultValue(null),
976
                new ParameterGenerator('additionalTablesFetch', 'array', []),
977
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
978
            ],
979
            MethodGenerator::FLAG_PROTECTED,
980
            $findFromSqlMethodBody,
981
            (new DocBlockGenerator(
982
                "Get a list of $beanClassWithoutNameSpace specified by its filters.",
983
                "Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part.
984
985
You should not put an alias on the main table name. So your \$from variable should look like:
986
987
   \"$tableName JOIN ... ON ...\"",
988
                [
989
                    new ParamTag('from', ['string'], 'The sql from statement'),
990
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
991
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
992
                    new ParamTag('orderBy', ['mixed'], 'The order string'),
993
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
994
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.')
995
                ]
996
            ))->setWordWrap(false)
997
        );
998
        $findFromSqlMethod->setReturnType($resultIteratorClass);
999
        $findFromSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromSqlGenerated($findFromSqlMethod, $this, $this->configuration, $class);
1000
        if ($findFromSqlMethod !== null) {
1001
            $class->addMethodFromGenerator($findFromSqlMethod);
1002
        }
1003
1004
        $findFromRawSqlMethodBody = <<<EOF
1005
return \$this->tdbmService->findObjectsFromRawSql('$tableName', \$sql, \$parameters, \$mode, \\$beanClassName::class, \$countSql, \\$resultIteratorClass::class);
1006
EOF;
1007
1008
        $findFromRawSqlMethod = new MethodGenerator(
1009
            'findFromRawSql',
1010
            [
1011
                new ParameterGenerator('sql', 'string'),
1012
                new ParameterGenerator('parameters', 'array', []),
1013
                (new ParameterGenerator('countSql', '?string'))->setDefaultValue(null),
1014
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
1015
            ],
1016
            MethodGenerator::FLAG_PROTECTED,
1017
            $findFromRawSqlMethodBody,
1018
            (new DocBlockGenerator(
1019
                "Get a list of $beanClassWithoutNameSpace from a SQL query.",
1020
                "Unlike the `find` and `findFromSql` methods, here you can pass the whole \$sql query.
1021
1022
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:
1023
1024
   \"SELECT $tableName .* FROM ...\"",
1025
                [
1026
                    new ParamTag('sql', ['string'], 'The sql query'),
1027
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the query'),
1028
                    new ParamTag('countSql', ['string', 'null'], 'The sql query that provides total count of rows (automatically computed if not provided)'),
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
        $findFromRawSqlMethod->setReturnType($resultIteratorClass);
1034
        $findFromRawSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromRawSqlGenerated($findFromRawSqlMethod, $this, $this->configuration, $class);
1035
        if ($findFromRawSqlMethod !== null) {
1036
            $class->addMethodFromGenerator($findFromRawSqlMethod);
1037
        }
1038
1039
        $findOneMethodBody = <<<EOF
1040
return \$this->tdbmService->findObject('$tableName', \$filter, \$parameters, \$additionalTablesFetch, \\$beanClassName::class, \\$resultIteratorClass::class);
1041
EOF;
1042
1043
1044
        $findOneMethod = new MethodGenerator(
1045
            'findOne',
1046
            [
1047
                (new ParameterGenerator('filter'))->setDefaultValue(null),
1048
                new ParameterGenerator('parameters', 'array', []),
1049
                new ParameterGenerator('additionalTablesFetch', 'array', []),
1050
            ],
1051
            MethodGenerator::FLAG_PROTECTED,
1052
            $findOneMethodBody,
1053
            (new DocBlockGenerator(
1054
                "Get a single $beanClassWithoutNameSpace specified by its filters.",
1055
                null,
1056
                [
1057
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
1058
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
1059
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
1060
                    new ReturnTag(['\\'.$beanClassName, 'null'])
1061
                ]
1062
            ))->setWordWrap(false)
1063
        );
1064
        $findOneMethod->setReturnType("?$beanClassName");
1065
        $findOneMethod = $this->codeGeneratorListener->onBaseDaoFindOneGenerated($findOneMethod, $this, $this->configuration, $class);
1066
        if ($findOneMethod !== null) {
1067
            $class->addMethodFromGenerator($findOneMethod);
1068
        }
1069
1070
        $findOneFromSqlMethodBody = <<<EOF
1071
return \$this->tdbmService->findObjectFromSql('$tableName', \$from, \$filter, \$parameters, \\$beanClassName::class, \\$resultIteratorClass::class);
1072
EOF;
1073
1074
        $findOneFromSqlMethod = new MethodGenerator(
1075
            'findOneFromSql',
1076
            [
1077
                new ParameterGenerator('from', 'string'),
1078
                (new ParameterGenerator('filter'))->setDefaultValue(null),
1079
                new ParameterGenerator('parameters', 'array', []),
1080
            ],
1081
            MethodGenerator::FLAG_PROTECTED,
1082
            $findOneFromSqlMethodBody,
1083
            (new DocBlockGenerator(
1084
                "Get a single $beanClassWithoutNameSpace specified by its filters.",
1085
                "Unlike the `findOne` method that guesses the FROM part of the statement, here you can pass the \$from part.
1086
1087
You should not put an alias on the main table name. So your \$from variable should look like:
1088
1089
    \"$tableName JOIN ... ON ...\"",
1090
                [
1091
                    new ParamTag('from', ['string'], 'The sql from statement'),
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 ReturnTag(['\\'.$beanClassName, 'null'])
1095
                ]
1096
            ))->setWordWrap(false)
1097
        );
1098
        $findOneFromSqlMethod->setReturnType("?$beanClassName");
1099
        $findOneFromSqlMethod = $this->codeGeneratorListener->onBaseDaoFindOneFromSqlGenerated($findOneFromSqlMethod, $this, $this->configuration, $class);
1100
        if ($findOneFromSqlMethod !== null) {
1101
            $class->addMethodFromGenerator($findOneFromSqlMethod);
1102
        }
1103
1104
1105
        $setDefaultSortMethod = new MethodGenerator(
1106
            'setDefaultSort',
1107
            [
1108
                new ParameterGenerator('defaultSort', 'string'),
1109
            ],
1110
            MethodGenerator::FLAG_PUBLIC,
1111
            '$this->defaultSort = $defaultSort;',
1112
            new DocBlockGenerator(
1113
                "Sets the default column for default sorting.",
1114
                null,
1115
                [
1116
                    new ParamTag('defaultSort', ['string']),
1117
                ]
1118
            )
1119
        );
1120
        $setDefaultSortMethod->setReturnType('void');
1121
        $setDefaultSortMethod = $this->codeGeneratorListener->onBaseDaoSetDefaultSortGenerated($setDefaultSortMethod, $this, $this->configuration, $class);
1122
        if ($setDefaultSortMethod !== null) {
1123
            $class->addMethodFromGenerator($setDefaultSortMethod);
1124
        }
1125
1126
        foreach ($findByDaoCodeMethods as $method) {
1127
            $class->addMethodFromGenerator($method);
1128
        }
1129
1130
        $file = $this->codeGeneratorListener->onBaseDaoGenerated($file, $this, $this->configuration);
1131
1132
        return $file;
1133
    }
1134
1135
    /**
1136
     * Writes the representation of the PHP ResultIterator file.
1137
     */
1138
    public function generateResultIteratorPhpCode(): ?FileGenerator
1139
    {
1140
        $file = new FileGenerator();
1141
        $class = new ClassGenerator();
1142
        $class->setAbstract(true);
1143
        $file->setClass($class);
1144
        $file->setNamespace($this->generatedResultIteratorNamespace);
1145
1146
        $tableName = $this->table->getName();
1147
1148
        $classNameWithoutNamespace = $this->namingStrategy->getResultIteratorClassName($tableName);
1149
        $baseClassName = $this->namingStrategy->getBaseResultIteratorClassName($tableName);
1150
        $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName);
1151
        $beanClassName = $this->beanNamespace.'\\'.$beanClassWithoutNameSpace;
1152
1153
        $file->setDocBlock(new DocBlockGenerator(
1154
            <<<EOF
1155
This file has been automatically generated by TDBM.
1156
DO NOT edit this file, as it might be overwritten.
1157
If you need to perform changes, edit the $classNameWithoutNamespace class instead!
1158
EOF
1159
        ));
1160
        $class->setName($baseClassName);
1161
        $extends = $this->getExtendedResultIteratorClassName();
1162
        if ($extends === null) {
1163
            $class->addUse(ResultIterator::class);
1164
            $class->setExtendedClass(ResultIterator::class);
1165
        } else {
1166
            $class->addUse($this->resultIteratorNamespace . '\\' . $extends);
1167
            $class->setExtendedClass($extends);
1168
        }
1169
1170
        $class->setDocBlock((new DocBlockGenerator(
1171
            "The $baseClassName class will iterate over results of $beanClassWithoutNameSpace class.",
1172
            null,
1173
            [new Tag\MethodTag('getIterator', ['\\' . $beanClassName . '[]'])]
1174
        ))->setWordWrap(false));
1175
1176
        $columnsWithWhitelist = [];
1177
        foreach ($this->table->getColumns() as $column) {
1178
            $columnsWithWhitelist[$column->getName()] = $column->getType()->getBindingType() !== ParameterType::LARGE_OBJECT; // Should "not null" values be included too ?
1179
        }
1180
        $whitelistProperty = new PropertyGenerator('whitelist');
1181
        $whitelistProperty->setVisibility(AbstractMemberGenerator::VISIBILITY_PRIVATE);
1182
        $whitelistProperty->setDocBlock(new DocBlockGenerator(
1183
            'Columns to fetch from db',
1184
            null,
1185
            [new VarTag(null,'array<string, bool>', 'Associative array indexed by columns')]
1186
        ));
1187
        $whitelistProperty->setDefaultValue(new PropertyValueGenerator($columnsWithWhitelist));
1188
        $class->addPropertyFromGenerator($whitelistProperty);
1189
1190
        $whitelistAddMethod = new MethodGenerator(
1191
            'addToWhitelist',
1192
            [new ParameterGenerator('column', 'string'), new ParameterGenerator('table', 'string', $tableName)],
1193
            MethodGenerator::FLAG_PROTECTED,
1194
            <<<PHP
1195
if (\$table === '$tableName') {
1196
    \$this->whitelist[\$column] = true;
1197
} else {
1198
    parent::addToWhitelist(\$column, \$table);
1199
}
1200
PHP
1201
        );
1202
        $whitelistAddMethod->setReturnType('void');
1203
        $whitelistRemoveMethod = new MethodGenerator(
1204
            'removeFromWhitelist',
1205
            [new ParameterGenerator('column', 'string'), new ParameterGenerator('table', 'string', $tableName)],
1206
            MethodGenerator::FLAG_PROTECTED,
1207
            <<<PHP
1208
if (\$table === '$tableName') {
1209
    \$this->whitelist[\$column] = false;
1210
} else {
1211
    parent::removeFromWhitelist(\$column, \$table);
1212
}
1213
PHP
1214
        );
1215
        $whitelistRemoveMethod->setReturnType('void');
1216
        $whitelistHasMethod = new MethodGenerator(
1217
            'isInWhitelist',
1218
            [new ParameterGenerator('column', 'string'), new ParameterGenerator('table', 'string', $tableName)],
1219
            MethodGenerator::FLAG_PUBLIC,
1220
            <<<PHP
1221
if (\$table === '$tableName') {
1222
    return \$this->whitelist[\$column];
1223
}
1224
return parent::isInWhitelist(\$column, \$table);
1225
PHP
1226
        );
1227
        $whitelistHasMethod->setReturnType('bool');
1228
        $class->addMethodFromGenerator($whitelistAddMethod);
1229
        $class->addMethodFromGenerator($whitelistRemoveMethod);
1230
        $class->addMethodFromGenerator($whitelistHasMethod);
1231
1232
        $file = $this->codeGeneratorListener->onBaseResultIteratorGenerated($file, $this, $this->configuration);
1233
1234
        return $file;
1235
    }
1236
1237
    /**
1238
     * Tries to find a @defaultSort annotation in one of the columns.
1239
     *
1240
     * @param Table $table
1241
     *
1242
     * @return mixed[] First item: column name, Second item: column order (asc/desc)
1243
     */
1244
    private function getDefaultSortColumnFromAnnotation(Table $table): array
1245
    {
1246
        $defaultSort = null;
1247
        $defaultSortDirection = null;
1248
        foreach ($table->getColumns() as $column) {
1249
            $comments = $column->getComment();
1250
            $matches = [];
1251
            if ($comments !== null && preg_match('/@defaultSort(\((desc|asc)\))*/', $comments, $matches) != 0) {
1252
                $defaultSort = $column->getName();
1253
                if (count($matches) === 3) {
1254
                    $defaultSortDirection = $matches[2];
1255
                } else {
1256
                    $defaultSortDirection = 'ASC';
1257
                }
1258
            }
1259
        }
1260
1261
        return [$defaultSort, $defaultSortDirection];
1262
    }
1263
1264
    /**
1265
     * @param string $beanNamespace
1266
     * @param string $beanClassName
1267
     *
1268
     * @return MethodGenerator[]
1269
     */
1270
    private function generateFindByDaoCode(string $beanNamespace, string $beanClassName, ClassGenerator $class): array
1271
    {
1272
        $methods = [];
1273
        foreach ($this->removeDuplicateIndexes($this->table->getIndexes()) as $index) {
1274
            if (!$index->isPrimary()) {
1275
                $method = $this->generateFindByDaoCodeForIndex($index, $beanNamespace, $beanClassName);
1276
1277
                if ($method !== null) {
1278
                    $method = $this->codeGeneratorListener->onBaseDaoFindByIndexGenerated($method, $index, $this, $this->configuration, $class);
1279
                    if ($method !== null) {
1280
                        $methods[] = $method;
1281
                    }
1282
                }
1283
            }
1284
        }
1285
        usort($methods, static function (MethodGenerator $methodA, MethodGenerator $methodB) {
1286
            return $methodA->getName() <=> $methodB->getName();
1287
        });
1288
1289
        return $methods;
1290
    }
1291
1292
    /**
1293
     * Remove identical indexes (indexes on same columns)
1294
     *
1295
     * @param Index[] $indexes
1296
     * @return Index[]
1297
     */
1298
    private function removeDuplicateIndexes(array $indexes): array
1299
    {
1300
        $indexesByKey = [];
1301
        foreach ($indexes as $index) {
1302
            $key = implode('__`__', $index->getUnquotedColumns());
1303
            // Unique Index have precedence over non unique one
1304
            if (!isset($indexesByKey[$key]) || $index->isUnique()) {
1305
                $indexesByKey[$key] = $index;
1306
            }
1307
        }
1308
1309
        return array_values($indexesByKey);
1310
    }
1311
1312
    /**
1313
     * @param Index  $index
1314
     * @param string $beanNamespace
1315
     * @param string $beanClassName
1316
     *
1317
     * @return MethodGenerator|null
1318
     */
1319
    private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespace, string $beanClassName): ?MethodGenerator
1320
    {
1321
        $columns = $index->getColumns();
1322
        $usedBeans = [];
1323
1324
        /**
1325
         * The list of elements building this index (expressed as columns or foreign keys)
1326
         * @var AbstractBeanPropertyDescriptor[]
1327
         */
1328
        $elements = [];
1329
1330
        foreach ($columns as $column) {
1331
            $fk = $this->isPartOfForeignKey($this->table, $this->table->getColumn($column));
1332
            if ($fk !== null) {
1333
                if (!isset($elements[$fk->getName()])) {
1334
                    $elements[$fk->getName()] = new ObjectBeanPropertyDescriptor($this->table, $fk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser, $this->registry->getBeanForTableName($fk->getForeignTableName()), $this->resultIteratorNamespace);
1335
                }
1336
            } else {
1337
                $elements[] = new ScalarBeanPropertyDescriptor($this->table, $this->table->getColumn($column), $this->namingStrategy, $this->annotationParser);
1338
            }
1339
        }
1340
        $elements = array_values($elements);
1341
1342
        // If the index is actually only a foreign key, let's bypass it entirely.
1343
        if (count($elements) === 1 && $elements[0] instanceof ObjectBeanPropertyDescriptor) {
1344
            return null;
1345
        }
1346
1347
        $parameters = [];
1348
        //$functionParameters = [];
1349
        $first = true;
1350
        /** @var AbstractBeanPropertyDescriptor $element */
1351
        foreach ($elements as $element) {
1352
            $parameter = new ParameterGenerator(ltrim($element->getVariableName(), '$'));
1353
            if (!$first && !($element->isCompulsory() && $index->isUnique())) {
1354
                $parameterType = '?';
1355
            //$functionParameter = '?';
1356
            } else {
1357
                $parameterType = '';
1358
                //$functionParameter = '';
1359
            }
1360
            $parameterType .= $element->getPhpType();
1361
            $parameter->setType($parameterType);
1362
            if (!$first && !($element->isCompulsory() && $index->isUnique())) {
1363
                $parameter->setDefaultValue(null);
1364
            }
1365
            //$functionParameter .= $element->getPhpType();
1366
            $elementClassName = $element->getClassName();
1367
            if ($elementClassName) {
1368
                $usedBeans[] = $beanNamespace.'\\'.$elementClassName;
1369
            }
1370
            //$functionParameter .= ' '.$element->getVariableName();
1371
            if ($first) {
1372
                $first = false;
1373
            } /*else {
1374
                $functionParameter .= ' = null';
1375
            }*/
1376
            //$functionParameters[] = $functionParameter;
1377
            $parameters[] = $parameter;
1378
        }
1379
1380
        //$functionParametersString = implode(', ', $functionParameters);
1381
1382
        $count = 0;
1383
1384
        $params = [];
1385
        $filterArrayCode = '';
1386
        $commentArguments = [];
1387
        $first = true;
1388
        foreach ($elements as $element) {
1389
            $params[] = $element->getParamAnnotation();
1390
            if ($element instanceof ScalarBeanPropertyDescriptor) {
1391
                $filterArrayCode .= '            '.var_export($element->getColumnName(), true).' => '.$element->getVariableName().",\n";
1392
            } elseif ($element instanceof ObjectBeanPropertyDescriptor) {
1393
                $foreignKey = $element->getForeignKey();
1394
                $columns = SafeFunctions::arrayCombine($foreignKey->getLocalColumns(), $foreignKey->getForeignColumns());
1395
                ++$count;
1396
                $foreignTable = $this->schema->getTable($foreignKey->getForeignTableName());
1397
                foreach ($columns as $localColumn => $foreignColumn) {
1398
                    // 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.
1399
                    $targetedElement = new ScalarBeanPropertyDescriptor($foreignTable, $foreignTable->getColumn($foreignColumn), $this->namingStrategy, $this->annotationParser);
1400
                    if ($first || $element->isCompulsory() && $index->isUnique()) {
1401
                        // First parameter for index is not nullable
1402
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => '.$element->getVariableName().'->'.$targetedElement->getGetterName()."(),\n";
1403
                    } else {
1404
                        // Other parameters for index is not nullable
1405
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => ('.$element->getVariableName().' !== null) ? '.$element->getVariableName().'->'.$targetedElement->getGetterName()."() : null,\n";
1406
                    }
1407
                }
1408
            }
1409
            $commentArguments[] = substr($element->getVariableName(), 1);
1410
            if ($first) {
1411
                $first = false;
1412
            }
1413
        }
1414
1415
        //$paramsString = implode("\n", $params);
1416
1417
1418
        $methodName = $this->namingStrategy->getFindByIndexMethodName($index, $elements);
1419
1420
        $method = new MethodGenerator($methodName);
1421
1422
        if ($index->isUnique()) {
1423
            $parameters[] = new ParameterGenerator('additionalTablesFetch', 'array', []);
1424
            $params[] = new ParamTag('additionalTablesFetch', [ 'string[]' ], 'A list of additional tables to fetch (for performance improvement)');
1425
            $params[] = new ReturnTag([ '\\'.$beanNamespace.'\\'.$beanClassName, 'null' ]);
1426
            $method->setReturnType('?\\'.$beanNamespace.'\\'.$beanClassName);
1427
1428
            $docBlock = new DocBlockGenerator("Get a $beanClassName filtered by ".implode(', ', $commentArguments). '.', null, $params);
1429
            $docBlock->setWordWrap(false);
1430
1431
            $body = "\$filter = [
1432
".$filterArrayCode."        ];
1433
return \$this->findOne(\$filter, [], \$additionalTablesFetch);
1434
";
1435
        } else {
1436
            $parameters[] = (new ParameterGenerator('orderBy'))->setDefaultValue(null);
1437
            $params[] = new ParamTag('orderBy', [ 'mixed' ], 'The order string');
1438
            $parameters[] = new ParameterGenerator('additionalTablesFetch', 'array', []);
1439
            $params[] = new ParamTag('additionalTablesFetch', [ 'string[]' ], 'A list of additional tables to fetch (for performance improvement)');
1440
            $parameters[] = (new ParameterGenerator('mode', '?int'))->setDefaultValue(null);
1441
            $params[] = new ParamTag('mode', [ 'int', 'null' ], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.');
1442
            $method->setReturnType($this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName());
1443
1444
            $docBlock = new DocBlockGenerator("Get a list of $beanClassName filtered by ".implode(', ', $commentArguments).".", null, $params);
1445
            $docBlock->setWordWrap(false);
1446
1447
            $body = "\$filter = [
1448
".$filterArrayCode."        ];
1449
return \$this->find(\$filter, [], \$orderBy, \$additionalTablesFetch, \$mode);
1450
";
1451
        }
1452
1453
        $method->setParameters($parameters);
1454
        $method->setDocBlock($docBlock);
1455
        $method->setBody($body);
1456
1457
        return $method;
1458
    }
1459
1460
    /**
1461
     * Generates the code for the getUsedTable protected method.
1462
     *
1463
     * @return MethodGenerator
1464
     */
1465
    private function generateGetUsedTablesCode(): MethodGenerator
1466
    {
1467
        $hasParentRelationship = $this->schemaAnalyzer->getParentRelationship($this->table->getName()) !== null;
1468
        if ($hasParentRelationship) {
1469
            $code = sprintf('$tables = parent::getUsedTables();
1470
$tables[] = %s;
1471
1472
return $tables;', var_export($this->table->getName(), true));
1473
        } else {
1474
            $code = sprintf('        return [ %s ];', var_export($this->table->getName(), true));
1475
        }
1476
1477
        $method = new MethodGenerator('getUsedTables');
1478
        $method->setDocBlock('Returns an array of used tables by this bean (from parent to child relationship).');
1479
        $method->getDocBlock()->setTag(new ReturnTag(['string[]']));
1480
        $method->setReturnType('array');
1481
        $method->setBody($code);
1482
1483
        return $method;
1484
    }
1485
1486
    private function generateOnDeleteCode(): ?MethodGenerator
1487
    {
1488
        $setRefsToNullCode = ['parent::onDelete();'];
1489
        $relationships = $this->getPropertiesForTable($this->table);
1490
        foreach ($relationships as $relationship) {
1491
            if ($relationship instanceof ObjectBeanPropertyDescriptor) {
1492
                $tdbmFk = ForeignKey::createFromFk($relationship->getForeignKey());
1493
                $foreignTableName = $tdbmFk->getForeignTableName();
1494
                $setRefsToNullCode[] = sprintf(
1495
                    '$this->setRef(%s, %s, %s, %s, %s);',
1496
                    var_export($tdbmFk->getCacheKey(), true),
1497
                    'null',
1498
                    var_export($this->table->getName(), true),
1499
                    '\\' . $this->beanNamespace . '\\' . $this->namingStrategy->getBeanClassName($foreignTableName) . '::class',
1500
                    '\\' . $this->resultIteratorNamespace . '\\' . $this->namingStrategy->getResultIteratorClassName($foreignTableName) . '::class'
1501
                );
1502
            }
1503
        }
1504
1505
        if (count($setRefsToNullCode) === 1) {
1506
            return null;
1507
        }
1508
1509
        $method = new MethodGenerator('onDelete');
1510
        $method->setDocBlock('Method called when the bean is removed from database.');
1511
        $method->setReturnType('void');
1512
        $method->setBody(implode("\n", $setRefsToNullCode));
1513
1514
        return $method;
1515
    }
1516
1517
    /**
1518
     * @param PivotTableMethodsDescriptor[] $pivotTableMethodsDescriptors
1519
     * @return MethodGenerator
1520
     */
1521
    private function generateGetManyToManyRelationshipDescriptorCode(array $pivotTableMethodsDescriptors): ?MethodGenerator
1522
    {
1523
        if (empty($pivotTableMethodsDescriptors)) {
1524
            return null;
1525
        }
1526
1527
        $method = new MethodGenerator('_getManyToManyRelationshipDescriptor');
1528
        $method->setVisibility(AbstractMemberGenerator::VISIBILITY_PUBLIC);
1529
        $method->setDocBlock('Get the paths used for many to many relationships methods.');
1530
        $method->getDocBlock()->setTag(new GenericTag('internal'));
1531
        $method->setReturnType(ManyToManyRelationshipPathDescriptor::class);
1532
1533
        $parameter = new ParameterGenerator('pathKey');
1534
        $parameter->setType('string');
1535
        $method->setParameter($parameter);
1536
1537
        $code = 'switch ($pathKey) {'."\n";
1538
        foreach ($pivotTableMethodsDescriptors as $pivotTableMethodsDescriptor) {
1539
            $code .= '    case '.var_export($pivotTableMethodsDescriptor->getManyToManyRelationshipKey(), true).":\n";
1540
            $code .= '        return '.$pivotTableMethodsDescriptor->getManyToManyRelationshipInstantiationCode().";\n";
1541
        }
1542
        $code .= "    default:\n";
1543
        $code .= "        return parent::_getManyToManyRelationshipDescriptor(\$pathKey);\n";
1544
        $code .= "}\n";
1545
1546
        $method->setBody($code);
1547
1548
        return $method;
1549
    }
1550
1551
    /**
1552
     * @param PivotTableMethodsDescriptor[] $pivotTableMethodsDescriptors
1553
     * @return MethodGenerator
1554
     */
1555
    private function generateGetManyToManyRelationshipDescriptorKeysCode(array $pivotTableMethodsDescriptors): ?MethodGenerator
1556
    {
1557
        if (empty($pivotTableMethodsDescriptors)) {
1558
            return null;
1559
        }
1560
1561
        $method = new MethodGenerator('_getManyToManyRelationshipDescriptorKeys');
1562
        $method->setVisibility(AbstractMemberGenerator::VISIBILITY_PUBLIC);
1563
        $method->setReturnType('array');
1564
        $method->setDocBlock('Returns the list of keys supported for many to many relationships');
1565
        $method->getDocBlock()->setTag(new GenericTag('internal'));
1566
        $method->getDocBlock()->setTag(new ReturnTag('string[]'));
1567
1568
        $keys = [];
1569
        foreach ($pivotTableMethodsDescriptors as $pivotTableMethodsDescriptor) {
1570
            $keys[] = var_export($pivotTableMethodsDescriptor->getManyToManyRelationshipKey(), true);
1571
        }
1572
1573
        $code = 'return array_merge(parent::_getManyToManyRelationshipDescriptorKeys(), ['.implode(', ', $keys).']);';
1574
1575
        $method->setBody($code);
1576
1577
        return $method;
1578
    }
1579
1580
    /**
1581
     * @param PivotTableMethodsDescriptor[] $pivotTableMethodsDescriptors
1582
     * @return MethodGenerator
1583
     */
1584
    private function generateCloneCode(array $pivotTableMethodsDescriptors): MethodGenerator
1585
    {
1586
        $precode = '';
1587
        $postcode = '';
1588
1589
        foreach ($this->beanPropertyDescriptors as $beanPropertyDescriptor) {
1590
            $postcode .= $beanPropertyDescriptor->getCloneRule();
1591
        }
1592
1593
        //cloning many to many relationships
1594
        foreach ($pivotTableMethodsDescriptors as $beanMethodDescriptor) {
1595
            $precode .= $beanMethodDescriptor->getCloneRule()."\n";
1596
        }
1597
1598
        $method = new MethodGenerator('__clone');
1599
        $method->setBody($precode."parent::__clone();\n".$postcode);
1600
1601
        return $method;
1602
    }
1603
1604
    /**
1605
     * Returns the bean class name (without the namespace).
1606
     *
1607
     * @return string
1608
     */
1609
    public function getBeanClassName() : string
1610
    {
1611
        return $this->namingStrategy->getBeanClassName($this->table->getName());
1612
    }
1613
1614
    /**
1615
     * Returns the base bean class name (without the namespace).
1616
     *
1617
     * @return string
1618
     */
1619
    public function getBaseBeanClassName() : string
1620
    {
1621
        return $this->namingStrategy->getBaseBeanClassName($this->table->getName());
1622
    }
1623
1624
    /**
1625
     * Returns the DAO class name (without the namespace).
1626
     *
1627
     * @return string
1628
     */
1629
    public function getDaoClassName() : string
1630
    {
1631
        return $this->namingStrategy->getDaoClassName($this->table->getName());
1632
    }
1633
1634
    /**
1635
     * Returns the base DAO class name (without the namespace).
1636
     *
1637
     * @return string
1638
     */
1639
    public function getBaseDaoClassName() : string
1640
    {
1641
        return $this->namingStrategy->getBaseDaoClassName($this->table->getName());
1642
    }
1643
1644
    /**
1645
     * Returns the ResultIterator class name (without the namespace).
1646
     *
1647
     * @return string
1648
     */
1649
    public function getResultIteratorClassName() : string
1650
    {
1651
        return $this->namingStrategy->getResultIteratorClassName($this->table->getName());
1652
    }
1653
1654
    /**
1655
     * Returns the base ResultIterator class name (without the namespace).
1656
     *
1657
     * @return string
1658
     */
1659
    public function getBaseResultIteratorClassName() : string
1660
    {
1661
        return $this->namingStrategy->getBaseResultIteratorClassName($this->table->getName());
1662
    }
1663
1664
    /**
1665
     * Returns the table used to build this bean.
1666
     *
1667
     * @return Table
1668
     */
1669
    public function getTable(): Table
1670
    {
1671
        return $this->table;
1672
    }
1673
1674
    /**
1675
     * Returns the extended bean class name (without the namespace), or null if the bean is not extended.
1676
     */
1677
    public function getExtendedBeanClassName(): ?string
1678
    {
1679
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
1680
        if ($parentFk !== null) {
1681
            return $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName());
1682
        }
1683
        return null;
1684
    }
1685
1686
    /**
1687
     * Returns the extended result iterator class name (without the namespace), or null if the result iterator is not extended.
1688
     */
1689
    public function getExtendedResultIteratorClassName(): ?string
1690
    {
1691
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
1692
        if ($parentFk !== null) {
1693
            return $this->namingStrategy->getResultIteratorClassName($parentFk->getForeignTableName());
1694
        }
1695
        return null;
1696
    }
1697
1698
    /**
1699
     * @return string
1700
     */
1701
    public function getBeanNamespace(): string
1702
    {
1703
        return $this->beanNamespace;
1704
    }
1705
1706
    /**
1707
     * @return string
1708
     */
1709
    public function getGeneratedBeanNamespace(): string
1710
    {
1711
        return $this->generatedBeanNamespace;
1712
    }
1713
1714
    /**
1715
     * @param ForeignKeyConstraint[] $fks
1716
     */
1717
    private function generateGetForeignKeys(array $fks): MethodGenerator
1718
    {
1719
        $fkArray = [];
1720
1721
        foreach ($fks as $fk) {
1722
            $tdbmFk = ForeignKey::createFromFk($fk);
1723
            $fkArray[$tdbmFk->getCacheKey()] = [
1724
                ForeignKey::FOREIGN_TABLE => $fk->getForeignTableName(),
1725
                ForeignKey::LOCAL_COLUMNS => $fk->getUnquotedLocalColumns(),
1726
                ForeignKey::FOREIGN_COLUMNS => $fk->getUnquotedForeignColumns(),
1727
            ];
1728
        }
1729
1730
        ksort($fkArray);
1731
        foreach ($fkArray as $tableFks) {
1732
            ksort($tableFks);
1733
        }
1734
1735
        $code = <<<EOF
1736
if (\$tableName === %s) {
1737
    if (self::\$foreignKeys === null) {
1738
        self::\$foreignKeys = new ForeignKeys(%s);
1739
    }
1740
    return self::\$foreignKeys;
1741
}
1742
return parent::getForeignKeys(\$tableName);
1743
EOF;
1744
        $code = sprintf($code, var_export($this->getTable()->getName(), true), $this->psr2VarExport($fkArray, '    '));
1745
1746
        $method = new MethodGenerator('getForeignKeys');
1747
        $method->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED);
1748
        $method->setStatic(true);
1749
        $method->setDocBlock('Internal method used to retrieve the list of foreign keys attached to this bean.');
1750
        $method->setReturnType(ForeignKeys::class);
1751
1752
        $parameter = new ParameterGenerator('tableName');
1753
        $parameter->setType('string');
1754
        $method->setParameter($parameter);
1755
1756
1757
        $method->setBody($code);
1758
1759
        return $method;
1760
    }
1761
1762
    /**
1763
     * @param mixed $var
1764
     * @param string $indent
1765
     * @return string
1766
     */
1767
    private function psr2VarExport($var, string $indent=''): string
1768
    {
1769
        if (is_array($var)) {
1770
            $indexed = array_keys($var) === range(0, count($var) - 1);
1771
            $r = [];
1772
            foreach ($var as $key => $value) {
1773
                $r[] = "$indent    "
1774
                    . ($indexed ? '' : $this->psr2VarExport($key) . ' => ')
1775
                    . $this->psr2VarExport($value, "$indent    ");
1776
            }
1777
            return "[\n" . implode(",\n", $r) . "\n" . $indent . ']';
1778
        }
1779
        return var_export($var, true);
1780
    }
1781
}
1782