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

BeanDescriptor::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 13
rs 9.9
c 0
b 0
f 0
cc 1
nc 1
nop 10

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
declare(strict_types=1);
3
4
namespace TheCodingMachine\TDBM\Utils;
5
6
use Doctrine\DBAL\Schema\Column;
7
use Doctrine\DBAL\Schema\Index;
8
use Doctrine\DBAL\Schema\Schema;
9
use Doctrine\DBAL\Schema\Table;
10
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
11
use JsonSerializable;
12
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
13
use PhpParser\Comment\Doc;
14
use Ramsey\Uuid\Uuid;
15
use TheCodingMachine\TDBM\AbstractTDBMObject;
16
use TheCodingMachine\TDBM\AlterableResultIterator;
17
use TheCodingMachine\TDBM\ResultIterator;
18
use TheCodingMachine\TDBM\SafeFunctions;
19
use TheCodingMachine\TDBM\TDBMException;
20
use TheCodingMachine\TDBM\TDBMSchemaAnalyzer;
21
use TheCodingMachine\TDBM\TDBMService;
22
use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser;
23
use Zend\Code\Generator\ClassGenerator;
24
use Zend\Code\Generator\DocBlock\Tag\ParamTag;
25
use Zend\Code\Generator\DocBlock\Tag\ReturnTag;
26
use Zend\Code\Generator\DocBlock\Tag\ThrowsTag;
27
use Zend\Code\Generator\DocBlock\Tag\VarTag;
28
use Zend\Code\Generator\DocBlockGenerator;
29
use Zend\Code\Generator\FileGenerator;
30
use Zend\Code\Generator\MethodGenerator;
31
use Zend\Code\Generator\ParameterGenerator;
32
use Zend\Code\Generator\PropertyGenerator;
33
34
/**
35
 * This class represents a bean.
36
 */
37
class BeanDescriptor implements BeanDescriptorInterface
38
{
39
    /**
40
     * @var Table
41
     */
42
    private $table;
43
44
    /**
45
     * @var SchemaAnalyzer
46
     */
47
    private $schemaAnalyzer;
48
49
    /**
50
     * @var Schema
51
     */
52
    private $schema;
53
54
    /**
55
     * @var AbstractBeanPropertyDescriptor[]
56
     */
57
    private $beanPropertyDescriptors = [];
58
59
    /**
60
     * @var TDBMSchemaAnalyzer
61
     */
62
    private $tdbmSchemaAnalyzer;
63
64
    /**
65
     * @var NamingStrategyInterface
66
     */
67
    private $namingStrategy;
68
    /**
69
     * @var string
70
     */
71
    private $beanNamespace;
72
    /**
73
     * @var string
74
     */
75
    private $generatedBeanNamespace;
76
    /**
77
     * @var AnnotationParser
78
     */
79
    private $annotationParser;
80
    /**
81
     * @var string
82
     */
83
    private $daoNamespace;
84
    /**
85
     * @var string
86
     */
87
    private $generatedDaoNamespace;
88
89
    public function __construct(Table $table, string $beanNamespace, string $generatedBeanNamespace, string $daoNamespace, string $generatedDaoNamespace, SchemaAnalyzer $schemaAnalyzer, Schema $schema, TDBMSchemaAnalyzer $tdbmSchemaAnalyzer, NamingStrategyInterface $namingStrategy, AnnotationParser $annotationParser)
90
    {
91
        $this->table = $table;
92
        $this->beanNamespace = $beanNamespace;
93
        $this->generatedBeanNamespace = $generatedBeanNamespace;
94
        $this->daoNamespace = $daoNamespace;
95
        $this->generatedDaoNamespace = $generatedDaoNamespace;
96
        $this->schemaAnalyzer = $schemaAnalyzer;
97
        $this->schema = $schema;
98
        $this->tdbmSchemaAnalyzer = $tdbmSchemaAnalyzer;
99
        $this->namingStrategy = $namingStrategy;
100
        $this->annotationParser = $annotationParser;
101
        $this->initBeanPropertyDescriptors();
102
    }
103
104
    private function initBeanPropertyDescriptors(): void
105
    {
106
        $this->beanPropertyDescriptors = $this->getProperties($this->table);
107
    }
108
109
    /**
110
     * Returns the foreign-key the column is part of, if any. null otherwise.
111
     *
112
     * @param Table  $table
113
     * @param Column $column
114
     *
115
     * @return ForeignKeyConstraint|null
116
     */
117
    private function isPartOfForeignKey(Table $table, Column $column) : ?ForeignKeyConstraint
118
    {
119
        $localColumnName = $column->getName();
120
        foreach ($table->getForeignKeys() as $foreignKey) {
121
            foreach ($foreignKey->getUnquotedLocalColumns() as $columnName) {
122
                if ($columnName === $localColumnName) {
123
                    return $foreignKey;
124
                }
125
            }
126
        }
127
128
        return null;
129
    }
130
131
    /**
132
     * @return AbstractBeanPropertyDescriptor[]
133
     */
134
    public function getBeanPropertyDescriptors(): array
135
    {
136
        return $this->beanPropertyDescriptors;
137
    }
138
139
    /**
140
     * Returns the list of columns that are not nullable and not autogenerated for a given table and its parent.
141
     *
142
     * @return AbstractBeanPropertyDescriptor[]
143
     */
144
    public function getConstructorProperties(): array
145
    {
146
        $constructorProperties = array_filter($this->beanPropertyDescriptors, function (AbstractBeanPropertyDescriptor $property) {
147
            return $property->isCompulsory();
148
        });
149
150
        return $constructorProperties;
151
    }
152
153
    /**
154
     * Returns the list of columns that have default values for a given table.
155
     *
156
     * @return AbstractBeanPropertyDescriptor[]
157
     */
158
    public function getPropertiesWithDefault(): array
159
    {
160
        $properties = $this->getPropertiesForTable($this->table);
161
        $defaultProperties = array_filter($properties, function (AbstractBeanPropertyDescriptor $property) {
162
            return $property->hasDefault();
163
        });
164
165
        return $defaultProperties;
166
    }
167
168
    /**
169
     * Returns the list of properties exposed as getters and setters in this class.
170
     *
171
     * @return AbstractBeanPropertyDescriptor[]
172
     */
173
    public function getExposedProperties(): array
174
    {
175
        $exposedProperties = array_filter($this->beanPropertyDescriptors, function (AbstractBeanPropertyDescriptor $property) {
176
            return $property->getTable()->getName() == $this->table->getName();
177
        });
178
179
        return $exposedProperties;
180
    }
181
182
    /**
183
     * Returns the list of properties for this table (including parent tables).
184
     *
185
     * @param Table $table
186
     *
187
     * @return AbstractBeanPropertyDescriptor[]
188
     */
189
    private function getProperties(Table $table): array
190
    {
191
        // Security check: a table MUST have a primary key
192
        TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($table);
193
194
        $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
195
        if ($parentRelationship) {
196
            $parentTable = $this->schema->getTable($parentRelationship->getForeignTableName());
197
            $properties = $this->getProperties($parentTable);
198
            // we merge properties by overriding property names.
199
            $localProperties = $this->getPropertiesForTable($table);
200
            foreach ($localProperties as $name => $property) {
201
                // We do not override properties if this is a primary key!
202
                if ($property->isPrimaryKey()) {
203
                    continue;
204
                }
205
                $properties[$name] = $property;
206
            }
207
        } else {
208
            $properties = $this->getPropertiesForTable($table);
209
        }
210
211
        return $properties;
212
    }
213
214
    /**
215
     * Returns the list of properties for this table (ignoring parent tables).
216
     *
217
     * @param Table $table
218
     *
219
     * @return AbstractBeanPropertyDescriptor[]
220
     */
221
    private function getPropertiesForTable(Table $table): array
222
    {
223
        $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
224
        if ($parentRelationship) {
225
            $ignoreColumns = $parentRelationship->getUnquotedLocalColumns();
226
        } else {
227
            $ignoreColumns = [];
228
        }
229
230
        $beanPropertyDescriptors = [];
231
        foreach ($table->getColumns() as $column) {
232
            if (array_search($column->getName(), $ignoreColumns) !== false) {
233
                continue;
234
            }
235
236
            $fk = $this->isPartOfForeignKey($table, $column);
237
            if ($fk !== null) {
238
                // Check that previously added descriptors are not added on same FK (can happen with multi key FK).
239
                foreach ($beanPropertyDescriptors as $beanDescriptor) {
240
                    if ($beanDescriptor instanceof ObjectBeanPropertyDescriptor && $beanDescriptor->getForeignKey() === $fk) {
241
                        continue 2;
242
                    }
243
                }
244
                // Check that this property is not an inheritance relationship
245
                $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
246
                if ($parentRelationship === $fk) {
247
                    continue;
248
                }
249
250
                $beanPropertyDescriptors[] = new ObjectBeanPropertyDescriptor($table, $fk, $this->namingStrategy, $this->beanNamespace);
251
            } else {
252
                $beanPropertyDescriptors[] = new ScalarBeanPropertyDescriptor($table, $column, $this->namingStrategy, $this->annotationParser);
253
            }
254
        }
255
256
        // Now, let's get the name of all properties and let's check there is no duplicate.
257
        /* @var $names AbstractBeanPropertyDescriptor[] */
258
        $names = [];
259
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
260
            $name = $beanDescriptor->getGetterName();
261
            if (isset($names[$name])) {
262
                $names[$name]->useAlternativeName();
263
                $beanDescriptor->useAlternativeName();
264
            } else {
265
                $names[$name] = $beanDescriptor;
266
            }
267
        }
268
269
        // Final check (throw exceptions if problem arises)
270
        $names = [];
271
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
272
            $name = $beanDescriptor->getGetterName();
273
            if (isset($names[$name])) {
274
                throw new TDBMException('Unsolvable name conflict while generating method name');
275
            } else {
276
                $names[$name] = $beanDescriptor;
277
            }
278
        }
