Passed
Pull Request — master (#116)
by David
03:08
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
            $class->addMethodFromGenerator($getter);
544
            $class->addMethodFromGenerator($setter);
545
        }
546
547
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
548
            if ($methodDescriptor instanceof DirectForeignKeyMethodDescriptor) {
549
                [$method] = $methodDescriptor->getCode();
550
                $method = $this->codeGeneratorListener->onBaseBeanOneToManyGenerated($method, $methodDescriptor, $this, $this->configuration, $class);
551
                if ($method) {
552
                    $class->addMethodFromGenerator($method);
553
                }
554
            } elseif ($methodDescriptor instanceof PivotTableMethodsDescriptor) {
555
                [ $getter, $adder, $remover, $has, $setter ] = $methodDescriptor->getCode();
556
                $methods = $this->codeGeneratorListener->onBaseBeanManyToManyGenerated($getter, $adder, $remover, $has, $setter, $methodDescriptor, $this, $this->configuration, $class);
557
                foreach ($methods as $method) {
558
                    if ($method) {
559
                        $class->addMethodFromGenerator($method);
560
                    }
561
                }
562
            } else {
563
                throw new \RuntimeException('Unexpected instance'); // @codeCoverageIgnore
564
            }
565
        }
566
567
        $method = $this->generateJsonSerialize();
568
        $method = $this->codeGeneratorListener->onBaseBeanJsonSerializeGenerated($method, $this, $this->configuration, $class);
569
        if ($method !== null) {
570
            $class->addMethodFromGenerator($method);
571
        }
572
573
        $class->addMethodFromGenerator($this->generateGetUsedTablesCode());
574
        $onDeleteCode = $this->generateOnDeleteCode();
575
        if ($onDeleteCode) {
576
            $class->addMethodFromGenerator($onDeleteCode);
577
        }
578
        $cloneCode = $this->generateCloneCode();
579
        if ($cloneCode) {
0 ignored issues
show
introduced by
$cloneCode is of type Zend\Code\Generator\MethodGenerator, thus it always evaluated to true.
Loading history...
580
            $cloneCode = $this->codeGeneratorListener->onBaseBeanCloneGenerated($cloneCode, $this, $this->configuration, $class);
581
            if ($cloneCode) {
582
                $class->addMethodFromGenerator($cloneCode);
583
            }
584
        }
585
586
        return $file;
587
    }
588
589
    /**
590
     * Writes the representation of the PHP DAO file.
591
     *
592
     * @return FileGenerator
593
     */
594
    public function generateDaoPhpCode(): FileGenerator
595
    {
596
        $file = new FileGenerator();
597
        $class = new ClassGenerator();
598
        $file->setClass($class);
599
        $file->setNamespace($this->generatedDaoNamespace);
600
601
        $tableName = $this->table->getName();
602
603
        $primaryKeyColumns = TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($this->table);
604
605
        list($defaultSort, $defaultSortDirection) = $this->getDefaultSortColumnFromAnnotation($this->table);
606
607
        $className = $this->namingStrategy->getDaoClassName($tableName);
608
        $baseClassName = $this->namingStrategy->getBaseDaoClassName($tableName);
609
        $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName);
610
        $beanClassName = $this->beanNamespace.'\\'.$beanClassWithoutNameSpace;
611
612
        $findByDaoCodeMethods = $this->generateFindByDaoCode($this->beanNamespace, $beanClassWithoutNameSpace, $class);
613
614
        $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...
615
        // Let's suppress duplicates in used beans (if any)
616
        $usedBeans = array_flip(array_flip($usedBeans));
617
        foreach ($usedBeans as $usedBean) {
618
            $class->addUse($usedBean);
619
        }
620
621
        $file->setDocBlock(new DocBlockGenerator(<<<EOF
622
This file has been automatically generated by TDBM.
623
DO NOT edit this file, as it might be overwritten.
624
If you need to perform changes, edit the $className class instead!
625
EOF
626
        ));
627
628
        $file->setNamespace($this->generatedDaoNamespace);
629
630
        $class->addUse(TDBMService::class);
631
        $class->addUse(ResultIterator::class);
632
        $class->addUse(TDBMException::class);
633
634
        $class->setName($baseClassName);
