Completed
Push — 4.2 ( 9b8943...37bf92 )
by David
56s
created

BeanDescriptor::generateOnDeleteCode()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 26
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 10
nc 6
nop 0
1
<?php
2
3
namespace Mouf\Database\TDBM\Utils;
4
5
use Doctrine\DBAL\Schema\Column;
6
use Doctrine\DBAL\Schema\Index;
7
use Doctrine\DBAL\Schema\Schema;
8
use Doctrine\DBAL\Schema\Table;
9
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
10
use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer;
11
use Mouf\Database\TDBM\AlterableResultIterator;
12
use Mouf\Database\TDBM\TDBMException;
13
use Mouf\Database\TDBM\TDBMSchemaAnalyzer;
14
15
/**
16
 * This class represents a bean.
17
 */
18
class BeanDescriptor
19
{
20
    /**
21
     * @var Table
22
     */
23
    private $table;
24
25
    /**
26
     * @var SchemaAnalyzer
27
     */
28
    private $schemaAnalyzer;
29
30
    /**
31
     * @var Schema
32
     */
33
    private $schema;
34
35
    /**
36
     * @var AbstractBeanPropertyDescriptor[]
37
     */
38
    private $beanPropertyDescriptors = [];
39
40
    /**
41
     * @var TDBMSchemaAnalyzer
42
     */
43
    private $tdbmSchemaAnalyzer;
44
45
    public function __construct(Table $table, SchemaAnalyzer $schemaAnalyzer, Schema $schema, TDBMSchemaAnalyzer $tdbmSchemaAnalyzer)
46
    {
47
        $this->table = $table;
48
        $this->schemaAnalyzer = $schemaAnalyzer;
49
        $this->schema = $schema;
50
        $this->tdbmSchemaAnalyzer = $tdbmSchemaAnalyzer;
51
        $this->initBeanPropertyDescriptors();
52
    }
53
54
    private function initBeanPropertyDescriptors()
55
    {
56
        $this->beanPropertyDescriptors = $this->getProperties($this->table);
57
    }
58
59
    /**
60
     * Returns the foreign-key the column is part of, if any. null otherwise.
61
     *
62
     * @param Table  $table
63
     * @param Column $column
64
     *
65
     * @return ForeignKeyConstraint|null
66
     */
67
    private function isPartOfForeignKey(Table $table, Column $column)
68
    {
69
        $localColumnName = $column->getName();
70
        foreach ($table->getForeignKeys() as $foreignKey) {
71
            foreach ($foreignKey->getColumns() as $columnName) {
72
                if ($columnName === $localColumnName) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $columnName (integer) and $localColumnName (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
73
                    return $foreignKey;
74
                }
75
            }
76
        }
77
78
        return;
79
    }
80
81
    /**
82
     * @return AbstractBeanPropertyDescriptor[]
83
     */
84
    public function getBeanPropertyDescriptors()
85
    {
86
        return $this->beanPropertyDescriptors;
87
    }
88
89
    /**
90
     * Returns the list of columns that are not nullable and not autogenerated for a given table and its parent.
91
     *
92
     * @return AbstractBeanPropertyDescriptor[]
93
     */
94
    public function getConstructorProperties()
95
    {
96
        $constructorProperties = array_filter($this->beanPropertyDescriptors, function (AbstractBeanPropertyDescriptor $property) {
97
            return $property->isCompulsory();
98
        });
99
100
        return $constructorProperties;
101
    }
102
103
    /**
104
     * Returns the list of columns that have default values for a given table.
105
     *
106
     * @return AbstractBeanPropertyDescriptor[]
107
     */
108
    public function getPropertiesWithDefault()
109
    {
110
        $properties = $this->getPropertiesForTable($this->table);
111
        $defaultProperties = array_filter($properties, function (AbstractBeanPropertyDescriptor $property) {
112
            return $property->hasDefault();
113
        });
114
115
        return $defaultProperties;
116
    }
117
118
    /**
119
     * Returns the list of properties exposed as getters and setters in this class.
120
     *
121
     * @return AbstractBeanPropertyDescriptor[]
122
     */
123
    public function getExposedProperties()
124
    {
125
        $exposedProperties = array_filter($this->beanPropertyDescriptors, function (AbstractBeanPropertyDescriptor $property) {
126
            return $property->getTable()->getName() == $this->table->getName();
127
        });
128
129
        return $exposedProperties;
130
    }
131
132
    /**
133
     * Returns the list of properties for this table (including parent tables).
134
     *
135
     * @param Table $table
136
     *
137
     * @return AbstractBeanPropertyDescriptor[]
138
     */
139
    private function getProperties(Table $table)
140
    {
141
        $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
142
        if ($parentRelationship) {
143
            $parentTable = $this->schema->getTable($parentRelationship->getForeignTableName());
144
            $properties = $this->getProperties($parentTable);
145
            // we merge properties by overriding property names.
146
            $localProperties = $this->getPropertiesForTable($table);
147
            foreach ($localProperties as $name => $property) {
148
                // We do not override properties if this is a primary key!
149
                if ($property->isPrimaryKey()) {
150
                    continue;
151
                }
152
                $properties[$name] = $property;
153
            }
154
        } else {
155
            $properties = $this->getPropertiesForTable($table);
156
        }
157
158
        return $properties;
159
    }
160
161
    /**
162
     * Returns the list of properties for this table (ignoring parent tables).
163
     *
164
     * @param Table $table
165
     *
166
     * @return AbstractBeanPropertyDescriptor[]
167
     */
168
    private function getPropertiesForTable(Table $table)
169
    {
170
        $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
171
        if ($parentRelationship) {
172
            $ignoreColumns = $parentRelationship->getLocalColumns();
173
        } else {
174
            $ignoreColumns = [];
175
        }
176
177
        $beanPropertyDescriptors = [];
178
179
        foreach ($table->getColumns() as $column) {
180
            if (array_search($column->getName(), $ignoreColumns) !== false) {
181
                continue;
182
            }
183
184
            $fk = $this->isPartOfForeignKey($table, $column);
185
            if ($fk !== null) {
186
                // Check that previously added descriptors are not added on same FK (can happen with multi key FK).
187
                foreach ($beanPropertyDescriptors as $beanDescriptor) {
188
                    if ($beanDescriptor instanceof ObjectBeanPropertyDescriptor && $beanDescriptor->getForeignKey() === $fk) {
189
                        continue 2;
190
                    }
191
                }
192
                // Check that this property is not an inheritance relationship
193
                $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName());
194
                if ($parentRelationship === $fk) {
195
                    continue;
196
                }
197
198
                $beanPropertyDescriptors[] = new ObjectBeanPropertyDescriptor($table, $fk, $this->schemaAnalyzer);
199
            } else {
200
                $beanPropertyDescriptors[] = new ScalarBeanPropertyDescriptor($table, $column);
201
            }
202
        }
203
204
        // Now, let's get the name of all properties and let's check there is no duplicate.
205
        /** @var $names AbstractBeanPropertyDescriptor[] */
206
        $names = [];
207
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
208
            $name = $beanDescriptor->getUpperCamelCaseName();
209
            if (isset($names[$name])) {
210
                $names[$name]->useAlternativeName();
211
                $beanDescriptor->useAlternativeName();
212
            } else {
213
                $names[$name] = $beanDescriptor;
214
            }
215
        }
