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

BeanDescriptor::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 11
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 8

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 Ramsey\Uuid\Uuid;
14
use TheCodingMachine\TDBM\AbstractTDBMObject;
15
use TheCodingMachine\TDBM\AlterableResultIterator;
16
use TheCodingMachine\TDBM\ResultIterator;
17
use TheCodingMachine\TDBM\SafeFunctions;
18
use TheCodingMachine\TDBM\TDBMException;
19
use TheCodingMachine\TDBM\TDBMSchemaAnalyzer;
20
use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser;
21
use Zend\Code\Generator\ClassGenerator;
22
use Zend\Code\Generator\DocBlock\Tag\ParamTag;
23
use Zend\Code\Generator\DocBlock\Tag\ReturnTag;
24
use Zend\Code\Generator\DocBlockGenerator;
25
use Zend\Code\Generator\FileGenerator;
26
use Zend\Code\Generator\MethodGenerator;
27
use Zend\Code\Generator\ParameterGenerator;
28
29
/**
30
 * This class represents a bean.
31
 */
32
class BeanDescriptor implements BeanDescriptorInterface
33
{
34
    /**
35
     * @var Table
36
     */
37
    private $table;
38
39
    /**
40
     * @var SchemaAnalyzer
41
     */
42
    private $schemaAnalyzer;
43
44
    /**
45
     * @var Schema
46
     */
47
    private $schema;
48
49
    /**
50
     * @var AbstractBeanPropertyDescriptor[]
51
     */
52
    private $beanPropertyDescriptors = [];
53
54
    /**
55
     * @var TDBMSchemaAnalyzer
56
     */
57
    private $tdbmSchemaAnalyzer;
58
59
    /**
60
     * @var NamingStrategyInterface
61
     */
62
    private $namingStrategy;
63
    /**
64
     * @var string
65
     */
66
    private $beanNamespace;
67
    /**
68
     * @var string
69
     */
70
    private $generatedBeanNamespace;
71
    /**
72
     * @var AnnotationParser
73
     */
74
    private $annotationParser;
75
76
    /**
77
     * @param Table $table
78
     * @param string $beanNamespace
79
     * @param string $generatedBeanNamespace
80
     * @param SchemaAnalyzer $schemaAnalyzer
81
     * @param Schema $schema
82
     * @param TDBMSchemaAnalyzer $tdbmSchemaAnalyzer
83
     * @param NamingStrategyInterface $namingStrategy
84
     * @param AnnotationParser $annotationParser
85
     */
86
    public function __construct(Table $table, string $beanNamespace, string $generatedBeanNamespace, SchemaAnalyzer $schemaAnalyzer, Schema $schema, TDBMSchemaAnalyzer $tdbmSchemaAnalyzer, NamingStrategyInterface $namingStrategy, AnnotationParser $annotationParser)
87
    {
88
        $this->table = $table;
89
        $this->beanNamespace = $beanNamespace;
90
        $this->generatedBeanNamespace = $generatedBeanNamespace;
91
        $this->schemaAnalyzer = $schemaAnalyzer;
92
        $this->schema = $schema;
93
        $this->tdbmSchemaAnalyzer = $tdbmSchemaAnalyzer;
94
        $this->namingStrategy = $namingStrategy;
95
        $this->annotationParser = $annotationParser;
96
        $this->initBeanPropertyDescriptors();
97
    }
98
99
    private function initBeanPropertyDescriptors(): void
100
    {
101
        $this->beanPropertyDescriptors = $this->getProperties($this->table);
102
    }
103
104
    /**
105
     * Returns the foreign-key the column is part of, if any. null otherwise.
106
     *
107
     * @param Table  $table
108
     * @param Column $column
109
     *
110
     * @return ForeignKeyConstraint|null
111
     */
112
    private function isPartOfForeignKey(Table $table, Column $column) : ?ForeignKeyConstraint
113
    {
114
        $localColumnName = $column->getName();
115
        foreach ($table->getForeignKeys() as $foreignKey) {
116
            foreach ($foreignKey->getUnquotedLocalColumns() as $columnName) {
117
                if ($columnName === $localColumnName) {
118
                    return $foreignKey;
119
                }
120
            }
121
        }
122
123
        return null;
124
    }
125
126
    /**
127
     * @return AbstractBeanPropertyDescriptor[]
128
     */
129
    public function getBeanPropertyDescriptors(): array
130
    {
131
        return $this->beanPropertyDescriptors;
132
    }
133
134
    /**
135
     * Returns the list of columns that are not nullable and not autogenerated for a given table and its parent.
136
     *
137
     * @return AbstractBeanPropertyDescriptor[]
138
     */
139
    public function getConstructorProperties(): array
140
    {
141
        $constructorProperties = array_filter($this->beanPropertyDescriptors, function (AbstractBeanPropertyDescriptor $property) {
142
            return $property->isCompulsory();
143
        });
144
145
        return $constructorProperties;
146
    }
147
148
    /**
149
     * Returns the list of columns that have default values for a given table.
150
     *
151
     * @return AbstractBeanPropertyDescriptor[]
152
     */
153
    public function getPropertiesWithDefault(): array
154
    {
155
        $properties = $this->getPropertiesForTable($this->table);
156
        $defaultProperties = array_filter($properties, function (AbstractBeanPropertyDescriptor $property) {
157
            return $property->hasDefault();
158
        });
159
160
        return $defaultProperties;
161
    }
162
163
    /**
164
     * Returns the list of properties exposed as getters and setters in this class.
165
     *
166
     * @return AbstractBeanPropertyDescriptor[]
167
     */
168
    public function getExposedProperties(): array
169
    {
170
        $exposedProperties = array_filter($this->beanPropertyDescriptors, function (AbstractBeanPropertyDescriptor $property) {
171
            return $property->getTable()->getName() == $this->table->getName();
172
        });
173
174
        return $exposedProperties;
175
    }
176
177
    /**
178
     * Returns the list of properties for this table (including parent tables).
179
     *
180
     * @param Table $table
181
     *
182
     * @return AbstractBeanPropertyDescriptor[]
183
     */
184
    private function getProperties(Table $table): array
185
    {
186
        // Security check: a table MUST have a primary key
187
        TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($table);
188
189
        $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
190
        if ($parentRelationship) {
191
            $parentTable = $this->schema->getTable($parentRelationship->getForeignTableName());
192
            $properties = $this->getProperties($parentTable);
193
            // we merge properties by overriding property names.
194
            $localProperties = $this->getPropertiesForTable($table);
195
            foreach ($localProperties as $name => $property) {
196
                // We do not override properties if this is a primary key!
197
                if ($property->isPrimaryKey()) {
198
                    continue;
199
                }
200
                $properties[$name] = $property;
201
            }
202
        } else {
203
            $properties = $this->getPropertiesForTable($table);
204
        }
205
206
        return $properties;
207
    }
208
209
    /**
210
     * Returns the list of properties for this table (ignoring parent tables).
211
     *
212
     * @param Table $table
213
     *
214
     * @return AbstractBeanPropertyDescriptor[]
215
     */
216
    private function getPropertiesForTable(Table $table): array
217
    {
218
        $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
219
        if ($parentRelationship) {
220
            $ignoreColumns = $parentRelationship->getUnquotedLocalColumns();
221
        } else {
222
            $ignoreColumns = [];
223
        }
224
225
        $beanPropertyDescriptors = [];
226
        foreach ($table->getColumns() as $column) {
227
            if (array_search($column->getName(), $ignoreColumns) !== false) {
228
                continue;
229
            }
230
231
            $fk = $this->isPartOfForeignKey($table, $column);
232
            if ($fk !== null) {
233
                // Check that previously added descriptors are not added on same FK (can happen with multi key FK).
234
                foreach ($beanPropertyDescriptors as $beanDescriptor) {
235
                    if ($beanDescriptor instanceof ObjectBeanPropertyDescriptor && $beanDescriptor->getForeignKey() === $fk) {
236
                        continue 2;
237
                    }
238
                }
239
                // Check that this property is not an inheritance relationship
240
                $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
241
                if ($parentRelationship === $fk) {
242
                    continue;
243
                }
244
245
                $beanPropertyDescriptors[] = new ObjectBeanPropertyDescriptor($table, $fk, $this->namingStrategy, $this->beanNamespace);
246
            } else {
247
                $beanPropertyDescriptors[] = new ScalarBeanPropertyDescriptor($table, $column, $this->namingStrategy, $this->annotationParser);
248
            }
249
        }
250
251
        // Now, let's get the name of all properties and let's check there is no duplicate.
252
        /* @var $names AbstractBeanPropertyDescriptor[] */
253
        $names = [];
254
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
255
            $name = $beanDescriptor->getGetterName();
256
            if (isset($names[$name])) {
257
                $names[$name]->useAlternativeName();
258
                $beanDescriptor->useAlternativeName();
259
            } else {
260
                $names[$name] = $beanDescriptor;
261
            }
262
        }
263
264
        // Final check (throw exceptions if problem arises)
265
        $names = [];
266
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
267
            $name = $beanDescriptor->getGetterName();
268
            if (isset($names[$name])) {
269
                throw new TDBMException('Unsolvable name conflict while generating method name');
270
            } else {
271
                $names[$name] = $beanDescriptor;
272
            }
273
        }