279
280
        // Last step, let's rebuild the list with a map:
281
        $beanPropertyDescriptorsMap = [];
282
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
283
            $beanPropertyDescriptorsMap[$beanDescriptor->getVariableName()] = $beanDescriptor;
284
        }
285
286
        return $beanPropertyDescriptorsMap;
287
    }
288
289
    private function generateBeanConstructor() : MethodGenerator
290
    {
291
        $constructorProperties = $this->getConstructorProperties();
292
293
        $constructor = new MethodGenerator('__construct', [], MethodGenerator::FLAG_PUBLIC);
294
        $constructor->setDocBlock('The constructor takes all compulsory arguments.');
295
296
        $assigns = [];
297
        $parentConstructorArguments = [];
298
299
        foreach ($constructorProperties as $property) {
300
            $parameter = new ParameterGenerator(ltrim($property->getVariableName(), '$'));
301
            if ($property->isTypeHintable()) {
302
                $parameter->setType($property->getPhpType());
303
            }
304
            $constructor->setParameter($parameter);
305
306
            $constructor->getDocBlock()->setTag($property->getParamAnnotation());
307
308
            if ($property->getTable()->getName() === $this->table->getName()) {
309
                $assigns[] = $property->getConstructorAssignCode()."\n";
310
            } else {
311
                $parentConstructorArguments[] = $property->getVariableName();
312
            }
313
        }
314
315
        $parentConstructorCode = sprintf("parent::__construct(%s);\n", implode(', ', $parentConstructorArguments));
316
317
        foreach ($this->getPropertiesWithDefault() as $property) {
318
            $assigns[] = $property->assignToDefaultCode()."\n";
319
        }
320
321
        $body = $parentConstructorCode . implode('', $assigns);
322
323
        $constructor->setBody($body);
324
325
        return $constructor;
326
    }
327
328
    /**
329
     * Returns the descriptors of one-to-many relationships (the foreign keys pointing on this beans)
330
     *
331
     * @return DirectForeignKeyMethodDescriptor[]
332
     */
333
    private function getDirectForeignKeysDescriptors(): array
