Passed
Pull Request — master (#116)
by David
05:35
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
This file has been automatically generated by TDBM.
512
DO NOT edit this file, as it might be overwritten.
513
If you need to perform changes, edit the $className class instead!
514
EOF
515
        ));
516
517
        $class->setDocBlock(new DocBlockGenerator("The $baseClassName class maps the '$tableName' table in database."));
518
        $class->setImplementedInterfaces([ JsonSerializable::class ]);
519
520
521
        $class->addMethodFromGenerator($this->generateBeanConstructor());
522
523
        foreach ($this->getExposedProperties() as $property) {
524
            foreach ($property->getGetterSetterCode() as $generator) {
525
                $class->addMethodFromGenerator($generator);
526
            }
527
        }
528
529
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
530
            foreach ($methodDescriptor->getCode() as $generator) {
531
                $class->addMethodFromGenerator($generator);
532
            }
533
        }
534
535
        $class->addMethodFromGenerator($this->generateJsonSerialize());
536
        $class->addMethodFromGenerator($this->generateGetUsedTablesCode());
537
        $onDeleteCode = $this->generateOnDeleteCode();
538
        if ($onDeleteCode) {
539
            $class->addMethodFromGenerator($onDeleteCode);
540
        }
541
        $cloneCode = $this->generateCloneCode();
542
        if ($cloneCode) {
0 ignored issues
show
introduced by
$cloneCode is of type Zend\Code\Generator\MethodGenerator, thus it always evaluated to true.
Loading history...
543
            $class->addMethodFromGenerator($cloneCode);
544
        }
545
546
        return $file;
547
    }
548
549
    /**
550
     * @param string $beanNamespace
551
     * @param string $beanClassName
552
     *
553
     * @return mixed[] first element: list of used beans, second item: PHP code as a string
554
     */
555
    public function generateFindByDaoCode(string $beanNamespace, string $beanClassName): array
556
    {
557
        $code = '';
558
        $usedBeans = [];
559
        foreach ($this->removeDuplicateIndexes($this->table->getIndexes()) as $index) {
560
            if (!$index->isPrimary()) {
561
                list($usedBeansForIndex, $codeForIndex) = $this->generateFindByDaoCodeForIndex($index, $beanNamespace, $beanClassName);
562
                $code .= $codeForIndex;
563
                $usedBeans = array_merge($usedBeans, $usedBeansForIndex);
564
            }
565
        }
566
567
        return [$usedBeans, $code];
568
    }
569
570
    /**
571
     * Remove identical indexes (indexes on same columns)
572
     *
573
     * @param Index[] $indexes
574
     * @return Index[]
575
     */
576
    private function removeDuplicateIndexes(array $indexes): array
577
    {
578
        $indexesByKey = [];
579
        foreach ($indexes as $index) {
580
            $indexesByKey[implode('__`__', $index->getUnquotedColumns())] = $index;
581
        }
582
583
        return array_values($indexesByKey);
584
    }
585
586
    /**
587
     * @param Index  $index
588
     * @param string $beanNamespace
589
     * @param string $beanClassName
590
     *
591
     * @return mixed[] first element: list of used beans, second item: PHP code as a string
592
     */
593
    private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespace, string $beanClassName): array