635
636
        $class->setDocBlock(new DocBlockGenerator("The $baseClassName class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table."));
637
638
        $tdbmServiceProperty = new PropertyGenerator('tdbmService');
639
        $tdbmServiceProperty->setDocBlock(new DocBlockGenerator(null, null, [new VarTag(null, ['\\'.TDBMService::class])]));
640
        $class->addPropertyFromGenerator($tdbmServiceProperty);
641
642
        $defaultSortProperty = new PropertyGenerator('defaultSort', $defaultSort);
643
        $defaultSortProperty->setDocBlock(new DocBlockGenerator('The default sort column.', null, [new VarTag(null, ['string', 'null'])]));
644
        $class->addPropertyFromGenerator($defaultSortProperty);
645
646
        $defaultSortPropertyDirection = new PropertyGenerator('defaultDirection', $defaultSort && $defaultSortDirection ? $defaultSortDirection : 'asc');
647
        $defaultSortPropertyDirection->setDocBlock(new DocBlockGenerator('The default sort direction.', null, [new VarTag(null, ['string'])]));
648
        $class->addPropertyFromGenerator($defaultSortPropertyDirection);
649
650
        $constructorMethod = new MethodGenerator('__construct',
651
            [ new ParameterGenerator('tdbmService', TDBMService::class) ],
652
            MethodGenerator::FLAG_PUBLIC,
653
            '$this->tdbmService = $tdbmService;',
654
            'Sets the TDBM service used by this DAO.');
655
        $constructorMethod = $this->codeGeneratorListener->onBaseDaoConstructorGenerated($constructorMethod, $this, $this->configuration, $class);
656
        if ($constructorMethod !== null) {
657
            $class->addMethodFromGenerator($constructorMethod);
658
        }
659
660
        $saveMethod = new MethodGenerator(
661
            'save',
662
            [ new ParameterGenerator('obj', $beanClassName) ],
663
            MethodGenerator::FLAG_PUBLIC,
664
            '$this->tdbmService->save($obj);',
665
            new DocBlockGenerator("Persist the $beanClassWithoutNameSpace instance.",
666
                null,
667
                [
668
                    new ParamTag('obj', [$beanClassWithoutNameSpace], 'The bean to save.')
669
                ]));
670
        $saveMethod->setReturnType('void');
671
672
        $saveMethod = $this->codeGeneratorListener->onBaseDaoSaveGenerated($saveMethod, $this, $this->configuration, $class);
673
        if ($saveMethod !== null) {
674
            $class->addMethodFromGenerator($saveMethod);
675
        }
676
677
        $findAllBody = <<<EOF
678
if (\$this->defaultSort) {
679
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
680
} else {
681
    \$orderBy = null;
682
}
683
return \$this->tdbmService->findObjects('$tableName', null, [], \$orderBy);
684
EOF;
685
686
        $findAllMethod = new MethodGenerator(
687
            'findAll',
688
            [],
689
            MethodGenerator::FLAG_PUBLIC,
690
            $findAllBody,
691
            (new DocBlockGenerator("Get all $beanClassWithoutNameSpace records.",
692
                null,
693
                [
694
                    new ReturnTag([ '\\'.$beanClassName.'[]', '\\'.ResultIterator::class ])
695
                ]))->setWordWrap(false)
696
        );
697
        $findAllMethod->setReturnType('iterable');
698
        $findAllMethod = $this->codeGeneratorListener->onBaseDaoFindAllGenerated($findAllMethod, $this, $this->configuration, $class);
699
        if ($findAllMethod !== null) {
700
            $class->addMethodFromGenerator($findAllMethod);
701
        }