274
275
        // Last step, let's rebuild the list with a map:
276
        $beanPropertyDescriptorsMap = [];
277
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
278
            $beanPropertyDescriptorsMap[$beanDescriptor->getVariableName()] = $beanDescriptor;
279
        }
280
281
        return $beanPropertyDescriptorsMap;
282
    }
283
284
    private function generateBeanConstructor() : MethodGenerator
285
    {
286
        $constructorProperties = $this->getConstructorProperties();
287
288
        $constructor = new MethodGenerator('__construct', [], MethodGenerator::FLAG_PUBLIC);
289
        $constructor->setDocBlock('The constructor takes all compulsory arguments.');
290
291
        $assigns = [];
292
        $parentConstructorArguments = [];
293
294
        foreach ($constructorProperties as $property) {
295
            $parameter = new ParameterGenerator(ltrim($property->getVariableName(), '$'));
296
            if ($property->isTypeHintable()) {
297
                $parameter->setType($property->getPhpType());
298
            }
299
            $constructor->setParameter($parameter);
300
301
            $constructor->getDocBlock()->setTag($property->getParamAnnotation());
302
303
            if ($property->getTable()->getName() === $this->table->getName()) {
304
                $assigns[] = $property->getConstructorAssignCode()."\n";
305
            } else {
306
                $parentConstructorArguments[] = $property->getVariableName();
307
            }
308
        }
309
310
        $parentConstructorCode = sprintf("parent::__construct(%s);\n", implode(', ', $parentConstructorArguments));
311
312
        foreach ($this->getPropertiesWithDefault() as $property) {
313
            $assigns[] = $property->assignToDefaultCode()."\n";
314
        }
315
316
        $body = $parentConstructorCode . implode('', $assigns);
317
318
        $constructor->setBody($body);
319
320
        return $constructor;
321
    }