594
    {
595
        $columns = $index->getColumns();
596
        $usedBeans = [];
597
598
        /**
599
         * The list of elements building this index (expressed as columns or foreign keys)
600
         * @var AbstractBeanPropertyDescriptor[]
601
         */
602
        $elements = [];
603
604
        foreach ($columns as $column) {
605
            $fk = $this->isPartOfForeignKey($this->table, $this->table->getColumn($column));
606
            if ($fk !== null) {
607
                if (!in_array($fk, $elements)) {
608
                    $elements[] = new ObjectBeanPropertyDescriptor($this->table, $fk, $this->namingStrategy, $this->beanNamespace);
609
                }
610
            } else {
611
                $elements[] = new ScalarBeanPropertyDescriptor($this->table, $this->table->getColumn($column), $this->namingStrategy, $this->annotationParser);
612
            }
613
        }
614
615
        // If the index is actually only a foreign key, let's bypass it entirely.
616
        if (count($elements) === 1 && $elements[0] instanceof ObjectBeanPropertyDescriptor) {
617
            return [[], ''];
618
        }
619
620
        $functionParameters = [];
621
        $first = true;
622
        foreach ($elements as $element) {
623
            if (!$first) {
624
                $functionParameter = '?';
625
            } else {
626
                $functionParameter = '';
627
            }
628
            $functionParameter .= $element->getPhpType();
629
            $elementClassName = $element->getClassName();
630
            if ($elementClassName) {
631
                $usedBeans[] = $beanNamespace.'\\'.$elementClassName;
632
            }
633
            $functionParameter .= ' '.$element->getVariableName();
634
            if ($first) {
635
                $first = false;
636
            } else {
637
                $functionParameter .= ' = null';
638
            }
639
            $functionParameters[] = $functionParameter;
640
        }
641
642
        $functionParametersString = implode(', ', $functionParameters);
643
644
        $count = 0;
645
646
        $params = [];
647
        $filterArrayCode = '';
648
        $commentArguments = [];
649
        $first = true;
650
        foreach ($elements as $element) {
651
            $params[] = $element->getParamAnnotation();
652
            if ($element instanceof ScalarBeanPropertyDescriptor) {
653
                $filterArrayCode .= '            '.var_export($element->getColumnName(), true).' => '.$element->getVariableName().",\n";
654
            } elseif ($element instanceof ObjectBeanPropertyDescriptor) {
655
                $foreignKey = $element->getForeignKey();
656
                $columns = SafeFunctions::arrayCombine($foreignKey->getLocalColumns(), $foreignKey->getForeignColumns());
657
                ++$count;
658
                $foreignTable = $this->schema->getTable($foreignKey->getForeignTableName());
659
                foreach ($columns as $localColumn => $foreignColumn) {
660
                    // 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.
661
                    $targetedElement = new ScalarBeanPropertyDescriptor($foreignTable, $foreignTable->getColumn($foreignColumn), $this->namingStrategy, $this->annotationParser);
662
                    if ($first) {
663
                        // First parameter for index is not nullable
664
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => '.$element->getVariableName().'->'.$targetedElement->getGetterName()."(),\n";
665
                    } else {
666
                        // Other parameters for index is not nullable
667
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => ('.$element->getVariableName().' !== null) ? '.$element->getVariableName().'->'.$targetedElement->getGetterName()."() : null,\n";
668
                    }
669
                }
670
            }
671
            $commentArguments[] = substr($element->getVariableName(), 1);
672
            if ($first) {
673
                $first = false;
674
            }
675
        }
676
677
        $paramsString = '';
678
        foreach ($params as $param) {
679
            $paramsString .= $param->generate()."\n";
680
        }
681
        //$paramsString = implode("\n", $params);
682
683
        $methodName = $this->namingStrategy->getFindByIndexMethodName($index, $elements);
684
685
        if ($index->isUnique()) {
686
            $returnType = $beanClassName;
687
688
            $code = "
689
    /**
690
     * Get a $beanClassName filtered by ".implode(', ', $commentArguments).".
691
     *
692
$paramsString
693
     * @param string[] \$additionalTablesFetch A list of additional tables to fetch (for performance improvement)
694
     * @return $returnType|null
695
     */
696
    public function $methodName($functionParametersString, array \$additionalTablesFetch = array()) : ?$returnType
697
    {
698
        \$filter = [
699
".$filterArrayCode."        ];
700
        return \$this->findOne(\$filter, [], \$additionalTablesFetch);
701
    }
702
";
703
        } else {
704
            $returnType = "{$beanClassName}[]|ResultIterator";
705
706
            $code = "
707
    /**
708
     * Get a list of $beanClassName filtered by ".implode(', ', $commentArguments).".
709
     *
710
$paramsString
711
     * @param mixed \$orderBy The order string
712
     * @param string[] \$additionalTablesFetch A list of additional tables to fetch (for performance improvement)
713
     * @param int \$mode Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.
714
     * @return $returnType
715
     */
716
    public function $methodName($functionParametersString, \$orderBy = null, array \$additionalTablesFetch = array(), ?int \$mode = null) : iterable
717
    {
718
        \$filter = [
719
".$filterArrayCode."        ];
720
        return \$this->find(\$filter, [], \$orderBy, \$additionalTablesFetch, \$mode);
721
    }
722
";
723
        }
724
725
        return [$usedBeans, $code];
726
    }
