Passed
Pull Request — master (#116)
by David
03:15
created

BeanDescriptor::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 15
rs 9.8333
c 0
b 0
f 0
cc 1
nc 1
nop 12

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