322
323
    /**
324
     * Returns the descriptors of one-to-many relationships (the foreign keys pointing on this beans)
325
     *
326
     * @return DirectForeignKeyMethodDescriptor[]
327
     */
328
    private function getDirectForeignKeysDescriptors(): array
329
    {
330
        $fks = $this->tdbmSchemaAnalyzer->getIncomingForeignKeys($this->table->getName());
331
332
        $descriptors = [];
333
334
        foreach ($fks as $fk) {
335
            $descriptors[] = new DirectForeignKeyMethodDescriptor($fk, $this->table, $this->namingStrategy);
336
        }
337
338
        return $descriptors;
339
    }
340
341
    /**
342
     * @return PivotTableMethodsDescriptor[]
343
     */
344
    private function getPivotTableDescriptors(): array
345
    {
346
        $descs = [];
347
        foreach ($this->schemaAnalyzer->detectJunctionTables(true) as $table) {
348
            // There are exactly 2 FKs since this is a pivot table.
349
            $fks = array_values($table->getForeignKeys());
350
351
            if ($fks[0]->getForeignTableName() === $this->table->getName()) {
352
                list($localFk, $remoteFk) = $fks;
353
            } elseif ($fks[1]->getForeignTableName() === $this->table->getName()) {
354
                list($remoteFk, $localFk) = $fks;
355
            } else {
356
                continue;
357
            }
358
359
            $descs[] = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->beanNamespace);
360
        }
361
362
        return $descs;
363
    }
364
365
    /**
366
     * Returns the list of method descriptors (and applies the alternative name if needed).
367
     *
368
     * @return MethodDescriptorInterface[]
369
     */
370
    public function getMethodDescriptors(): array