702
703
        if (count($primaryKeyColumns) === 1) {
704
            $primaryKeyColumn = $primaryKeyColumns[0];
705
            $primaryKeyPhpType = TDBMDaoGenerator::dbalTypeToPhpType($this->table->getColumn($primaryKeyColumn)->getType());
706
707
            $getByIdMethod = new MethodGenerator(
708
                'getById',
709
                [
710
                    new ParameterGenerator('id', $primaryKeyPhpType),
711
                    new ParameterGenerator('lazyLoading', 'bool', false)
712
                ],
713
                MethodGenerator::FLAG_PUBLIC,
714
                "return \$this->tdbmService->findObjectByPk('$tableName', ['$primaryKeyColumn' => \$id], [], \$lazyLoading);",
715
                (new DocBlockGenerator("Get $beanClassWithoutNameSpace specified by its ID (its primary key).",
716
                    'If the primary key does not exist, an exception is thrown.',
717
                    [
718
                        new ParamTag('id', [$primaryKeyPhpType]),
719
                        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.'),
720
                        new ReturnTag(['\\'.$beanClassName . '[]', '\\'.ResultIterator::class]),
721
                        new ThrowsTag('\\'.TDBMException::class)
722
                    ]))->setWordWrap(false)
723
            );
724
            $getByIdMethod = $this->codeGeneratorListener->onBaseDaoGetByIdGenerated($getByIdMethod, $this, $this->configuration, $class);
725
            if ($getByIdMethod) {
726
                $class->addMethodFromGenerator($getByIdMethod);
727
            }
728
        }
729
730
        $deleteMethodBody = <<<EOF
731
if (\$cascade === true) {
732
    \$this->tdbmService->deleteCascade(\$obj);
733
} else {
734
    \$this->tdbmService->delete(\$obj);
735
}
736
EOF;
737
738
739
        $deleteMethod = new MethodGenerator(
740
            'delete',
741
            [
742
                new ParameterGenerator('obj', $beanClassName),
743
                new ParameterGenerator('cascade', 'bool', false)
744
            ],
745
            MethodGenerator::FLAG_PUBLIC,
746
            $deleteMethodBody,
747
            (new DocBlockGenerator("Get all $beanClassWithoutNameSpace records.",
748
                null,
749
                [
750
                    new ParamTag('obj', ['\\'.$beanClassName], 'The object to delete'),
751
                    new ParamTag('cascade', ['bool'], 'If true, it will delete all objects linked to $obj'),
752
                ]))->setWordWrap(false)
753
        );
754
        $deleteMethod->setReturnType('void');
755
        $deleteMethod = $this->codeGeneratorListener->onBaseDaoDeleteGenerated($deleteMethod, $this, $this->configuration, $class);
756
        if ($deleteMethod !== null) {
757
            $class->addMethodFromGenerator($deleteMethod);
758
        }
759
760
        $findMethodBody = <<<EOF
761
if (\$this->defaultSort && \$orderBy == null) {
762
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
763
}
764
return \$this->tdbmService->findObjects('$tableName', \$filter, \$parameters, \$orderBy, \$additionalTablesFetch, \$mode);
765
EOF;
766
767
768
        $findMethod = new MethodGenerator(
769
            'find',
770
            [
771
                (new ParameterGenerator('filter'))->setDefaultValue(null),
772
                new ParameterGenerator('parameters', 'array', []),
773
                (new ParameterGenerator('orderBy'))->setDefaultValue(null),
774
                new ParameterGenerator('additionalTablesFetch', 'array', []),
775
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
776
            ],
777
            MethodGenerator::FLAG_PROTECTED,
778
            $findMethodBody,
779
            (new DocBlockGenerator("Get all $beanClassWithoutNameSpace records.",
780
                null,
781
                [
782
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
783
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
784
                    new ParamTag('orderBy', ['mixed'], 'The order string'),
785
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
786
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.'),
787
                    new ReturnTag(['\\' . $beanClassName . '[]', '\\'.ResultIterator::class])
788
                ]))->setWordWrap(false)
789
        );
790
        $findMethod->setReturnType('iterable');
791
        $findMethod = $this->codeGeneratorListener->onBaseDaoFindGenerated($findMethod, $this, $this->configuration, $class);
792
        if ($findMethod !== null) {
793
            $class->addMethodFromGenerator($findMethod);
794
        }
795
796
        $findFromSqlMethodBody = <<<EOF