727
728
    /**
729
     * Generates the code for the getUsedTable protected method.
730
     *
731
     * @return MethodGenerator
732
     */
733
    private function generateGetUsedTablesCode(): MethodGenerator
734
    {
735
        $hasParentRelationship = $this->schemaAnalyzer->getParentRelationship($this->table->getName()) !== null;
736
        if ($hasParentRelationship) {
737
            $code = sprintf('$tables = parent::getUsedTables();
738
$tables[] = %s;
739
740
return $tables;', var_export($this->table->getName(), true));
741
        } else {
742
            $code = sprintf('        return [ %s ];', var_export($this->table->getName(), true));
743
        }
744
745
        $method = new MethodGenerator('getUsedTables');
746
        $method->setDocBlock('Returns an array of used tables by this bean (from parent to child relationship).');
747
        $method->getDocBlock()->setTag(new ReturnTag(['string[]']));
748
        $method->setReturnType('array');
749
        $method->setBody($code);
750
751
        return $method;
752
    }
753
754
    private function generateOnDeleteCode(): ?MethodGenerator
755
    {
756
        $code = '';
757
        $relationships = $this->getPropertiesForTable($this->table);
758
        foreach ($relationships as $relationship) {
759
            if ($relationship instanceof ObjectBeanPropertyDescriptor) {
760
                $code .= sprintf('$this->setRef('.var_export($relationship->getForeignKey()->getName(), true).', null, '.var_export($this->table->getName(), true).");\n");
761
            }
762
        }
763
764
        if (!$code) {
765
            return null;
766
        }
767
768
        $method = new MethodGenerator('onDelete');
769
        $method->setDocBlock('Method called when the bean is removed from database.');
770
        $method->setReturnType('void');
771
        $method->setBody('parent::onDelete();
772
'.$code);
773
774
        return $method;
775
    }
776
777
    private function generateCloneCode(): ?MethodGenerator
778
    {
779
        $code = '';
780
781
        foreach ($this->beanPropertyDescriptors as $beanPropertyDescriptor) {
782
            $code .= $beanPropertyDescriptor->getCloneRule();
783
        }
784
785
        $method = new MethodGenerator('__clone');
786
        $method->setBody('parent::__clone();
787
'.$code);
788
789
        return $method;
790
    }
791
792
    /**
793
     * Returns the bean class name (without the namespace).
794
     *
795
     * @return string
796
     */
797
    public function getBeanClassName() : string
798
    {
799
        return $this->namingStrategy->getBeanClassName($this->table->getName());
800
    }
801
802
    /**
803
     * Returns the base bean class name (without the namespace).
804
     *
805
     * @return string
806
     */
807
    public function getBaseBeanClassName() : string
808
    {
809
        return $this->namingStrategy->getBaseBeanClassName($this->table->getName());
810
    }
811
812
    /**
813
     * Returns the DAO class name (without the namespace).
814
     *
815
     * @return string
816
     */
817
    public function getDaoClassName() : string
818
    {
819
        return $this->namingStrategy->getDaoClassName($this->table->getName());
820
    }
821
822
    /**
823
     * Returns the base DAO class name (without the namespace).
824
     *
825
     * @return string
826
     */
827
    public function getBaseDaoClassName() : string
828
    {
829
        return $this->namingStrategy->getBaseDaoClassName($this->table->getName());
830
    }
831
832
    /**
833
     * Returns the table used to build this bean.
834
     *
835
     * @return Table
836
     */
837
    public function getTable(): Table
838
    {
839
        return $this->table;
840
    }
841
842
    /**
843
     * Returns the extended bean class name (without the namespace), or null if the bean is not extended.
844
     *
845
     * @return string
846
     */
847
    public function getExtendedBeanClassName(): ?string
848
    {
849
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
850
        if ($parentFk !== null) {
851
            return $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName());
852
        } else {
853
            return null;
854
        }
855
    }
856
857
    /**
858
     * @return string
859
     */
860
    public function getBeanNamespace(): string
861
    {
862
        return $this->beanNamespace;
863
    }
864
865
    /**
866
     * @return string
867
     */
868
    public function getGeneratedBeanNamespace(): string
869
    {
870
        return $this->generatedBeanNamespace;
871
    }
872
}
873