371
    {
372
        $directForeignKeyDescriptors = $this->getDirectForeignKeysDescriptors();
373
        $pivotTableDescriptors = $this->getPivotTableDescriptors();
374
375
        $descriptors = array_merge($directForeignKeyDescriptors, $pivotTableDescriptors);
376
377
        // Descriptors by method names
378
        $descriptorsByMethodName = [];
379
380
        foreach ($descriptors as $descriptor) {
381
            $descriptorsByMethodName[$descriptor->getName()][] = $descriptor;
382
        }
383
384
        foreach ($descriptorsByMethodName as $descriptorsForMethodName) {
385
            if (count($descriptorsForMethodName) > 1) {
386
                foreach ($descriptorsForMethodName as $descriptor) {
387
                    $descriptor->useAlternativeName();
388
                }
389
            }
390
        }
391
392
        return $descriptors;
393
    }
394
395
    public function generateJsonSerialize(): MethodGenerator
396
    {
397
        $tableName = $this->table->getName();
398
        $parentFk = $this->schemaAnalyzer->getParentRelationship($tableName);
399
        if ($parentFk !== null) {
400
            $initializer = '$array = parent::jsonSerialize($stopRecursion);';
401
        } else {
402
            $initializer = '$array = [];';
403
        }
404
405
        $method = new MethodGenerator('jsonSerialize');
406
        $method->setDocBlock('Serializes the object for JSON encoding.');
407
        $method->getDocBlock()->setTag(new ParamTag('$stopRecursion', ['bool'], 'Parameter used internally by TDBM to stop embedded objects from embedding other objects.'));
408
        $method->getDocBlock()->setTag(new ReturnTag(['array']));
409
        $method->setParameter(new ParameterGenerator('stopRecursion', 'bool', false));
410
411
        $str = '%s
412
%s
413
%s
414
return $array;
415
';
416
417
        $propertiesCode = '';
418
        foreach ($this->getExposedProperties() as $beanPropertyDescriptor) {
419
            $propertiesCode .= $beanPropertyDescriptor->getJsonSerializeCode();
420
        }
421
422
        // Many2many relationships
423
        $methodsCode = '';
424
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
425
            $methodsCode .= $methodDescriptor->getJsonSerializeCode();
426
        }
427
428
        $method->setBody(sprintf($str, $initializer, $propertiesCode, $methodsCode));
429
430
        return $method;
431
    }
432
433
    /**
434
     * Returns as an array the class we need to extend from and the list of use statements.
435
     *
436
     * @param ForeignKeyConstraint|null $parentFk
437
     * @return string[]
438
     */
439
    private function generateExtendsAndUseStatements(ForeignKeyConstraint $parentFk = null): array
440
    {
441
        $classes = [];
442
        if ($parentFk !== null) {
443
            $extends = $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName());
444
            $classes[] = $extends;
445
        }
446
447
        foreach ($this->getBeanPropertyDescriptors() as $beanPropertyDescriptor) {
448
            $className = $beanPropertyDescriptor->getClassName();
449
            if (null !== $className) {
450
                $classes[] = $className;
451
            }
452
        }
453
454
        foreach ($this->getMethodDescriptors() as $descriptor) {
455
            $classes = array_merge($classes, $descriptor->getUsedClasses());
456
        }
457
458
        $classes = array_unique($classes);
459
460
        return $classes;
461
    }
462
463
    /**
464
     * Writes the PHP bean file with all getters and setters from the table passed in parameter.
465
     *
466
     * @return FileGenerator
467
     */
468
    public function generatePhpCode(): FileGenerator