797
if (\$this->defaultSort && \$orderBy == null) {
798
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
799
}
800
return \$this->tdbmService->findObjectsFromSql('$tableName', \$from, \$filter, \$parameters, \$orderBy, \$mode);
801
EOF;
802
803
        $findFromSqlMethod = new MethodGenerator(
804
            'findFromSql',
805
            [
806
                new ParameterGenerator('from', 'string'),
807
                (new ParameterGenerator('filter'))->setDefaultValue(null),
808
                new ParameterGenerator('parameters', 'array', []),
809
                (new ParameterGenerator('orderBy'))->setDefaultValue(null),
810
                new ParameterGenerator('additionalTablesFetch', 'array', []),
811
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
812
            ],
813
            MethodGenerator::FLAG_PROTECTED,
814
            $findFromSqlMethodBody,
815
            (new DocBlockGenerator("Get a list of $beanClassWithoutNameSpace specified by its filters.",
816
                "Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part.
817
818
You should not put an alias on the main table name. So your \$from variable should look like:
819
820
   \"$tableName JOIN ... ON ...\"",
821
                [
822
                    new ParamTag('from', ['string'], 'The sql from statement'),
823
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
824
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
825
                    new ParamTag('orderBy', ['mixed'], 'The order string'),
826
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
827
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.'),
828
                    new ReturnTag(['\\'.$beanClassName . '[]', '\\'.ResultIterator::class])
829
                ]))->setWordWrap(false)
830
        );
831
        $findFromSqlMethod->setReturnType('iterable');
832
        $findFromSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromSqlGenerated($findFromSqlMethod, $this, $this->configuration, $class);
833
        if ($findFromSqlMethod !== null) {
834
            $class->addMethodFromGenerator($findFromSqlMethod);
835
        }
836
837
        $findFromRawSqlMethodBody = <<<EOF
838
return \$this->tdbmService->findObjectsFromRawSql('$tableName', \$sql, \$parameters, \$mode, null, \$countSql);
839
EOF;
840
841
        $findFromRawSqlMethod = new MethodGenerator(
842
            'findFromRawSql',
843
            [
844
                new ParameterGenerator('sql', 'string'),
845
                new ParameterGenerator('parameters', 'array', []),
846
                (new ParameterGenerator('countSql', '?string'))->setDefaultValue(null),
847
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
848
            ],
849
            MethodGenerator::FLAG_PROTECTED,
850
            $findFromRawSqlMethodBody,
851
            (new DocBlockGenerator("Get a list of $beanClassWithoutNameSpace from a SQL query.",
852
                "Unlike the `find` and `findFromSql` methods, here you can pass the whole \$sql query.
853
854
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:
855
856
   \"SELECT $tableName .* FROM ...\"",
857
                [
858
                    new ParamTag('sql', ['string'], 'The sql query'),
859
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the query'),
860
                    new ParamTag('countSql', ['string', 'null'], 'The sql query that provides total count of rows (automatically computed if not provided)'),
861
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.'),
862
                    new ReturnTag(['\\'.$beanClassName . '[]', '\\'.ResultIterator::class])
863
                ]))->setWordWrap(false)
864
        );
865
        $findFromRawSqlMethod->setReturnType('iterable');
866
        $findFromRawSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromRawSqlGenerated($findFromRawSqlMethod, $this, $this->configuration, $class);
867
        if ($findFromRawSqlMethod !== null) {
868
            $class->addMethodFromGenerator($findFromRawSqlMethod);
869
        }
870
871
        $findOneMethodBody = <<<EOF
872
return \$this->tdbmService->findObject('$tableName', \$filter, \$parameters, \$additionalTablesFetch);
873
EOF;
874
875
876
        $findOneMethod = new MethodGenerator(
877
            'findOne',
878
            [
879
                (new ParameterGenerator('filter'))->setDefaultValue(null),
880
                new ParameterGenerator('parameters', 'array', []),
881
                new ParameterGenerator('additionalTablesFetch', 'array', []),
882
            ],
883
            MethodGenerator::FLAG_PROTECTED,
884
            $findOneMethodBody,
885
            (new DocBlockGenerator("Get a single $beanClassWithoutNameSpace specified by its filters.",
886
                null,
887
                [
888
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
889
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
890
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
891
                    new ReturnTag(['\\'.$beanClassName, 'null'])
892
                ]))->setWordWrap(false)
893
        );
894
        $findOneMethod->setReturnType("?$beanClassName");
895
        $findOneMethod = $this->codeGeneratorListener->onBaseDaoFindOneGenerated($findOneMethod, $this, $this->configuration, $class);