334
    {
335
        $fks = $this->tdbmSchemaAnalyzer->getIncomingForeignKeys($this->table->getName());
336
337
        $descriptors = [];
338
339
        foreach ($fks as $fk) {
340
            $descriptors[] = new DirectForeignKeyMethodDescriptor($fk, $this->table, $this->namingStrategy);
341
        }
342
343
        return $descriptors;
344
    }
345
346
    /**
347
     * @return PivotTableMethodsDescriptor[]
348
     */
349
    private function getPivotTableDescriptors(): array
350
    {
351
        $descs = [];
352
        foreach ($this->schemaAnalyzer->detectJunctionTables(true) as $table) {
353
            // There are exactly 2 FKs since this is a pivot table.
354
            $fks = array_values($table->getForeignKeys());
355
356
            if ($fks[0]->getForeignTableName() === $this->table->getName()) {
357
                list($localFk, $remoteFk) = $fks;
358
            } elseif ($fks[1]->getForeignTableName() === $this->table->getName()) {
359
                list($remoteFk, $localFk) = $fks;
360
            } else {
361
                continue;
362
            }
363
364
            $descs[] = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->beanNamespace);
365
        }
366
367
        return $descs;
368
    }
369
370
    /**
371
     * Returns the list of method descriptors (and applies the alternative name if needed).
372
     *
373
     * @return MethodDescriptorInterface[]
374
     */
375
    public function getMethodDescriptors(): array
376
    {
377
        $directForeignKeyDescriptors = $this->getDirectForeignKeysDescriptors();
378
        $pivotTableDescriptors = $this->getPivotTableDescriptors();
379
380
        $descriptors = array_merge($directForeignKeyDescriptors, $pivotTableDescriptors);
381
382
        // Descriptors by method names
383
        $descriptorsByMethodName = [];
384
385
        foreach ($descriptors as $descriptor) {
386
            $descriptorsByMethodName[$descriptor->getName()][] = $descriptor;
387
        }
388
389
        foreach ($descriptorsByMethodName as $descriptorsForMethodName) {
390
            if (count($descriptorsForMethodName) > 1) {
391
                foreach ($descriptorsForMethodName as $descriptor) {
392
                    $descriptor->useAlternativeName();
393
                }
394
            }
395
        }
396
397
        return $descriptors;
398
    }
399
400
    public function generateJsonSerialize(): MethodGenerator
401
    {
402
        $tableName = $this->table->getName();
403
        $parentFk = $this->schemaAnalyzer->getParentRelationship($tableName);
404
        if ($parentFk !== null) {
405
            $initializer = '$array = parent::jsonSerialize($stopRecursion);';
406
        } else {
407
            $initializer = '$array = [];';
408
        }
409
410
        $method = new MethodGenerator('jsonSerialize');
411
        $method->setDocBlock('Serializes the object for JSON encoding.');
412
        $method->getDocBlock()->setTag(new ParamTag('$stopRecursion', ['bool'], 'Parameter used internally by TDBM to stop embedded objects from embedding other objects.'));
413
        $method->getDocBlock()->setTag(new ReturnTag(['array']));
414
        $method->setParameter(new ParameterGenerator('stopRecursion', 'bool', false));
415
416
        $str = '%s
417
%s
418
%s
419
return $array;
420
';
421
422
        $propertiesCode = '';
423
        foreach ($this->getExposedProperties() as $beanPropertyDescriptor) {
424
            $propertiesCode .= $beanPropertyDescriptor->getJsonSerializeCode();
425
        }
426
427
        // Many2many relationships
428
        $methodsCode = '';
429
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
430
            $methodsCode .= $methodDescriptor->getJsonSerializeCode();
431
        }
432
433
        $method->setBody(sprintf($str, $initializer, $propertiesCode, $methodsCode));
434
435
        return $method;
436
    }
437
438
    /**
439
     * Returns as an array the class we need to extend from and the list of use statements.
440
     *
441
     * @param ForeignKeyConstraint|null $parentFk
442
     * @return string[]
443
     */
444
    private function generateExtendsAndUseStatements(ForeignKeyConstraint $parentFk = null): array
445
    {
446
        $classes = [];
447
        if ($parentFk !== null) {
448
            $extends = $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName());
449
            $classes[] = $extends;
450
        }
451
452
        foreach ($this->getBeanPropertyDescriptors() as $beanPropertyDescriptor) {
453
            $className = $beanPropertyDescriptor->getClassName();
454
            if (null !== $className) {
455
                $classes[] = $className;
456
            }
457
        }
458
459
        foreach ($this->getMethodDescriptors() as $descriptor) {
460
            $classes = array_merge($classes, $descriptor->getUsedClasses());
461
        }
462
463
        $classes = array_unique($classes);
464
465
        return $classes;
466
    }
467
468
    /**
469
     * Returns the representation of the PHP bean file with all getters and setters.
470
     *
471
     * @return FileGenerator
472
     */
473
    public function generatePhpCode(): FileGenerator
