Passed
Pull Request — master (#116)
by David
02:50
created

BeanDescriptor::getPivotTableDescriptors()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 19
rs 9.9
c 0
b 0
f 0
cc 4
nc 4
nop 0
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