896
        if ($findOneMethod !== null) {
897
            $class->addMethodFromGenerator($findOneMethod);
898
        }
899
900
        $findOneFromSqlMethodBody = <<<EOF
901
return \$this->tdbmService->findObjectFromSql('$tableName', \$from, \$filter, \$parameters);
902
EOF;
903
904
        $findOneFromSqlMethod = new MethodGenerator(
905
            'findOneFromSql',
906
            [
907
                new ParameterGenerator('from', 'string'),
908
                (new ParameterGenerator('filter'))->setDefaultValue(null),
909
                new ParameterGenerator('parameters', 'array', []),
910
            ],
911
            MethodGenerator::FLAG_PROTECTED,
912
            $findOneFromSqlMethodBody,
913
            new DocBlockGenerator("Get a single $beanClassWithoutNameSpace specified by its filters.",
914
                "Unlike the `findOne` method that guesses the FROM part of the statement, here you can pass the \$from part.
915
916
You should not put an alias on the main table name. So your \$from variable should look like:
917
918
    \"$tableName JOIN ... ON ...\"",
919
                [
920
                    new ParamTag('from', ['string'], 'The sql from statement'),
921
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
922
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
923
                    new ReturnTag(['\\'.$beanClassName, 'null'])
924
                ])
925
        );
926
        $findOneFromSqlMethod->setReturnType("?$beanClassName");
927
        $findOneFromSqlMethod = $this->codeGeneratorListener->onBaseDaoFindOneFromSqlGenerated($findOneFromSqlMethod, $this, $this->configuration, $class);
928
        if ($findOneFromSqlMethod !== null) {
929
            $class->addMethodFromGenerator($findOneFromSqlMethod);
930
        }
931
932
933
        $setDefaultSortMethod = new MethodGenerator(
934
            'setDefaultSort',
935
            [
936
                new ParameterGenerator('defaultSort', 'string'),
937
            ],
938
            MethodGenerator::FLAG_PUBLIC,
939
            '$this->defaultSort = $defaultSort;',
940
            new DocBlockGenerator("Sets the default column for default sorting.",
941
            null,
942
                [
943
                    new ParamTag('defaultSort', ['string']),
944
                ])
945
        );
946
        $setDefaultSortMethod->setReturnType('void');
947
        $setDefaultSortMethod = $this->codeGeneratorListener->onBaseDaoSetDefaultSortGenerated($setDefaultSortMethod, $this, $this->configuration, $class);
948
        if ($setDefaultSortMethod !== null) {
949
            $class->addMethodFromGenerator($setDefaultSortMethod);
950
        }
951
952
        foreach ($findByDaoCodeMethods as $method) {
953
            $class->addMethodFromGenerator($method);
954
        }
955
956
        return $file;
957
    }
958
959
    /**
960
     * Tries to find a @defaultSort annotation in one of the columns.
961
     *
962
     * @param Table $table
963
     *
964
     * @return mixed[] First item: column name, Second item: column order (asc/desc)
965
     */
966
    private function getDefaultSortColumnFromAnnotation(Table $table): array
967
    {
968
        $defaultSort = null;
969
        $defaultSortDirection = null;
970
        foreach ($table->getColumns() as $column) {
971
            $comments = $column->getComment();
972
            $matches = [];
973
            if ($comments !== null && preg_match('/@defaultSort(\((desc|asc)\))*/', $comments, $matches) != 0) {
974
                $defaultSort = $column->getName();
975
                if (count($matches) === 3) {
976
                    $defaultSortDirection = $matches[2];
977
                } else {
978
                    $defaultSortDirection = 'ASC';
979
                }
980
            }
981
        }
982
983
        return [$defaultSort, $defaultSortDirection];
984
    }
985
986
    /**
987
     * @param string $beanNamespace
988
     * @param string $beanClassName
989
     *
990
     * @return MethodGenerator[]
991
     */
992
    private function generateFindByDaoCode(string $beanNamespace, string $beanClassName, ClassGenerator $class): array