474
    {
475
476
        $file = new FileGenerator();
477
        $class = new ClassGenerator();
478
        $file->setClass($class);
479
        $file->setNamespace($this->generatedBeanNamespace);
480
481
        $tableName = $this->table->getName();
482
        $baseClassName = $this->namingStrategy->getBaseBeanClassName($tableName);
483
        $className = $this->namingStrategy->getBeanClassName($tableName);
484
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
485
486
        $classes = $this->generateExtendsAndUseStatements($parentFk);
487
488
        foreach ($classes as $useClass) {
489
            $file->setUse($this->beanNamespace.'\\'.$useClass);
490
        }
491
492
        /*$uses = array_map(function ($className) {
493
            return 'use '.$this->beanNamespace.'\\'.$className.";\n";
494
        }, $classes);
495
        $use = implode('', $uses);*/
496
497
        $extends = $this->getExtendedBeanClassName();
498
        if ($extends === null) {
499
            $class->setExtendedClass(AbstractTDBMObject::class);
500
            $file->setUse(AbstractTDBMObject::class);
501
        } else {
502
            $class->setExtendedClass($extends);
503
        }
504
505
        $file->setUse(ResultIterator::class);
506
        $file->setUse(AlterableResultIterator::class);
507
        $file->setUse(Uuid::class);
508
        $file->setUse(JsonSerializable::class);
509
510
        $class->setName($baseClassName);
511
        $class->setAbstract(true);
512
513
        $file->setDocBlock(new DocBlockGenerator('This file has been automatically generated by TDBM.', <<<EOF
514
DO NOT edit this file, as it might be overwritten.
515
If you need to perform changes, edit the $className class instead!
516
EOF
517
        ));
518
519
        $class->setDocBlock(new DocBlockGenerator("The $baseClassName class maps the '$tableName' table in database."));
520
        $class->setImplementedInterfaces([ JsonSerializable::class ]);
521
522
523
        $class->addMethodFromGenerator($this->generateBeanConstructor());
524
525
        foreach ($this->getExposedProperties() as $property) {
526
            foreach ($property->getGetterSetterCode() as $generator) {
527
                $class->addMethodFromGenerator($generator);
528
            }
529
        }
530
531
        foreach ($this->getMethodDescriptors() as $methodDescriptor) {
532
            foreach ($methodDescriptor->getCode() as $generator) {
533
                $class->addMethodFromGenerator($generator);
534
            }
535
        }
536
537
        $class->addMethodFromGenerator($this->generateJsonSerialize());
538
        $class->addMethodFromGenerator($this->generateGetUsedTablesCode());
539
        $onDeleteCode = $this->generateOnDeleteCode();
540
        if ($onDeleteCode) {
541
            $class->addMethodFromGenerator($onDeleteCode);
542
        }
543
        $cloneCode = $this->generateCloneCode();
544
        if ($cloneCode) {
0 ignored issues
show
introduced by
$cloneCode is of type Zend\Code\Generator\MethodGenerator, thus it always evaluated to true.
Loading history...
545
            $class->addMethodFromGenerator($cloneCode);
546
        }
547
548
        return $file;
549
    }
550
551
    /**
552
     * Writes the representation of the PHP DAO file.
553
     *
554
     * @return FileGenerator
555
     */
556
    public function generateDaoPhpCode(): FileGenerator
557
    {
558
        $file = new FileGenerator();
559
        $class = new ClassGenerator();
560
        $file->setClass($class);
561
        $file->setNamespace($this->generatedDaoNamespace);
562
563
        $tableName = $this->table->getName();
564
565
        $primaryKeyColumns = TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($this->table);
566
567
        list($defaultSort, $defaultSortDirection) = $this->getDefaultSortColumnFromAnnotation($this->table);
568
569
        $className = $this->namingStrategy->getDaoClassName($tableName);
570
        $baseClassName = $this->namingStrategy->getBaseDaoClassName($tableName);
571
        $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName);
572
        $beanClassName = $this->beanNamespace.'\\'.$beanClassWithoutNameSpace;
573
574
        $findByDaoCodeMethods = $this->generateFindByDaoCode($this->beanNamespace, $beanClassWithoutNameSpace);
575
576
        $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...
577
        // Let's suppress duplicates in used beans (if any)
578
        $usedBeans = array_flip(array_flip($usedBeans));
579
        foreach ($usedBeans as $usedBean) {
580
            $class->addUse($usedBean);
581
        }
582
583
        $file->setDocBlock(new DocBlockGenerator(<<<EOF
584
This file has been automatically generated by TDBM.
585
DO NOT edit this file, as it might be overwritten.
586
If you need to perform changes, edit the $className class instead!
587
EOF
588
        ));
589
590
        $file->setNamespace($this->generatedDaoNamespace);
591
592
        $class->addUse(TDBMService::class);
593
        $class->addUse(ResultIterator::class);
594
        $class->addUse(TDBMException::class);
595
596
        $class->setName($baseClassName);
597
598
        $class->setDocBlock(new DocBlockGenerator("The $baseClassName class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table."));
599
600
        $tdbmServiceProperty = new PropertyGenerator('tdbmService');
601
        $tdbmServiceProperty->setDocBlock(new DocBlockGenerator(null, null, [new VarTag(null, ['\\'.TDBMService::class])]));
602
        $class->addPropertyFromGenerator($tdbmServiceProperty);
603
604
        $defaultSortProperty = new PropertyGenerator('defaultSort', $defaultSort);
605
        $defaultSortProperty->setDocBlock(new DocBlockGenerator('The default sort column.', null, [new VarTag(null, ['string', 'null'])]));
606
        $class->addPropertyFromGenerator($defaultSortProperty);
607
608
        $defaultSortPropertyDirection = new PropertyGenerator('defaultDirection', $defaultSort && $defaultSortDirection ? $defaultSortDirection : 'asc');
609
        $defaultSortPropertyDirection->setDocBlock(new DocBlockGenerator('The default sort direction.', null, [new VarTag(null, ['string'])]));
610
        $class->addPropertyFromGenerator($defaultSortPropertyDirection);
611
612
        $class->addMethod(
613
            '__construct',
614
            [ new ParameterGenerator('tdbmService', TDBMService::class) ],
615
            MethodGenerator::FLAG_PUBLIC,
616
            '$this->tdbmService = $tdbmService;',
617
            'Sets the TDBM service used by this DAO.'
618
        );
619
620
        $saveMethod = new MethodGenerator(
621
            'save',
622
            [ new ParameterGenerator('obj', $beanClassName) ],
623
            MethodGenerator::FLAG_PUBLIC,
624
            '$this->tdbmService->save($obj);',
625
            new DocBlockGenerator("Persist the $beanClassWithoutNameSpace instance.",
626
                null,
627
                [
628
                    new ParamTag('obj', [$beanClassWithoutNameSpace], 'The bean to save.')
629
                ]));
630
        $saveMethod->setReturnType('void');
631
632
        $class->addMethodFromGenerator($saveMethod);
633
634
        $findAllBody = <<<EOF