216
217
        // Final check (throw exceptions if problem arises)
218
        $names = [];
219
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
220
            $name = $beanDescriptor->getUpperCamelCaseName();
221
            if (isset($names[$name])) {
222
                throw new TDBMException('Unsolvable name conflict while generating method name');
223
            } else {
224
                $names[$name] = $beanDescriptor;
225
            }
226
        }
227
228
        // Last step, let's rebuild the list with a map:
229
        $beanPropertyDescriptorsMap = [];
230
        foreach ($beanPropertyDescriptors as $beanDescriptor) {
231
            $beanPropertyDescriptorsMap[$beanDescriptor->getLowerCamelCaseName()] = $beanDescriptor;
232
        }
233
234
        return $beanPropertyDescriptorsMap;
235
    }
236
237
    public function generateBeanConstructor()
238
    {
239
        $constructorProperties = $this->getConstructorProperties();
240
241
        $constructorCode = '    /**
242
     * The constructor takes all compulsory arguments.
243
     *
244
%s
245
     */
246
    public function __construct(%s)
247
    {
248
%s%s
249
    }
250
    ';
251
252
        $paramAnnotations = [];
253
        $arguments = [];
254
        $assigns = [];
255
        $parentConstructorArguments = [];
256
257
        foreach ($constructorProperties as $property) {
258
            $className = $property->getClassName();
259
            if ($className) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $className of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
260
                $arguments[] = $className.' '.$property->getVariableName();
261
            } else {
262
                $arguments[] = $property->getVariableName();
263
            }
264
            $paramAnnotations[] = $property->getParamAnnotation();
265
            if ($property->getTable()->getName() === $this->table->getName()) {
266
                $assigns[] = $property->getConstructorAssignCode();
267
            } else {
268
                $parentConstructorArguments[] = $property->getVariableName();
269
            }
270
        }