993
    {
994
        $methods = [];
995
        foreach ($this->removeDuplicateIndexes($this->table->getIndexes()) as $index) {
996
            if (!$index->isPrimary()) {
997
                $method = $this->generateFindByDaoCodeForIndex($index, $beanNamespace, $beanClassName);
998
999
                if ($method !== null) {
1000
                    $method = $this->codeGeneratorListener->onBaseDaoFindByIndexGenerated($method, $index, $this, $this->configuration, $class);
1001
                    if ($method !== null) {
1002
                        $methods[] = $method;
1003
                    }
1004
                }
1005
            }
1006
        }
1007
1008
        return $methods;
1009
    }
1010
1011
    /**
1012
     * Remove identical indexes (indexes on same columns)
1013
     *
1014
     * @param Index[] $indexes
1015
     * @return Index[]
1016
     */
1017
    private function removeDuplicateIndexes(array $indexes): array
1018
    {
1019
        $indexesByKey = [];
1020
        foreach ($indexes as $index) {
1021
            $indexesByKey[implode('__`__', $index->getUnquotedColumns())] = $index;
1022
        }
1023
1024
        return array_values($indexesByKey);
1025
    }
1026
1027
    /**
1028
     * @param Index  $index
1029
     * @param string $beanNamespace
1030
     * @param string $beanClassName
1031
     *
1032
     * @return MethodGenerator|null
1033
     */
1034
    private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespace, string $beanClassName): ?MethodGenerator