635
if (\$this->defaultSort) {
636
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
637
} else {
638
    \$orderBy = null;
639
}
640
return \$this->tdbmService->findObjects('$tableName', null, [], \$orderBy);
641
EOF;
642
643
        $findAllMethod = new MethodGenerator(
644
            'findAll',
645
            [],
646
            MethodGenerator::FLAG_PUBLIC,
647
            $findAllBody,
648
            (new DocBlockGenerator("Get all $beanClassWithoutNameSpace records.",
649
                null,
650
                [
651
                    new ReturnTag([ '\\'.$beanClassName.'[]', '\\'.ResultIterator::class ])
652
                ]))->setWordWrap(false)
653
        );
654
        $findAllMethod->setReturnType('iterable');
655
        $class->addMethodFromGenerator($findAllMethod);
656
657
        if (count($primaryKeyColumns) === 1) {
658
            $primaryKeyColumn = $primaryKeyColumns[0];
659
            $primaryKeyPhpType = TDBMDaoGenerator::dbalTypeToPhpType($this->table->getColumn($primaryKeyColumn)->getType());
660
661
            $getByIdMethod = new MethodGenerator(
662
                'getById',
663
                [
664
                    new ParameterGenerator('id', $primaryKeyPhpType),
665
                    new ParameterGenerator('lazyLoading', 'bool', false)
666
                ],
667
                MethodGenerator::FLAG_PUBLIC,
668
                "return \$this->tdbmService->findObjectByPk('$tableName', ['$primaryKeyColumn' => \$id], [], \$lazyLoading);",
669
                (new DocBlockGenerator("Get $beanClassWithoutNameSpace specified by its ID (its primary key).",
670
                    'If the primary key does not exist, an exception is thrown.',
671
                    [
672
                        new ParamTag('id', [$primaryKeyPhpType]),
673
                        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.'),
674
                        new ReturnTag(['\\'.$beanClassName . '[]', '\\'.ResultIterator::class]),
675
                        new ThrowsTag('\\'.TDBMException::class)
676
                    ]))->setWordWrap(false)
677
            );
678
            $class->addMethodFromGenerator($getByIdMethod);
679
        }
680
681
        $deleteMethodBody = <<<EOF
682
if (\$cascade === true) {
683
    \$this->tdbmService->deleteCascade(\$obj);
684
} else {
685
    \$this->tdbmService->delete(\$obj);
686
}
687
EOF;
688
689
690
        $deleteMethod = new MethodGenerator(
691
            'delete',
692
            [
693
                new ParameterGenerator('obj', $beanClassName),
694
                new ParameterGenerator('cascade', 'bool', false)
695
            ],
696
            MethodGenerator::FLAG_PUBLIC,
697
            $deleteMethodBody,
698
            (new DocBlockGenerator("Get all $beanClassWithoutNameSpace records.",
699
                null,
700
                [
701
                    new ParamTag('obj', ['\\'.$beanClassName], 'The object to delete'),
702
                    new ParamTag('cascade', ['bool'], 'If true, it will delete all objects linked to $obj'),
703
                ]))->setWordWrap(false)
704
        );
705
        $deleteMethod->setReturnType('void');
706
        $class->addMethodFromGenerator($deleteMethod);
707
708
        $findMethodBody = <<<EOF
709
if (\$this->defaultSort && \$orderBy == null) {
710
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
711
}
712
return \$this->tdbmService->findObjects('$tableName', \$filter, \$parameters, \$orderBy, \$additionalTablesFetch, \$mode);
713
EOF;
714
715
716
        $findMethod = new MethodGenerator(
717
            'find',
718
            [
719
                (new ParameterGenerator('filter'))->setDefaultValue(null),
720
                new ParameterGenerator('parameters', 'array', []),
721
                (new ParameterGenerator('orderBy'))->setDefaultValue(null),
722
                new ParameterGenerator('additionalTablesFetch', 'array', []),
723
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
724
            ],
725
            MethodGenerator::FLAG_PUBLIC,
726
            $findMethodBody,
727
            (new DocBlockGenerator("Get all $beanClassWithoutNameSpace records.",
728
                null,
729
                [
730
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
731
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
732
                    new ParamTag('orderBy', ['mixed'], 'The order string'),
733
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
734
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.'),
735
                    new ReturnTag(['\\' . $beanClassName . '[]', '\\'.ResultIterator::class])
736
                ]))->setWordWrap(false)
737
        );
738
        $findMethod->setReturnType('iterable');
739
        $class->addMethodFromGenerator($findMethod);
740
741
742
        $findFromSqlMethodBody = <<<EOF
743
if (\$this->defaultSort && \$orderBy == null) {
744
    \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection;
745
}
746
return \$this->tdbmService->findObjectsFromSql('$tableName', \$from, \$filter, \$parameters, \$orderBy, \$mode);
747
EOF;
748
749
        $findFromSqlMethod = new MethodGenerator(
750
            'findFromSql',
751
            [
752
                new ParameterGenerator('from', 'string'),
753
                (new ParameterGenerator('filter'))->setDefaultValue(null),
754
                new ParameterGenerator('parameters', 'array', []),
755
                (new ParameterGenerator('orderBy'))->setDefaultValue(null),
756
                new ParameterGenerator('additionalTablesFetch', 'array', []),
757
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
758
            ],
759
            MethodGenerator::FLAG_PUBLIC,
760
            $findFromSqlMethodBody,
761
            (new DocBlockGenerator("Get a list of $beanClassWithoutNameSpace specified by its filters.",
762
                "Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part.
763
764
You should not put an alias on the main table name. So your \$from variable should look like:
765
766
   \"$tableName JOIN ... ON ...\"",
767
                [
768
                    new ParamTag('from', ['string'], 'The sql from statement'),
769
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
770
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
771
                    new ParamTag('orderBy', ['mixed'], 'The order string'),
772
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
773
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.'),
774
                    new ReturnTag(['\\'.$beanClassName . '[]', '\\'.ResultIterator::class])
775
                ]))->setWordWrap(false)
776
        );
777
        $findFromSqlMethod->setReturnType('iterable');
778
        $class->addMethodFromGenerator($findFromSqlMethod);
779
780
781
782
783
        $findFromRawSqlMethodBody = <<<EOF