271
272
        $parentConstructorCode = sprintf("        parent::__construct(%s);\n", implode(', ', $parentConstructorArguments));
273
274
        foreach ($this->getPropertiesWithDefault() as $property) {
275
            $assigns[] = $property->assignToDefaultCode();
276
        }
277
278
        return sprintf($constructorCode, implode("\n", $paramAnnotations), implode(', ', $arguments), $parentConstructorCode, implode("\n", $assigns));
279
    }
280
281
    public function generateDirectForeignKeysCode()
282
    {
283
        $fks = $this->tdbmSchemaAnalyzer->getIncomingForeignKeys($this->table->getName());
284
285
        $fksByTable = [];
286
287
        foreach ($fks as $fk) {
288
            $fksByTable[$fk->getLocalTableName()][] = $fk;
289
        }
290
291
        /* @var $fksByMethodName ForeignKeyConstraint[] */
292
        $fksByMethodName = [];
293
294
        foreach ($fksByTable as $tableName => $fksForTable) {
295
            if (count($fksForTable) > 1) {
296
                foreach ($fksForTable as $fk) {
297
                    $methodName = 'get'.TDBMDaoGenerator::toCamelCase($fk->getLocalTableName()).'By';
298
299
                    $camelizedColumns = array_map(['Mouf\\Database\\TDBM\\Utils\\TDBMDaoGenerator', 'toCamelCase'], $fk->getLocalColumns());
300
301
                    $methodName .= implode('And', $camelizedColumns);
302
303
                    $fksByMethodName[$methodName] = $fk;
304
                }
305
            } else {
306
                $methodName = 'get'.TDBMDaoGenerator::toCamelCase($fksForTable[0]->getLocalTableName());
307
                $fksByMethodName[$methodName] = $fksForTable[0];
308
            }
309
        }
310
311
        $code = '';
312
313
        foreach ($fksByMethodName as $methodName => $fk) {
314
            $getterCode = '    /**
315
     * Returns the list of %s pointing to this bean via the %s column.
316
     *
317
     * @return %s[]|AlterableResultIterator
318
     */
319
    public function %s() : AlterableResultIterator
320
    {
321
        return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s, %s);
322
    }
323
324
';
325
326
            $beanClass = TDBMDaoGenerator::getBeanNameFromTableName($fk->getLocalTableName());
327
            $code .= sprintf($getterCode,
328
                $beanClass,
329
                implode(', ', $fk->getColumns()),
330
                $beanClass,
331
                $methodName,
332
                var_export($fk->getLocalTableName(), true),
333
                var_export($fk->getName(), true),
334
                var_export($fk->getLocalTableName(), true),
335
                $this->getFilters($fk)
336
            );
337
        }
338
339
        return $code;
340
    }
341
342
    private function getFilters(ForeignKeyConstraint $fk) : string
343
    {
344
        $counter = 0;
345
        $parameters = [];
346
347
        $pkColumns = $this->table->getPrimaryKeyColumns();
348
349
        foreach ($fk->getLocalColumns() as $columnName) {
350
            $pkColumn = $pkColumns[$counter];
351
            $parameters[] = sprintf('%s => $this->get(%s, %s)', var_export($fk->getLocalTableName().'.'.$columnName, true), var_export($pkColumn, true), var_export($this->table->getName(), true));
352
            ++$counter;
353
        }
354
        $parametersCode = '['.implode(', ', $parameters).']';
355
356
        return $parametersCode;
357
    }
358
359
    /**
360
     * Generate code section about pivot tables.
361
     *
362
     * @return string
363
     */
364
    public function generatePivotTableCode()
365
    {
366
        $finalDescs = $this->getPivotTableDescriptors();
367
368
        $code = '';
369
370
        foreach ($finalDescs as $desc) {
371
            $code .= $this->getPivotTableCode($desc['name'], $desc['table'], $desc['localFK'], $desc['remoteFK']);
372
        }
373
374
        return $code;
375
    }