1035
    {
1036
        $columns = $index->getColumns();
1037
        $usedBeans = [];
1038
1039
        /**
1040
         * The list of elements building this index (expressed as columns or foreign keys)
1041
         * @var AbstractBeanPropertyDescriptor[]
1042
         */
1043
        $elements = [];
1044
1045
        foreach ($columns as $column) {
1046
            $fk = $this->isPartOfForeignKey($this->table, $this->table->getColumn($column));
1047
            if ($fk !== null) {
1048
                if (!in_array($fk, $elements)) {
1049
                    $elements[] = new ObjectBeanPropertyDescriptor($this->table, $fk, $this->namingStrategy, $this->beanNamespace);
1050
                }
1051
            } else {
1052
                $elements[] = new ScalarBeanPropertyDescriptor($this->table, $this->table->getColumn($column), $this->namingStrategy, $this->annotationParser);
1053
            }
1054
        }
1055
1056
        // If the index is actually only a foreign key, let's bypass it entirely.
1057
        if (count($elements) === 1 && $elements[0] instanceof ObjectBeanPropertyDescriptor) {
1058
            return null;
1059
        }
1060
1061
        $parameters = [];
1062
        //$functionParameters = [];
1063
        $first = true;
1064
        foreach ($elements as $element) {
1065
            $parameter = new ParameterGenerator(ltrim($element->getVariableName(), '$'));
1066
            if (!$first) {
1067
                $parameterType = '?';
1068
                //$functionParameter = '?';
1069
            } else {
1070
                $parameterType = '';
1071
                //$functionParameter = '';
1072
            }
1073
            $parameterType .= $element->getPhpType();
1074
            $parameter->setType($parameterType);
1075
            if (!$first) {
1076
                $parameter->setDefaultValue(null);
1077
            }
1078
            //$functionParameter .= $element->getPhpType();
1079
            $elementClassName = $element->getClassName();
1080
            if ($elementClassName) {
1081
                $usedBeans[] = $beanNamespace.'\\'.$elementClassName;
1082
            }
1083
            //$functionParameter .= ' '.$element->getVariableName();
1084
            if ($first) {
1085
                $first = false;
1086
            } /*else {
1087
                $functionParameter .= ' = null';
1088
            }*/
1089
            //$functionParameters[] = $functionParameter;
1090
            $parameters[] = $parameter;
1091
        }
1092
1093
        //$functionParametersString = implode(', ', $functionParameters);
1094
1095
        $count = 0;
1096
1097
        $params = [];
1098
        $filterArrayCode = '';
1099
        $commentArguments = [];
1100
        $first = true;
1101
        foreach ($elements as $element) {
1102
            $params[] = $element->getParamAnnotation();
1103
            if ($element instanceof ScalarBeanPropertyDescriptor) {
1104
                $filterArrayCode .= '            '.var_export($element->getColumnName(), true).' => '.$element->getVariableName().",\n";
1105
            } elseif ($element instanceof ObjectBeanPropertyDescriptor) {
1106
                $foreignKey = $element->getForeignKey();
1107
                $columns = SafeFunctions::arrayCombine($foreignKey->getLocalColumns(), $foreignKey->getForeignColumns());
1108
                ++$count;
1109
                $foreignTable = $this->schema->getTable($foreignKey->getForeignTableName());
1110
                foreach ($columns as $localColumn => $foreignColumn) {
1111
                    // 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.
1112
                    $targetedElement = new ScalarBeanPropertyDescriptor($foreignTable, $foreignTable->getColumn($foreignColumn), $this->namingStrategy, $this->annotationParser);
1113
                    if ($first) {
1114
                        // First parameter for index is not nullable
1115
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => '.$element->getVariableName().'->'.$targetedElement->getGetterName()."(),\n";
1116
                    } else {
1117
                        // Other parameters for index is not nullable
1118
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => ('.$element->getVariableName().' !== null) ? '.$element->getVariableName().'->'.$targetedElement->getGetterName()."() : null,\n";
1119
                    }
1120
                }
1121
            }
1122
            $commentArguments[] = substr($element->getVariableName(), 1);
1123
            if ($first) {
1124
                $first = false;
1125
            }
1126
        }
1127
1128
        //$paramsString = implode("\n", $params);
1129
1130
1131
        $methodName = $this->namingStrategy->getFindByIndexMethodName($index, $elements);
1132
1133
        $method = new MethodGenerator($methodName);
1134
1135
        if ($index->isUnique()) {
1136
            $parameters[] = new ParameterGenerator('additionalTablesFetch', 'array', []);
1137
            $params[] = new ParamTag('additionalTablesFetch', [ 'string[]' ], 'A list of additional tables to fetch (for performance improvement)');
1138
            $params[] = new ReturnTag([ '\\'.$beanNamespace.'\\'.$beanClassName, 'null' ]);
1139
            $method->setReturnType('?\\'.$beanNamespace.'\\'.$beanClassName);
1140
1141
            $docBlock = new DocBlockGenerator("Get a $beanClassName filtered by ".implode(', ', $commentArguments). '.', null, $params);
1142
            $docBlock->setWordWrap(false);
1143
1144
            $body = "\$filter = [
1145
".$filterArrayCode."        ];
1146
return \$this->findOne(\$filter, [], \$additionalTablesFetch);
1147
";
1148
        } else {
1149
            $parameters[] = (new ParameterGenerator('orderBy'))->setDefaultValue(null);
1150
            $params[] = new ParamTag('orderBy', [ 'mixed' ], 'The order string');
1151
            $parameters[] = new ParameterGenerator('additionalTablesFetch', 'array', []);
1152
            $params[] = new ParamTag('additionalTablesFetch', [ 'string[]' ], 'A list of additional tables to fetch (for performance improvement)');
1153
            $parameters[] = (new ParameterGenerator('mode', '?int'))->setDefaultValue(null);
1154
            $params[] = new ParamTag('mode', [ 'int', 'null' ], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.');
1155
            $params[] = new ReturnTag([ '\\'.$beanNamespace.'\\'.$beanClassName.'[]', '\\'.ResultIterator::class ]);
1156
            $method->setReturnType('iterable');
1157
1158
            $docBlock = new DocBlockGenerator("Get a list of $beanClassName filtered by ".implode(', ', $commentArguments).".", null, $params);
1159
            $docBlock->setWordWrap(false);
1160
1161
            $body = "\$filter = [
1162
".$filterArrayCode."        ];
1163
return \$this->find(\$filter, [], \$orderBy, \$additionalTablesFetch, \$mode);
1164
";
1165
        }
1166
1167
        $method->setParameters($parameters);
1168
        $method->setDocBlock($docBlock);
1169
        $method->setBody($body);
1170
1171
        return $method;
1172
    }
1173
1174
    /**
1175
     * Generates the code for the getUsedTable protected method.
1176
     *
1177
     * @return MethodGenerator
1178
     */
1179
    private function generateGetUsedTablesCode(): MethodGenerator