784
return \$this->tdbmService->findObjectsFromRawSql('$tableName', \$sql, \$parameters, \$mode, null, \$countSql);
785
EOF;
786
787
        $findFromRawSqlMethod = new MethodGenerator(
788
            'findFromRawSql',
789
            [
790
                new ParameterGenerator('sql', 'string'),
791
                new ParameterGenerator('parameters', 'array', []),
792
                (new ParameterGenerator('countSql', '?string'))->setDefaultValue(null),
793
                (new ParameterGenerator('mode', '?int'))->setDefaultValue(null),
794
            ],
795
            MethodGenerator::FLAG_PUBLIC,
796
            $findFromRawSqlMethodBody,
797
            (new DocBlockGenerator("Get a list of $beanClassWithoutNameSpace from a SQL query.",
798
                "Unlike the `find` and `findFromSql` methods, here you can pass the whole \$sql query.
799
800
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:
801
802
   \"SELECT $tableName .* FROM ...\"",
803
                [
804
                    new ParamTag('sql', ['string'], 'The sql query'),
805
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the query'),
806
                    new ParamTag('countSql', ['string', 'null'], 'The sql query that provides total count of rows (automatically computed if not provided)'),
807
                    new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.'),
808
                    new ReturnTag(['\\'.$beanClassName . '[]', '\\'.ResultIterator::class])
809
                ]))->setWordWrap(false)
810
        );
811
        $findFromRawSqlMethod->setReturnType('iterable');
812
        $class->addMethodFromGenerator($findFromRawSqlMethod);
813
814
815
816
        $findOneMethodBody = <<<EOF
817
return \$this->tdbmService->findObject('$tableName', \$filter, \$parameters, \$additionalTablesFetch);
818
EOF;
819
820
821
        $findOneMethod = new MethodGenerator(
822
            'findOne',
823
            [
824
                (new ParameterGenerator('filter'))->setDefaultValue(null),
825
                new ParameterGenerator('parameters', 'array', []),
826
                new ParameterGenerator('additionalTablesFetch', 'array', []),
827
            ],
828
            MethodGenerator::FLAG_PUBLIC,
829
            $findOneMethodBody,
830
            (new DocBlockGenerator("Get a single $beanClassWithoutNameSpace specified by its filters.",
831
                null,
832
                [
833
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
834
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
835
                    new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'),
836
                    new ReturnTag(['\\'.$beanClassName, 'null'])
837
                ]))->setWordWrap(false)
838
        );
839
        $findOneMethod->setReturnType("?$beanClassName");
840
        $class->addMethodFromGenerator($findOneMethod);
841
842
843
        $findOneFromSqlMethodBody = <<<EOF
844
return \$this->tdbmService->findObjectFromSql('$tableName', \$from, \$filter, \$parameters);
845
EOF;
846
847
        $findOneFromSqlMethod = new MethodGenerator(
848
            'findOneFromSql',
849
            [
850
                new ParameterGenerator('from', 'string'),
851
                (new ParameterGenerator('filter'))->setDefaultValue(null),
852
                new ParameterGenerator('parameters', 'array', []),
853
            ],
854
            MethodGenerator::FLAG_PUBLIC,
855
            $findOneFromSqlMethodBody,
856
            new DocBlockGenerator("Get a single $beanClassWithoutNameSpace specified by its filters.",
857
                "Unlike the `findOne` method that guesses the FROM part of the statement, here you can pass the \$from part.
858
859
You should not put an alias on the main table name. So your \$from variable should look like:
860
861
    \"$tableName JOIN ... ON ...\"",
862
                [
863
                    new ParamTag('from', ['string'], 'The sql from statement'),
864
                    new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'),
865
                    new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'),
866
                    new ReturnTag(['\\'.$beanClassName, 'null'])
867
                ])
868
        );
869
        $findOneFromSqlMethod->setReturnType("?$beanClassName");
870
        $class->addMethodFromGenerator($findOneFromSqlMethod);
871
872
873
        $setDefaultSortMethod = new MethodGenerator(
874
            'setDefaultSort',
875
            [
876
                new ParameterGenerator('defaultSort', 'string'),
877
            ],
878
            MethodGenerator::FLAG_PUBLIC,
879
            '$this->defaultSort = $defaultSort;',
880
            new DocBlockGenerator("Sets the default column for default sorting.",
881
            null,
882
                [
883
                    new ParamTag('defaultSort', ['string']),
884
                ])
885
        );
886
        $setDefaultSortMethod->setReturnType('void');
887
        $class->addMethodFromGenerator($setDefaultSortMethod);
888
889
        foreach ($findByDaoCodeMethods as $method) {
890
            $class->addMethodFromGenerator($method);
891
        }
892
893
        return $file;
894
    }
895
896
    /**
897
     * Tries to find a @defaultSort annotation in one of the columns.
898
     *
899
     * @param Table $table
900
     *
901
     * @return mixed[] First item: column name, Second item: column order (asc/desc)
902
     */
903
    private function getDefaultSortColumnFromAnnotation(Table $table): array
904
    {
905
        $defaultSort = null;
906
        $defaultSortDirection = null;
907
        foreach ($table->getColumns() as $column) {
908
            $comments = $column->getComment();
909
            $matches = [];
910
            if ($comments !== null && preg_match('/@defaultSort(\((desc|asc)\))*/', $comments, $matches) != 0) {
911
                $defaultSort = $column->getName();
912
                if (count($matches) === 3) {
913
                    $defaultSortDirection = $matches[2];
914
                } else {
915
                    $defaultSortDirection = 'ASC';
916
                }
917
            }
918
        }
919
920
        return [$defaultSort, $defaultSortDirection];
921
    }
922
923
    /**
924
     * @param string $beanNamespace
925
     * @param string $beanClassName
926
     *
927
     * @return MethodGenerator[]
928
     */
929
    private function generateFindByDaoCode(string $beanNamespace, string $beanClassName): array
930
    {
931
        $methods = [];
932
        foreach ($this->removeDuplicateIndexes($this->table->getIndexes()) as $index) {
933
            if (!$index->isPrimary()) {
934
                $method = $this->generateFindByDaoCodeForIndex($index, $beanNamespace, $beanClassName);
935
                if ($method !== null) {
936
                    $methods[] = $method;
937
                }
938
            }
939
        }
940
941
        return $methods;
942
    }