376
377
    private function getPivotTableDescriptors()
378
    {
379
        $descs = [];
380
        foreach ($this->schemaAnalyzer->detectJunctionTables(true) as $table) {
381
            // There are exactly 2 FKs since this is a pivot table.
382
            $fks = array_values($table->getForeignKeys());
383
384
            if ($fks[0]->getForeignTableName() === $this->table->getName()) {
385
                $localFK = $fks[0];
386
                $remoteFK = $fks[1];
387
            } elseif ($fks[1]->getForeignTableName() === $this->table->getName()) {
388
                $localFK = $fks[1];
389
                $remoteFK = $fks[0];
390
            } else {
391
                continue;
392
            }
393
394
            $descs[$remoteFK->getForeignTableName()][] = [
395
                'table' => $table,
396
                'localFK' => $localFK,
397
                'remoteFK' => $remoteFK,
398
            ];
399
        }
400
401
        $finalDescs = [];
402
        foreach ($descs as $descArray) {
403
            if (count($descArray) > 1) {
404
                foreach ($descArray as $desc) {
405
                    $desc['name'] = TDBMDaoGenerator::toCamelCase($desc['remoteFK']->getForeignTableName()).'By'.TDBMDaoGenerator::toCamelCase($desc['table']->getName());
406
                    $finalDescs[] = $desc;
407
                }
408
            } else {
409
                $desc = $descArray[0];
410
                $desc['name'] = TDBMDaoGenerator::toCamelCase($desc['remoteFK']->getForeignTableName());
411
                $finalDescs[] = $desc;
412
            }
413
        }
414
415
        return $finalDescs;
416
    }
417
418
    public function getPivotTableCode($name, Table $table, ForeignKeyConstraint $localFK, ForeignKeyConstraint $remoteFK)
0 ignored issues
show
Unused Code introduced by
The parameter $localFK is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
419
    {
420
        $singularName = TDBMDaoGenerator::toSingular($name);
421
        $pluralName = $name;
422
        $remoteBeanName = TDBMDaoGenerator::getBeanNameFromTableName($remoteFK->getForeignTableName());
423
        $variableName = '$'.TDBMDaoGenerator::toVariableName($remoteBeanName);
424
        $pluralVariableName = $variableName.'s';
425
426
        $str = '    /**
427
     * Returns the list of %s associated to this bean via the %s pivot table.
428
     *
429
     * @return %s[]
430
     */
431
    public function get%s()
432
    {
433
        return $this->_getRelationships(%s);
434
    }
435
';
436
437
        $getterCode = sprintf($str, $remoteBeanName, $table->getName(), $remoteBeanName, $name, var_export($remoteFK->getLocalTableName(), true));
438
439
        $str = '    /**
440
     * Adds a relationship with %s associated to this bean via the %s pivot table.
441
     *
442
     * @param %s %s
443
     */
444
    public function add%s(%s %s)
445
    {
446
        return $this->addRelationship(%s, %s);
447
    }
448
';
449
450
        $adderCode = sprintf($str, $remoteBeanName, $table->getName(), $remoteBeanName, $variableName, $singularName, $remoteBeanName, $variableName, var_export($remoteFK->getLocalTableName(), true), $variableName);
451
452
        $str = '    /**
453
     * Deletes the relationship with %s associated to this bean via the %s pivot table.
454
     *
455
     * @param %s %s
456
     */
457
    public function remove%s(%s %s)
458
    {
459
        return $this->_removeRelationship(%s, %s);
460
    }
461
';
462
463
        $removerCode = sprintf($str, $remoteBeanName, $table->getName(), $remoteBeanName, $variableName, $singularName, $remoteBeanName, $variableName, var_export($remoteFK->getLocalTableName(), true), $variableName);
464
465
        $str = '    /**
466
     * Returns whether this bean is associated with %s via the %s pivot table.
467
     *
468
     * @param %s %s
469
     * @return bool
470
     */
471
    public function has%s(%s %s) : bool
472
    {
473
        return $this->hasRelationship(%s, %s);
474
    }
475
';
476
477
        $hasCode = sprintf($str, $remoteBeanName, $table->getName(), $remoteBeanName, $variableName, $singularName, $remoteBeanName, $variableName, var_export($remoteFK->getLocalTableName(), true), $variableName);
478
479
        $str = '    /**
480
     * Sets all relationships with %s associated to this bean via the %s pivot table.
481
     * Exiting relationships will be removed and replaced by the provided relationships.
482
     *
483
     * @param %s[] %s
484
     */
485
    public function set%s(array %s)
486
    {
487
        return $this->setRelationships(%s, %s);
488
    }
489
';
490
491
        $setterCode = sprintf($str, $remoteBeanName, $table->getName(), $remoteBeanName, $pluralVariableName, $pluralName, $pluralVariableName, var_export($remoteFK->getLocalTableName(), true), $pluralVariableName);
492
493
        $code = $getterCode.$adderCode.$removerCode.$hasCode.$setterCode;
494
495
        return $code;
496
    }
