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