943
944
    /**
945
     * Remove identical indexes (indexes on same columns)
946
     *
947
     * @param Index[] $indexes
948
     * @return Index[]
949
     */
950
    private function removeDuplicateIndexes(array $indexes): array
951
    {
952
        $indexesByKey = [];
953
        foreach ($indexes as $index) {
954
            $indexesByKey[implode('__`__', $index->getUnquotedColumns())] = $index;
955
        }
956
957
        return array_values($indexesByKey);
958
    }
959
960
    /**
961
     * @param Index  $index
962
     * @param string $beanNamespace
963
     * @param string $beanClassName
964
     *
965
     * @return MethodGenerator|null
966
     */
967
    private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespace, string $beanClassName): ?MethodGenerator
968
    {
969
        $columns = $index->getColumns();
970
        $usedBeans = [];
971
972
        /**
973
         * The list of elements building this index (expressed as columns or foreign keys)
974
         * @var AbstractBeanPropertyDescriptor[]
975
         */
976
        $elements = [];
977
978
        foreach ($columns as $column) {
979
            $fk = $this->isPartOfForeignKey($this->table, $this->table->getColumn($column));
980
            if ($fk !== null) {
981
                if (!in_array($fk, $elements)) {
982
                    $elements[] = new ObjectBeanPropertyDescriptor($this->table, $fk, $this->namingStrategy, $this->beanNamespace);
983
                }
984
            } else {
985
                $elements[] = new ScalarBeanPropertyDescriptor($this->table, $this->table->getColumn($column), $this->namingStrategy, $this->annotationParser);
986
            }
987
        }
988
989
        // If the index is actually only a foreign key, let's bypass it entirely.
990
        if (count($elements) === 1 && $elements[0] instanceof ObjectBeanPropertyDescriptor) {
991
            return null;
992
        }
993
994
        $parameters = [];
995
        //$functionParameters = [];
996
        $first = true;
997
        foreach ($elements as $element) {
998
            $parameter = new ParameterGenerator(ltrim($element->getVariableName(), '$'));
999
            if (!$first) {
1000
                $parameterType = '?';
1001
                //$functionParameter = '?';
1002
            } else {
1003
                $parameterType = '';
1004
                //$functionParameter = '';
1005
            }
1006
            $parameterType .= $element->getPhpType();
1007
            $parameter->setType($parameterType);
1008
            if (!$first) {
1009
                $parameter->setDefaultValue(null);
1010
            }
1011
            //$functionParameter .= $element->getPhpType();
1012
            $elementClassName = $element->getClassName();
1013
            if ($elementClassName) {
1014
                $usedBeans[] = $beanNamespace.'\\'.$elementClassName;
1015
            }
1016
            //$functionParameter .= ' '.$element->getVariableName();
1017
            if ($first) {
1018
                $first = false;
1019
            } /*else {
1020
                $functionParameter .= ' = null';
1021
            }*/
1022
            //$functionParameters[] = $functionParameter;
1023
            $parameters[] = $parameter;
1024
        }
1025
1026
        //$functionParametersString = implode(', ', $functionParameters);
1027
1028
        $count = 0;
1029
1030
        $params = [];
1031
        $filterArrayCode = '';
1032
        $commentArguments = [];
1033
        $first = true;
1034
        foreach ($elements as $element) {
1035
            $params[] = $element->getParamAnnotation();
1036
            if ($element instanceof ScalarBeanPropertyDescriptor) {
1037
                $filterArrayCode .= '            '.var_export($element->getColumnName(), true).' => '.$element->getVariableName().",\n";
1038
            } elseif ($element instanceof ObjectBeanPropertyDescriptor) {
1039
                $foreignKey = $element->getForeignKey();
1040
                $columns = SafeFunctions::arrayCombine($foreignKey->getLocalColumns(), $foreignKey->getForeignColumns());
1041
                ++$count;
1042
                $foreignTable = $this->schema->getTable($foreignKey->getForeignTableName());
1043
                foreach ($columns as $localColumn => $foreignColumn) {
1044
                    // 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.
1045
                    $targetedElement = new ScalarBeanPropertyDescriptor($foreignTable, $foreignTable->getColumn($foreignColumn), $this->namingStrategy, $this->annotationParser);
1046
                    if ($first) {
1047
                        // First parameter for index is not nullable
1048
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => '.$element->getVariableName().'->'.$targetedElement->getGetterName()."(),\n";
1049
                    } else {
1050
                        // Other parameters for index is not nullable
1051
                        $filterArrayCode .= '            '.var_export($localColumn, true).' => ('.$element->getVariableName().' !== null) ? '.$element->getVariableName().'->'.$targetedElement->getGetterName()."() : null,\n";
1052
                    }
1053
                }
1054
            }
1055
            $commentArguments[] = substr($element->getVariableName(), 1);
1056
            if ($first) {
1057
                $first = false;
1058
            }
1059
        }
1060
1061
        //$paramsString = implode("\n", $params);
1062
1063
1064
        $methodName = $this->namingStrategy->getFindByIndexMethodName($index, $elements);
1065
1066
        $method = new MethodGenerator($methodName);
1067
1068
        if ($index->isUnique()) {
1069
            $parameters[] = new ParameterGenerator('additionalTablesFetch', 'array', []);
1070
            $params[] = new ParamTag('additionalTablesFetch', [ 'string[]' ], 'A list of additional tables to fetch (for performance improvement)');
1071
            $params[] = new ReturnTag([ '\\'.$beanNamespace.'\\'.$beanClassName, 'null' ]);
1072
            $method->setReturnType('?\\'.$beanNamespace.'\\'.$beanClassName);
1073
1074
            $docBlock = new DocBlockGenerator("Get a $beanClassName filtered by ".implode(', ', $commentArguments). '.', null, $params);
1075
            $docBlock->setWordWrap(false);
1076
1077
            $body = "\$filter = [
1078
".$filterArrayCode."        ];
1079
return \$this->findOne(\$filter, [], \$additionalTablesFetch);
1080
";
1081
        } else {
1082
            $parameters[] = (new ParameterGenerator('orderBy'))->setDefaultValue(null);
1083
            $params[] = new ParamTag('orderBy', [ 'mixed' ], 'The order string');
1084
            $parameters[] = new ParameterGenerator('additionalTablesFetch', 'array', []);
1085
            $params[] = new ParamTag('additionalTablesFetch', [ 'string[]' ], 'A list of additional tables to fetch (for performance improvement)');
1086
            $parameters[] = (new ParameterGenerator('mode', '?int'))->setDefaultValue(null);
1087
            $params[] = new ParamTag('mode', [ 'int', 'null' ], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.');
1088
            $params[] = new ReturnTag([ '\\'.$beanNamespace.'\\'.$beanClassName.'[]', '\\'.ResultIterator::class ]);
1089
            $method->setReturnType('iterable');
1090
1091
            $docBlock = new DocBlockGenerator("Get a list of $beanClassName filtered by ".implode(', ', $commentArguments).".", null, $params);
1092
            $docBlock->setWordWrap(false);
1093
1094
            $body = "\$filter = [
1095
".$filterArrayCode."        ];
1096
return \$this->find(\$filter, [], \$orderBy, \$additionalTablesFetch, \$mode);
1097
";
1098
        }