497
498
    public function generateJsonSerialize()
499
    {
500
        $tableName = $this->table->getName();
501
        $parentFk = $this->schemaAnalyzer->getParentRelationship($tableName);
502
        if ($parentFk !== null) {
503
            $initializer = '$array = parent::jsonSerialize($stopRecursion);';
504
        } else {
505
            $initializer = '$array = [];';
506
        }
507
508
        $str = '
509
    /**
510
     * Serializes the object for JSON encoding.
511
     *
512
     * @param bool $stopRecursion Parameter used internally by TDBM to stop embedded objects from embedding other objects.
513
     * @return array
514
     */
515
    public function jsonSerialize($stopRecursion = false)
516
    {
517
        %s
518
%s
519
%s
520
        return $array;
521
    }
522
';
523
524
        $propertiesCode = '';
525
        foreach ($this->beanPropertyDescriptors as $beanPropertyDescriptor) {
526
            $propertiesCode .= $beanPropertyDescriptor->getJsonSerializeCode();
527
        }
528
529
        // Many to many relationships:
530
531
        $descs = $this->getPivotTableDescriptors();
532
533
        $many2manyCode = '';
534
535
        foreach ($descs as $desc) {
536
            $remoteFK = $desc['remoteFK'];
537
            $remoteBeanName = TDBMDaoGenerator::getBeanNameFromTableName($remoteFK->getForeignTableName());
538
            $variableName = '$'.TDBMDaoGenerator::toVariableName($remoteBeanName);
539
540
            $many2manyCode .= '        if (!$stopRecursion) {
541
            $array[\''.lcfirst($desc['name']).'\'] = array_map(function ('.$remoteBeanName.' '.$variableName.') {
542
                return '.$variableName.'->jsonSerialize(true);
543
            }, $this->get'.$desc['name'].'());
544
        }
545
';
546
        }
547
548
        return sprintf($str, $initializer, $propertiesCode, $many2manyCode);
549
    }
550
551
    /**
552
     * Returns as an array the class we need to extend from and the list of use statements.
553
     *
554
     * @return array
555
     */
556
    private function generateExtendsAndUseStatements(ForeignKeyConstraint $parentFk = null)
557
    {
558
        $classes = [];
559
        if ($parentFk !== null) {
560
            $extends = TDBMDaoGenerator::getBeanNameFromTableName($parentFk->getForeignTableName());
561
            $classes[] = $extends;
562
        }
563
564
        foreach ($this->getBeanPropertyDescriptors() as $beanPropertyDescriptor) {
565
            $className = $beanPropertyDescriptor->getClassName();
566
            if (null !== $className) {
567
                $classes[] = $beanPropertyDescriptor->getClassName();
568
            }
569
        }
570
571
        foreach ($this->getPivotTableDescriptors() as $descriptor) {
572
            /* @var $fk ForeignKeyConstraint */
573
            $fk = $descriptor['remoteFK'];
574
            $classes[] = TDBMDaoGenerator::getBeanNameFromTableName($fk->getForeignTableName());
575
        }
576
577
        // Many-to-one relationships
578
        $fks = $this->tdbmSchemaAnalyzer->getIncomingForeignKeys($this->table->getName());
579
        foreach ($fks as $fk) {
580
            $classes[] = TDBMDaoGenerator::getBeanNameFromTableName($fk->getLocalTableName());
581
        }
582
583
        $classes = array_unique($classes);
584
585
        return $classes;
586
    }
587
588
    /**
589
     * Writes the PHP bean file with all getters and setters from the table passed in parameter.
590
     *
591
     * @param string $beannamespace The namespace of the bean
592
     */