1180
    {
1181
        $hasParentRelationship = $this->schemaAnalyzer->getParentRelationship($this->table->getName()) !== null;
1182
        if ($hasParentRelationship) {
1183
            $code = sprintf('$tables = parent::getUsedTables();
1184
$tables[] = %s;
1185
1186
return $tables;', var_export($this->table->getName(), true));
1187
        } else {
1188
            $code = sprintf('        return [ %s ];', var_export($this->table->getName(), true));
1189
        }
1190
1191
        $method = new MethodGenerator('getUsedTables');
1192
        $method->setDocBlock('Returns an array of used tables by this bean (from parent to child relationship).');
1193
        $method->getDocBlock()->setTag(new ReturnTag(['string[]']));
1194
        $method->setReturnType('array');
1195
        $method->setBody($code);
1196
1197
        return $method;
1198
    }
1199
1200
    private function generateOnDeleteCode(): ?MethodGenerator
1201
    {
1202
        $code = '';
1203
        $relationships = $this->getPropertiesForTable($this->table);
1204
        foreach ($relationships as $relationship) {
1205
            if ($relationship instanceof ObjectBeanPropertyDescriptor) {
1206
                $code .= sprintf('$this->setRef('.var_export($relationship->getForeignKey()->getName(), true).', null, '.var_export($this->table->getName(), true).");\n");
1207
            }
1208
        }
1209
1210
        if (!$code) {
1211
            return null;
1212
        }
1213
1214
        $method = new MethodGenerator('onDelete');
1215
        $method->setDocBlock('Method called when the bean is removed from database.');
1216
        $method->setReturnType('void');
1217
        $method->setBody('parent::onDelete();
1218
'.$code);
1219
1220
        return $method;
1221
    }
1222
1223
    private function generateCloneCode(): ?MethodGenerator
1224
    {
1225
        $code = '';
1226
1227
        foreach ($this->beanPropertyDescriptors as $beanPropertyDescriptor) {
1228
            $code .= $beanPropertyDescriptor->getCloneRule();
1229
        }
1230
1231
        $method = new MethodGenerator('__clone');
1232
        $method->setBody('parent::__clone();
1233
'.$code);
1234
1235
        return $method;
1236
    }
1237
1238
    /**
1239
     * Returns the bean class name (without the namespace).
1240
     *
1241
     * @return string
1242
     */
1243
    public function getBeanClassName() : string
1244
    {
1245
        return $this->namingStrategy->getBeanClassName($this->table->getName());
1246
    }
1247
1248
    /**
1249
     * Returns the base bean class name (without the namespace).
1250
     *
1251
     * @return string
1252
     */
1253
    public function getBaseBeanClassName() : string
1254
    {
1255
        return $this->namingStrategy->getBaseBeanClassName($this->table->getName());
1256
    }
1257
1258
    /**
1259
     * Returns the DAO class name (without the namespace).
1260
     *
1261
     * @return string
1262
     */
1263
    public function getDaoClassName() : string
1264
    {
1265
        return $this->namingStrategy->getDaoClassName($this->table->getName());
1266
    }
1267
1268
    /**
1269
     * Returns the base DAO class name (without the namespace).
1270
     *
1271
     * @return string
1272
     */
1273
    public function getBaseDaoClassName() : string
1274
    {
1275
        return $this->namingStrategy->getBaseDaoClassName($this->table->getName());
1276
    }
1277
1278
    /**
1279
     * Returns the table used to build this bean.
1280
     *
1281
     * @return Table
1282
     */
1283
    public function getTable(): Table
1284
    {
1285
        return $this->table;
1286
    }
1287
1288
    /**
1289
     * Returns the extended bean class name (without the namespace), or null if the bean is not extended.
1290
     *
1291
     * @return string
1292
     */
1293
    public function getExtendedBeanClassName(): ?string
1294
    {
1295
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
1296
        if ($parentFk !== null) {
1297
            return $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName());
1298
        } else {
1299
            return null;
1300
        }
1301
    }
1302
1303
    /**
1304
     * @return string
1305
     */
1306
    public function getBeanNamespace(): string
1307
    {
1308
        return $this->beanNamespace;
1309
    }
1310
1311
    /**
1312
     * @return string
1313
     */
1314
    public function getGeneratedBeanNamespace(): string
1315
    {
1316
        return $this->generatedBeanNamespace;
1317
    }
1318
}
1319