1099
1100
        $method->setParameters($parameters);
1101
        $method->setDocBlock($docBlock);
1102
        $method->setBody($body);
1103
1104
        return $method;
1105
    }
1106
1107
    /**
1108
     * Generates the code for the getUsedTable protected method.
1109
     *
1110
     * @return MethodGenerator
1111
     */
1112
    private function generateGetUsedTablesCode(): MethodGenerator
1113
    {
1114
        $hasParentRelationship = $this->schemaAnalyzer->getParentRelationship($this->table->getName()) !== null;
1115
        if ($hasParentRelationship) {
1116
            $code = sprintf('$tables = parent::getUsedTables();
1117
$tables[] = %s;
1118
1119
return $tables;', var_export($this->table->getName(), true));
1120
        } else {
1121
            $code = sprintf('        return [ %s ];', var_export($this->table->getName(), true));
1122
        }
1123
1124
        $method = new MethodGenerator('getUsedTables');
1125
        $method->setDocBlock('Returns an array of used tables by this bean (from parent to child relationship).');
1126
        $method->getDocBlock()->setTag(new ReturnTag(['string[]']));
1127
        $method->setReturnType('array');
1128
        $method->setBody($code);
1129
1130
        return $method;
1131
    }
1132
1133
    private function generateOnDeleteCode(): ?MethodGenerator
1134
    {
1135
        $code = '';
1136
        $relationships = $this->getPropertiesForTable($this->table);
1137
        foreach ($relationships as $relationship) {
1138
            if ($relationship instanceof ObjectBeanPropertyDescriptor) {
1139
                $code .= sprintf('$this->setRef('.var_export($relationship->getForeignKey()->getName(), true).', null, '.var_export($this->table->getName(), true).");\n");
1140
            }
1141
        }
1142
1143
        if (!$code) {
1144
            return null;
1145
        }
1146
1147
        $method = new MethodGenerator('onDelete');
1148
        $method->setDocBlock('Method called when the bean is removed from database.');
1149
        $method->setReturnType('void');
1150
        $method->setBody('parent::onDelete();
1151
'.$code);
1152
1153
        return $method;
1154
    }
1155
1156
    private function generateCloneCode(): ?MethodGenerator
1157
    {
1158
        $code = '';
1159
1160
        foreach ($this->beanPropertyDescriptors as $beanPropertyDescriptor) {
1161
            $code .= $beanPropertyDescriptor->getCloneRule();
1162
        }
1163
1164
        $method = new MethodGenerator('__clone');
1165
        $method->setBody('parent::__clone();
1166
'.$code);
1167
1168
        return $method;
1169
    }
1170
1171
    /**
1172
     * Returns the bean class name (without the namespace).
1173
     *
1174
     * @return string
1175
     */
1176
    public function getBeanClassName() : string
1177
    {
1178
        return $this->namingStrategy->getBeanClassName($this->table->getName());
1179
    }
1180
1181
    /**
1182
     * Returns the base bean class name (without the namespace).
1183
     *
1184
     * @return string
1185
     */
1186
    public function getBaseBeanClassName() : string
1187
    {
1188
        return $this->namingStrategy->getBaseBeanClassName($this->table->getName());
1189
    }
1190
1191
    /**
1192
     * Returns the DAO class name (without the namespace).
1193
     *
1194
     * @return string
1195
     */
1196
    public function getDaoClassName() : string
1197
    {
1198
        return $this->namingStrategy->getDaoClassName($this->table->getName());
1199
    }
1200
1201
    /**
1202
     * Returns the base DAO class name (without the namespace).
1203
     *
1204
     * @return string
1205
     */
1206
    public function getBaseDaoClassName() : string
1207
    {
1208
        return $this->namingStrategy->getBaseDaoClassName($this->table->getName());
1209
    }
1210
1211
    /**
1212
     * Returns the table used to build this bean.
1213
     *
1214
     * @return Table
1215
     */
1216
    public function getTable(): Table
1217
    {
1218
        return $this->table;
1219
    }
1220
1221
    /**
1222
     * Returns the extended bean class name (without the namespace), or null if the bean is not extended.
1223
     *
1224
     * @return string
1225
     */
1226
    public function getExtendedBeanClassName(): ?string
1227
    {
1228
        $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName());
1229
        if ($parentFk !== null) {
1230
            return $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName());
1231
        } else {
1232
            return null;
1233
        }
1234
    }
1235
1236
    /**
1237
     * @return string
1238
     */
1239
    public function getBeanNamespace(): string
1240
    {
1241
        return $this->beanNamespace;
1242
    }
1243
1244
    /**
1245
     * @return string
1246
     */
1247
    public function getGeneratedBeanNamespace(): string
1248
    {
1249
        return $this->generatedBeanNamespace;
1250
    }
1251
}
1252