593
    public function generatePhpCode($beannamespace)
594
    {
595
        $tableName = $this->table->getName();
596
        $baseClassName = TDBMDaoGenerator::getBaseBeanNameFromTableName($tableName);
597
        $className = TDBMDaoGenerator::getBeanNameFromTableName($tableName);
598
        $parentFk = $this->schemaAnalyzer->getParentRelationship($tableName);
599
600
        $classes = $this->generateExtendsAndUseStatements($parentFk);
601
602
        $uses = array_map(function ($className) use ($beannamespace) {
603
            return 'use '.$beannamespace.'\\'.$className.";\n";
604
        }, $classes);
605
        $use = implode('', $uses);
606
607
        if ($parentFk !== null) {
608
            $extends = TDBMDaoGenerator::getBeanNameFromTableName($parentFk->getForeignTableName());
609
        } else {
610
            $extends = 'AbstractTDBMObject';
611
            $use .= "use Mouf\\Database\\TDBM\\AbstractTDBMObject;\n";
612
        }
613
614
        $str = "<?php
615
namespace {$beannamespace}\\Generated;
616
617
use Mouf\\Database\\TDBM\\ResultIterator;
618
use Mouf\\Database\\TDBM\\ResultArray;
619
use Mouf\\Database\\TDBM\\AlterableResultIterator;
620
$use
621
/*
622
 * This file has been automatically generated by TDBM.
623
 * DO NOT edit this file, as it might be overwritten.
624
 * If you need to perform changes, edit the $className class instead!
625
 */
626
627
/**
628
 * The $baseClassName class maps the '$tableName' table in database.
629
 */
630
class $baseClassName extends $extends implements \\JsonSerializable
631
{
632
";
633
634
        $str .= $this->generateBeanConstructor();
635
636
        foreach ($this->getExposedProperties() as $property) {
637
            $str .= $property->getGetterSetterCode();
638
        }
639
640
        $str .= $this->generateDirectForeignKeysCode();
641
        $str .= $this->generatePivotTableCode();
642
        $str .= $this->generateJsonSerialize();
643
644
        $str .= $this->generateGetUsedTablesCode();
645
646
        $str .= $this->generateOnDeleteCode();
647
648
        $str .= '}
649
';
650
651
        return $str;
652
    }
653
654
    /**
655
     * @param string $beanNamespace
656
     * @param string $beanClassName
657
     *
658
     * @return array first element: list of used beans, second item: PHP code as a string
659
     */
660
    public function generateFindByDaoCode($beanNamespace, $beanClassName)
661
    {
662
        $code = '';
663
        $usedBeans = [];
664
        foreach ($this->table->getIndexes() as $index) {
665
            if (!$index->isPrimary()) {
666
                list($usedBeansForIndex, $codeForIndex) = $this->generateFindByDaoCodeForIndex($index, $beanNamespace, $beanClassName);
667
                $code .= $codeForIndex;
668
                $usedBeans = array_merge($usedBeans, $usedBeansForIndex);
669
            }
670
        }
671
672
        return [$usedBeans, $code];
673
    }
674
675
    /**
676
     * @param Index  $index
677
     * @param string $beanNamespace
678
     * @param string $beanClassName
679
     *
680
     * @return array first element: list of used beans, second item: PHP code as a string
681
     */
682
    private function generateFindByDaoCodeForIndex(Index $index, $beanNamespace, $beanClassName)
683
    {
684
        $columns = $index->getColumns();
685
        $usedBeans = [];
686
687
        /*
688
         * The list of elements building this index (expressed as columns or foreign keys)
689
         * @var AbstractBeanPropertyDescriptor[]
690
         */
691
        $elements = [];
692
693
        foreach ($columns as $column) {
694
            $fk = $this->isPartOfForeignKey($this->table, $this->table->getColumn($column));
695
            if ($fk !== null) {
696
                if (!in_array($fk, $elements)) {
697
                    $elements[] = new ObjectBeanPropertyDescriptor($this->table, $fk, $this->schemaAnalyzer);
698
                }
699
            } else {
700
                $elements[] = new ScalarBeanPropertyDescriptor($this->table, $this->table->getColumn($column));
701
            }
702
        }
703
704
        // If the index is actually only a foreign key, let's bypass it entirely.
705
        if (count($elements) === 1 && $elements[0] instanceof ObjectBeanPropertyDescriptor) {
706
            return [[], ''];
707
        }
708
709
        $methodNameComponent = [];
710
        $functionParameters = [];
711
        $first = true;
712
        foreach ($elements as $element) {
713
            $methodNameComponent[] = $element->getUpperCamelCaseName();
714
            $functionParameter = $element->getClassName();
715
            if ($functionParameter) {
716
                $usedBeans[] = $beanNamespace.'\\'.$functionParameter;
717
                $functionParameter .= ' ';
718
            }
719
            $functionParameter .= $element->getVariableName();
720
            if ($first) {
721
                $first = false;
722
            } else {
723
                $functionParameter .= ' = null';
724
            }
725
            $functionParameters[] = $functionParameter;
726
        }
727
        if ($index->isUnique()) {
728
            $methodName = 'findOneBy'.implode('And', $methodNameComponent);
729
            $calledMethod = 'findOne';
730
            $returnType = "{$beanClassName}";
731
        } else {
732
            $methodName = 'findBy'.implode('And', $methodNameComponent);
733
            $returnType = "{$beanClassName}[]|ResultIterator|ResultArray";
734
            $calledMethod = 'find';
735
        }
736
        $functionParametersString = implode(', ', $functionParameters);
737
738
        $count = 0;
739
740
        $params = [];
741
        $filterArrayCode = '';
742
        $commentArguments = [];
743
        foreach ($elements as $element) {
744
            $params[] = $element->getParamAnnotation();
745
            if ($element instanceof ScalarBeanPropertyDescriptor) {
746
                $filterArrayCode .= '            '.var_export($element->getColumnName(), true).' => '.$element->getVariableName().",\n";
747
            } else {
748
                ++$count;
749
                $filterArrayCode .= '            '.$count.' => '.$element->getVariableName().",\n";
750
            }
751
            $commentArguments[] = substr($element->getVariableName(), 1);
752
        }
753
        $paramsString = implode("\n", $params);
754
755
        $code = "
756
    /**
757
     * Get a list of $beanClassName filtered by ".implode(', ', $commentArguments).".
758
     *
759
$paramsString
760
     * @param mixed \$orderBy The order string
761
     * @param array \$additionalTablesFetch A list of additional tables to fetch (for performance improvement)
762
     * @param string \$mode Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.
763
     * @return $returnType
764
     */
765
    public function $methodName($functionParametersString, \$orderBy = null, array \$additionalTablesFetch = array(), \$mode = null)
766
    {
767
        \$filter = [
768
".$filterArrayCode."        ];
769
        return \$this->$calledMethod(\$filter, [], \$orderBy, \$additionalTablesFetch, \$mode);
770
    }
771
";
772
773
        return [$usedBeans, $code];
774
    }
775
776
    /**
777
     * Generates the code for the getUsedTable protected method.
778
     *
779
     * @return string
780
     */
781
    private function generateGetUsedTablesCode()
782
    {
783
        $hasParentRelationship = $this->schemaAnalyzer->getParentRelationship($this->table->getName()) !== null;
784
        if ($hasParentRelationship) {
785
            $code = sprintf('        $tables = parent::getUsedTables();
786
        $tables[] = %s;
787
        
788
        return $tables;', var_export($this->table->getName(), true));
789
        } else {
790
            $code = sprintf('        return [ %s ];', var_export($this->table->getName(), true));
791
        }
792
793
        return sprintf('
794
    /**
795
     * Returns an array of used tables by this bean (from parent to child relationship).
796
     *
797
     * @return string[]
798
     */
799
    protected function getUsedTables()
800
    {
801
%s    
802
    }
803
', $code);
804
    }
805
806
    private function generateOnDeleteCode()
807
    {
808
        $code = '';
809
        $relationships = $this->getPropertiesForTable($this->table);
810
        foreach ($relationships as $relationship) {
811
            if ($relationship instanceof ObjectBeanPropertyDescriptor) {
812
                $code .= sprintf('        $this->setRef('.var_export($relationship->getForeignKey()->getName(), true).', null, '.var_export($this->table->getName(), true).');');
813
            }
814
        }
815
816
        if ($code) {
817
            return sprintf('
818
    /**
819
     * Method called when the bean is removed from database.
820
     *
821
     */
822
    protected function onDelete()
823
    {
824
        parent::onDelete();
825
%s
826
    }
827
', $code);
828
        }
829
830
        return '';
831
    }
832
}
833