469
    {
470
471
        $file = new FileGenerator();
472
        $class = new ClassGenerator();
473
        $file->setClass($class);
474
        $file->setNamespace($this->generatedBeanNamespace);
475
476
        $tableName = $this->table->getName();
477
        $baseClassName = $this->namingStrategy->getBaseBeanClassName($tableName);
478
        $className = $this->namingStrategy->getBeanClassName($tableName);
479
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
480
481
        $classes = $this->generateExtendsAndUseStatements($parentFk);
482
483
        foreach ($classes as $useClass) {
484
            $file->setUse($this->beanNamespace.'\\'.$useClass);
485
        }
486
487
        /*$uses = array_map(function ($className) {
488
            return 'use '.$this->beanNamespace.'\\'.$className.";\n";
489
        }, $classes);
490
        $use = implode('', $uses);*/
491
492
        $extends = $this->getExtendedBeanClassName();
493
        if ($extends === null) {
494
            $class->setExtendedClass(AbstractTDBMObject::class);
495
            $file->setUse(AbstractTDBMObject::class);
496
        } else {
497
            $class->setExtendedClass($extends);
498
        }
499
500
        $file->setUse(ResultIterator::class);
501
        $file->setUse(AlterableResultIterator::class);
502
        $file->setUse(Uuid::class);
503
        $file->setUse(JsonSerializable::class);
504
505
        $class->setName($baseClassName);
506
        $class->setAbstract(true);
507
508
        // TODO: CHECK AVAILABILITY OF declare(strict_types=1);
509
510
        $file->setDocBlock(new DocBlockGenerator('This file has been automatically generated by TDBM.', <<<EOF
511
DO NOT edit this file, as it might be overwritten.
512
If you need to perform changes, edit the $className class instead!
513
EOF
514
        ));
515
516
        $class->setDocBlock(new DocBlockGenerator("The $baseClassName class maps the '$tableName' table in database."));
517
        $class->setImplementedInterfaces([ JsonSerializable::class ]);
518
519
520
        $class->addMethodFromGenerator($this->generateBeanConstructor());
521
522
        foreach ($this->getExposedProperties() as $property) {
523
            foreach ($property->getGetterSetterCode() as $generator) {
524
                $class->addMethodFromGenerator($generator);
525
            }
526
        }
527
528
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
529
            foreach ($methodDescriptor->getCode() as $generator) {
530
                $class->addMethodFromGenerator($generator);
531
            }
532
        }
533
534
        $class->addMethodFromGenerator($this->generateJsonSerialize());
535
        $class->addMethodFromGenerator($this->generateGetUsedTablesCode());
536
        $onDeleteCode = $this->generateOnDeleteCode();
537
        if ($onDeleteCode) {
538
            $class->addMethodFromGenerator($onDeleteCode);
539
        }
540
        $cloneCode = $this->generateCloneCode();
541
        if ($cloneCode) {
0 ignored issues
show
introduced by
$cloneCode is of type Zend\Code\Generator\MethodGenerator, thus it always evaluated to true.
Loading history...
542
            $class->addMethodFromGenerator($cloneCode);
543
        }
544
545
        return $file;
546
    }
547
548
    /**
549
     * @param string $beanNamespace
550
     * @param string $beanClassName
551
     *
552
     * @return mixed[] first element: list of used beans, second item: PHP code as a string
553
     */
554
    public function generateFindByDaoCode(string $beanNamespace, string $beanClassName): array
555
    {
556
        $code = '';
557
        $usedBeans = [];
558
        foreach ($this->removeDuplicateIndexes($this->table->getIndexes()) as $index) {
559
            if (!$index->isPrimary()) {
560
                list($usedBeansForIndex, $codeForIndex) = $this->generateFindByDaoCodeForIndex($index, $beanNamespace, $beanClassName);
561
                $code .= $codeForIndex;
562
                $usedBeans = array_merge($usedBeans, $usedBeansForIndex);
563
            }
564
        }
565
566
        return [$usedBeans, $code];
567
    }
568
569
    /**
570
     * Remove identical indexes (indexes on same columns)
571
     *
572
     * @param Index[] $indexes
573
     * @return Index[]
574
     */
575
    private function removeDuplicateIndexes(array $indexes): array
576
    {
577
        $indexesByKey = [];
578
        foreach ($indexes as $index) {
579
            $indexesByKey[implode('__`__', $index->getUnquotedColumns())] = $index;
580
        }
581
582
        return array_values($indexesByKey);
583
    }
584
585
    /**
586
     * @param Index  $index
587
     * @param string $beanNamespace
588
     * @param string $beanClassName
589
     *
590
     * @return mixed[] first element: list of used beans, second item: PHP code as a string
591
     */
592
    private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespace, string $beanClassName): array
