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

BeanDescriptor::isPartOfForeignKey()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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