Passed
Push — 5.2 ( 346579 )
by
unknown
09:03 queued 04:26
created

BeanDescriptor::generateGetUsedTablesCode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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

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