593
    {
594
        $columns = $index->getColumns();
595
        $usedBeans = [];
596
597
        /**
598
         * The list of elements building this index (expressed as columns or foreign keys)
599
         * @var AbstractBeanPropertyDescriptor[]
600
         */
601
        $elements = [];
602
603
        foreach ($columns as $column) {
604
            $fk = $this->isPartOfForeignKey($this->table, $this->table->getColumn($column));
605
            if ($fk !== null) {
606
                if (!in_array($fk, $elements)) {
607
                    $elements[] = new ObjectBeanPropertyDescriptor($this->table, $fk, $this->namingStrategy, $this->beanNamespace);
608
                }
609
            } else {
610
                $elements[] = new ScalarBeanPropertyDescriptor($this->table, $this->table->getColumn($column), $this->namingStrategy, $this->annotationParser);
611
            }
612
        }
613
614
        // If the index is actually only a foreign key, let's bypass it entirely.
615
        if (count($elements) === 1 && $elements[0] instanceof ObjectBeanPropertyDescriptor) {
616
            return [[], ''];
617
        }
618
619
        $functionParameters = [];
620
        $first = true;
621
        foreach ($elements as $element) {
622
            if (!$first) {
623
                $functionParameter = '?';
624
            } else {
625
                $functionParameter = '';
626
            }
627
            $functionParameter .= $element->getPhpType();
628
            $elementClassName = $element->getClassName();
629
            if ($elementClassName) {
630
                $usedBeans[] = $beanNamespace.'\\'.$elementClassName;
631
            }
632
            $functionParameter .= ' '.$element->getVariableName();
633
            if ($first) {
634
                $first = false;
635
            } else {
636
                $functionParameter .= ' = null';
637
            }
638
            $functionParameters[] = $functionParameter;
639
        }
640
641
        $functionParametersString = implode(', ', $functionParameters);
642
643
        $count = 0;
644
645
        $params = [];
646
        $filterArrayCode = '';
647
        $commentArguments = [];
648
        $first = true;
649
        foreach ($elements as $element) {
650
            $params[] = $element->getParamAnnotation();
651
            if ($element instanceof ScalarBeanPropertyDescriptor) {
652
                $filterArrayCode .= '            '.var_export($element->getColumnName(), true).' => '.$element->getVariableName().",\n";
653
            } elseif ($element instanceof ObjectBeanPropertyDescriptor) {
654
                $foreignKey = $element->getForeignKey();
655
                $columns = SafeFunctions::arrayCombine($foreignKey->getLocalColumns(), $foreignKey->getForeignColumns());
656
                ++$count;
657
                $foreignTable = $this->schema->getTable($foreignKey->getForeignTableName());
658
                foreach ($columns as $localColumn => $foreignColumn) {
659
                    // 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.
660
                    $targetedElement = new ScalarBeanPropertyDescriptor($foreignTable, $foreignTable->getColumn($foreignColumn), $this->namingStrategy, $this->annotationParser);
661
                    if ($first) {
662
                        // First parameter for index is not nullable
663
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => '.$element->getVariableName().'->'.$targetedElement->getGetterName()."(),\n";
664
                    } else {
665
                        // Other parameters for index is not nullable
666
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => ('.$element->getVariableName().' !== null) ? '.$element->getVariableName().'->'.$targetedElement->getGetterName()."() : null,\n";
667
                    }
668
                }
669
            }
670
            $commentArguments[] = substr($element->getVariableName(), 1);
671
            if ($first) {
672
                $first = false;
673
            }
674
        }
675
676
        $paramsString = '';
677
        foreach ($params as $param) {
678
            $paramsString .= $param->generate()."\n";
679
        }
680
        //$paramsString = implode("\n", $params);
681
682
        $methodName = $this->namingStrategy->getFindByIndexMethodName($index, $elements);
683
684
        if ($index->isUnique()) {
685
            $returnType = $beanClassName;
686
687
            $code = "
688
    /**
689
     * Get a $beanClassName filtered by ".implode(', ', $commentArguments).".
690
     *
691
$paramsString
692
     * @param string[] \$additionalTablesFetch A list of additional tables to fetch (for performance improvement)
693
     * @return $returnType|null
694
     */
695
    public function $methodName($functionParametersString, array \$additionalTablesFetch = array()) : ?$returnType
696
    {
697
        \$filter = [
698
".$filterArrayCode."        ];
699
        return \$this->findOne(\$filter, [], \$additionalTablesFetch);
700
    }
701
";
702
        } else {
703
            $returnType = "{$beanClassName}[]|ResultIterator";
704
705
            $code = "
706
    /**
707
     * Get a list of $beanClassName filtered by ".implode(', ', $commentArguments).".
708
     *
709
$paramsString
710
     * @param mixed \$orderBy The order string
711
     * @param string[] \$additionalTablesFetch A list of additional tables to fetch (for performance improvement)
712
     * @param int \$mode Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.
713
     * @return $returnType
714
     */
715
    public function $methodName($functionParametersString, \$orderBy = null, array \$additionalTablesFetch = array(), ?int \$mode = null) : iterable
716
    {
717
        \$filter = [
718
".$filterArrayCode."        ];
719
        return \$this->find(\$filter, [], \$orderBy, \$additionalTablesFetch, \$mode);
720
    }
721
";
722
        }
723
724
        return [$usedBeans, $code];
725
    }
726
727
    /**
728
     * Generates the code for the getUsedTable protected method.
729
     *
730
     * @return MethodGenerator
731
     */
732
    private function generateGetUsedTablesCode(): MethodGenerator
733
    {
734
        $hasParentRelationship = $this->schemaAnalyzer->getParentRelationship($this->table->getName()) !== null;
735
        if ($hasParentRelationship) {
736
            $code = sprintf('$tables = parent::getUsedTables();
737
$tables[] = %s;
738
739
return $tables;', var_export($this->table->getName(), true));
740
        } else {
741
            $code = sprintf('        return [ %s ];', var_export($this->table->getName(), true));
742
        }
743
744
        $method = new MethodGenerator('getUsedTables');
745
        $method->setDocBlock('Returns an array of used tables by this bean (from parent to child relationship).');
746
        $method->getDocBlock()->setTag(new ReturnTag(['string[]']));
747
        $method->setReturnType('array');
748
        $method->setBody($code);
749
750
        return $method;
751
    }
752
753
    private function generateOnDeleteCode(): ?MethodGenerator
754
    {
755
        $code = '';
756
        $relationships = $this->getPropertiesForTable($this->table);
757
        foreach ($relationships as $relationship) {
758
            if ($relationship instanceof ObjectBeanPropertyDescriptor) {
759
                $code .= sprintf('$this->setRef('.var_export($relationship->getForeignKey()->getName(), true).', null, '.var_export($this->table->getName(), true).");\n");
760
            }
761
        }
762
763
        if (!$code) {
764
            return null;
765
        }
766
767
        $method = new MethodGenerator('onDelete');
768
        $method->setDocBlock('Method called when the bean is removed from database.');
769
        $method->setReturnType('void');
770
        $method->setBody('parent::onDelete();
771
'.$code);
772
773
        return $method;
774
    }
775
776
    private function generateCloneCode(): ?MethodGenerator
777
    {
778
        $code = '';
779
780
        foreach ($this->beanPropertyDescriptors as $beanPropertyDescriptor) {
781
            $code .= $beanPropertyDescriptor->getCloneRule();
782
        }
783
784
        $method = new MethodGenerator('__clone');
785
        $method->setBody('parent::__clone();
786
'.$code);
787
788
        return $method;
789
    }
790
791
    /**
792
     * Returns the bean class name (without the namespace).
793
     *
794
     * @return string
795
     */
796
    public function getBeanClassName() : string
797
    {
798
        return $this->namingStrategy->getBeanClassName($this->table->getName());
799
    }
800
801
    /**
802
     * Returns the base bean class name (without the namespace).
803
     *
804
     * @return string
805
     */
806
    public function getBaseBeanClassName() : string
807
    {
808
        return $this->namingStrategy->getBaseBeanClassName($this->table->getName());
809
    }
810
811
    /**
812
     * Returns the DAO class name (without the namespace).
813
     *
814
     * @return string
815
     */
816
    public function getDaoClassName() : string
817
    {
818
        return $this->namingStrategy->getDaoClassName($this->table->getName());
819
    }
820
821
    /**
822
     * Returns the base DAO class name (without the namespace).
823
     *
824
     * @return string
825
     */
826
    public function getBaseDaoClassName() : string
827
    {
828
        return $this->namingStrategy->getBaseDaoClassName($this->table->getName());
829
    }
830
831
    /**
832
     * Returns the table used to build this bean.
833
     *
834
     * @return Table
835
     */
836
    public function getTable(): Table
837
    {
838
        return $this->table;
839
    }
840
841
    /**
842
     * Returns the extended bean class name (without the namespace), or null if the bean is not extended.
843
     *
844
     * @return string
845
     */
846
    public function getExtendedBeanClassName(): ?string
847
    {
848
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
849
        if ($parentFk !== null) {
850
            return $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName());
851
        } else {
852
            return null;
853
        }
854
    }
855
856
    /**
857
     * @return string
858
     */
859
    public function getBeanNamespace(): string
860
    {
861
        return $this->beanNamespace;
862
    }
863
864
    /**
865
     * @return string
866
     */
867
    public function getGeneratedBeanNamespace(): string
868
    {
869
        return $this->generatedBeanNamespace;
870
    }
871
}
872