1 | <?php |
||||
2 | |||||
3 | declare(strict_types=1); |
||||
4 | |||||
5 | namespace TheCodingMachine\TDBM\Utils; |
||||
6 | |||||
7 | use Doctrine\DBAL\Schema\Column; |
||||
8 | use Doctrine\DBAL\Schema\Index; |
||||
9 | use Doctrine\DBAL\Schema\Schema; |
||||
10 | use Doctrine\DBAL\Schema\Table; |
||||
11 | use Doctrine\DBAL\Schema\ForeignKeyConstraint; |
||||
12 | use Doctrine\DBAL\Types\Types; |
||||
13 | use JsonSerializable; |
||||
14 | use Mouf\Database\SchemaAnalyzer\SchemaAnalyzer; |
||||
15 | use Ramsey\Uuid\Uuid; |
||||
16 | use TheCodingMachine\TDBM\AbstractTDBMObject; |
||||
17 | use TheCodingMachine\TDBM\AlterableResultIterator; |
||||
18 | use TheCodingMachine\TDBM\ConfigurationInterface; |
||||
19 | use TheCodingMachine\TDBM\ResultIterator; |
||||
20 | use TheCodingMachine\TDBM\Schema\ForeignKey; |
||||
21 | use TheCodingMachine\TDBM\Schema\ForeignKeys; |
||||
22 | use TheCodingMachine\TDBM\TDBMException; |
||||
23 | use TheCodingMachine\TDBM\TDBMSchemaAnalyzer; |
||||
24 | use TheCodingMachine\TDBM\TDBMService; |
||||
25 | use TheCodingMachine\TDBM\Utils\Annotation\AbstractTraitAnnotation; |
||||
26 | use TheCodingMachine\TDBM\Utils\Annotation\AddInterfaceOnDao; |
||||
27 | use TheCodingMachine\TDBM\Utils\Annotation\AddTrait; |
||||
28 | use TheCodingMachine\TDBM\Utils\Annotation\AddTraitOnDao; |
||||
29 | use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser; |
||||
30 | use TheCodingMachine\TDBM\Utils\Annotation\AddInterface; |
||||
31 | use Laminas\Code\Generator\AbstractMemberGenerator; |
||||
32 | use Laminas\Code\Generator\ClassGenerator; |
||||
33 | use Laminas\Code\Generator\DocBlock\Tag; |
||||
34 | use Laminas\Code\Generator\DocBlock\Tag\GenericTag; |
||||
35 | use Laminas\Code\Generator\DocBlock\Tag\ParamTag; |
||||
36 | use Laminas\Code\Generator\DocBlock\Tag\ReturnTag; |
||||
37 | use Laminas\Code\Generator\DocBlock\Tag\ThrowsTag; |
||||
38 | use Laminas\Code\Generator\DocBlock\Tag\VarTag; |
||||
39 | use Laminas\Code\Generator\DocBlockGenerator; |
||||
40 | use Laminas\Code\Generator\FileGenerator; |
||||
41 | use Laminas\Code\Generator\MethodGenerator; |
||||
42 | use Laminas\Code\Generator\ParameterGenerator; |
||||
43 | use Laminas\Code\Generator\PropertyGenerator; |
||||
44 | |||||
45 | use function array_combine; |
||||
46 | use function implode; |
||||
47 | use function strtolower; |
||||
48 | use function var_export; |
||||
49 | |||||
50 | /** |
||||
51 | * This class represents a bean. |
||||
52 | */ |
||||
53 | class BeanDescriptor implements BeanDescriptorInterface |
||||
54 | { |
||||
55 | /** |
||||
56 | * @var Table |
||||
57 | */ |
||||
58 | private $table; |
||||
59 | |||||
60 | /** |
||||
61 | * @var SchemaAnalyzer |
||||
62 | */ |
||||
63 | private $schemaAnalyzer; |
||||
64 | |||||
65 | /** |
||||
66 | * @var Schema |
||||
67 | */ |
||||
68 | private $schema; |
||||
69 | |||||
70 | /** |
||||
71 | * @var AbstractBeanPropertyDescriptor[] |
||||
72 | */ |
||||
73 | private $beanPropertyDescriptors = []; |
||||
74 | |||||
75 | /** |
||||
76 | * @var TDBMSchemaAnalyzer |
||||
77 | */ |
||||
78 | private $tdbmSchemaAnalyzer; |
||||
79 | |||||
80 | /** |
||||
81 | * @var NamingStrategyInterface |
||||
82 | */ |
||||
83 | private $namingStrategy; |
||||
84 | /** |
||||
85 | * @var string |
||||
86 | */ |
||||
87 | private $beanNamespace; |
||||
88 | /** |
||||
89 | * @var string |
||||
90 | */ |
||||
91 | private $generatedBeanNamespace; |
||||
92 | /** |
||||
93 | * @var AnnotationParser |
||||
94 | */ |
||||
95 | private $annotationParser; |
||||
96 | /** |
||||
97 | * @var string |
||||
98 | */ |
||||
99 | private $daoNamespace; |
||||
100 | /** |
||||
101 | * @var string |
||||
102 | */ |
||||
103 | private $generatedDaoNamespace; |
||||
104 | /** |
||||
105 | * @var string |
||||
106 | */ |
||||
107 | private $resultIteratorNamespace; |
||||
108 | /** |
||||
109 | * @var string |
||||
110 | */ |
||||
111 | private $generatedResultIteratorNamespace; |
||||
112 | /** |
||||
113 | * @var CodeGeneratorListenerInterface |
||||
114 | */ |
||||
115 | private $codeGeneratorListener; |
||||
116 | /** |
||||
117 | * @var ConfigurationInterface |
||||
118 | */ |
||||
119 | private $configuration; |
||||
120 | /** |
||||
121 | * @var BeanRegistry |
||||
122 | */ |
||||
123 | private $registry; |
||||
124 | /** |
||||
125 | * @var MethodDescriptorInterface[][] |
||||
126 | */ |
||||
127 | private $descriptorsByMethodName = []; |
||||
128 | /** |
||||
129 | * @var DirectForeignKeyMethodDescriptor[]|null |
||||
130 | */ |
||||
131 | private $directForeignKeysDescriptors = null; |
||||
132 | /** |
||||
133 | * @var PivotTableMethodsDescriptor[]|null |
||||
134 | */ |
||||
135 | private $pivotTableDescriptors = null; |
||||
136 | |||||
137 | public function __construct( |
||||
138 | Table $table, |
||||
139 | string $beanNamespace, |
||||
140 | string $generatedBeanNamespace, |
||||
141 | string $daoNamespace, |
||||
142 | string $generatedDaoNamespace, |
||||
143 | string $resultIteratorNamespace, |
||||
144 | string $generatedResultIteratorNamespace, |
||||
145 | SchemaAnalyzer $schemaAnalyzer, |
||||
146 | Schema $schema, |
||||
147 | TDBMSchemaAnalyzer $tdbmSchemaAnalyzer, |
||||
148 | NamingStrategyInterface $namingStrategy, |
||||
149 | AnnotationParser $annotationParser, |
||||
150 | CodeGeneratorListenerInterface $codeGeneratorListener, |
||||
151 | ConfigurationInterface $configuration, |
||||
152 | BeanRegistry $registry |
||||
153 | ) { |
||||
154 | $this->table = $table; |
||||
155 | $this->beanNamespace = $beanNamespace; |
||||
156 | $this->generatedBeanNamespace = $generatedBeanNamespace; |
||||
157 | $this->daoNamespace = $daoNamespace; |
||||
158 | $this->generatedDaoNamespace = $generatedDaoNamespace; |
||||
159 | $this->resultIteratorNamespace = $resultIteratorNamespace; |
||||
160 | $this->generatedResultIteratorNamespace = $generatedResultIteratorNamespace; |
||||
161 | $this->schemaAnalyzer = $schemaAnalyzer; |
||||
162 | $this->schema = $schema; |
||||
163 | $this->tdbmSchemaAnalyzer = $tdbmSchemaAnalyzer; |
||||
164 | $this->namingStrategy = $namingStrategy; |
||||
165 | $this->annotationParser = $annotationParser; |
||||
166 | $this->codeGeneratorListener = $codeGeneratorListener; |
||||
167 | $this->configuration = $configuration; |
||||
168 | $this->registry = $registry; |
||||
169 | } |
||||
170 | |||||
171 | public function initBeanPropertyDescriptors(): void |
||||
172 | { |
||||
173 | $this->beanPropertyDescriptors = $this->getProperties($this->table); |
||||
174 | |||||
175 | //init the list of method names with regular properties names |
||||
176 | foreach ($this->beanPropertyDescriptors as $beanPropertyDescriptor) { |
||||
177 | $this->checkForDuplicate($beanPropertyDescriptor); |
||||
178 | } |
||||
179 | } |
||||
180 | |||||
181 | /** |
||||
182 | * Returns the foreign-key the column is part of, if any. null otherwise. |
||||
183 | * |
||||
184 | * @param Table $table |
||||
185 | * @param Column $column |
||||
186 | * |
||||
187 | * @return ForeignKeyConstraint|null |
||||
188 | */ |
||||
189 | private function isPartOfForeignKey(Table $table, Column $column): ?ForeignKeyConstraint |
||||
190 | { |
||||
191 | $localColumnName = $column->getName(); |
||||
192 | foreach ($table->getForeignKeys() as $foreignKey) { |
||||
193 | foreach ($foreignKey->getUnquotedLocalColumns() as $columnName) { |
||||
194 | if ($columnName === $localColumnName) { |
||||
195 | return $foreignKey; |
||||
196 | } |
||||
197 | } |
||||
198 | } |
||||
199 | |||||
200 | return null; |
||||
201 | } |
||||
202 | |||||
203 | /** |
||||
204 | * @return AbstractBeanPropertyDescriptor[] |
||||
205 | */ |
||||
206 | public function getBeanPropertyDescriptors(): array |
||||
207 | { |
||||
208 | return $this->beanPropertyDescriptors; |
||||
209 | } |
||||
210 | |||||
211 | /** |
||||
212 | * Returns the list of columns that are not nullable and not autogenerated for a given table and its parent. |
||||
213 | * |
||||
214 | * @return AbstractBeanPropertyDescriptor[] |
||||
215 | */ |
||||
216 | public function getConstructorProperties(): array |
||||
217 | { |
||||
218 | $constructorProperties = array_filter($this->beanPropertyDescriptors, static function (AbstractBeanPropertyDescriptor $property) { |
||||
219 | return !$property instanceof InheritanceReferencePropertyDescriptor && $property->isCompulsory() && !$property->isReadOnly(); |
||||
220 | }); |
||||
221 | |||||
222 | return $constructorProperties; |
||||
223 | } |
||||
224 | |||||
225 | /** |
||||
226 | * Returns the list of columns that have default values for a given table. |
||||
227 | * |
||||
228 | * @return AbstractBeanPropertyDescriptor[] |
||||
229 | */ |
||||
230 | public function getPropertiesWithDefault(): array |
||||
231 | { |
||||
232 | $properties = $this->getPropertiesForTable($this->table); |
||||
233 | $defaultProperties = array_filter($properties, function (AbstractBeanPropertyDescriptor $property) { |
||||
234 | return $property->hasDefault(); |
||||
235 | }); |
||||
236 | |||||
237 | return $defaultProperties; |
||||
238 | } |
||||
239 | |||||
240 | /** |
||||
241 | * Returns the list of properties exposed as getters and setters in this class. |
||||
242 | * |
||||
243 | * @return AbstractBeanPropertyDescriptor[] |
||||
244 | */ |
||||
245 | public function getExposedProperties(): array |
||||
246 | { |
||||
247 | $exposedProperties = array_filter($this->beanPropertyDescriptors, function (AbstractBeanPropertyDescriptor $property) { |
||||
248 | return !$property instanceof InheritanceReferencePropertyDescriptor && $property->getTable()->getName() === $this->table->getName(); |
||||
249 | }); |
||||
250 | |||||
251 | return $exposedProperties; |
||||
252 | } |
||||
253 | |||||
254 | /** |
||||
255 | * Returns the list of properties for this table (including parent tables). |
||||
256 | * |
||||
257 | * @param Table $table |
||||
258 | * |
||||
259 | * @return AbstractBeanPropertyDescriptor[] |
||||
260 | */ |
||||
261 | private function getProperties(Table $table): array |
||||
262 | { |
||||
263 | // Security check: a table MUST have a primary key |
||||
264 | TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($table); |
||||
265 | |||||
266 | $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName()); |
||||
267 | if ($parentRelationship) { |
||||
268 | $parentTable = $this->schema->getTable($parentRelationship->getForeignTableName()); |
||||
269 | $properties = $this->getProperties($parentTable); |
||||
270 | // we merge properties by overriding property names. |
||||
271 | $localProperties = $this->getPropertiesForTable($table); |
||||
272 | foreach ($localProperties as $name => $property) { |
||||
273 | // We do not override properties if this is a primary key! |
||||
274 | if (!$property instanceof InheritanceReferencePropertyDescriptor && $property->isPrimaryKey()) { |
||||
275 | continue; |
||||
276 | } |
||||
277 | $properties[$name] = $property; |
||||
278 | } |
||||
279 | } else { |
||||
280 | $properties = $this->getPropertiesForTable($table); |
||||
281 | } |
||||
282 | |||||
283 | return $properties; |
||||
284 | } |
||||
285 | |||||
286 | /** |
||||
287 | * Returns the list of properties for this table (ignoring parent tables). |
||||
288 | * |
||||
289 | * @param Table $table |
||||
290 | * |
||||
291 | * @return AbstractBeanPropertyDescriptor[] |
||||
292 | */ |
||||
293 | private function getPropertiesForTable(Table $table): array |
||||
294 | { |
||||
295 | $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName()); |
||||
296 | if ($parentRelationship) { |
||||
297 | $ignoreColumns = $parentRelationship->getUnquotedForeignColumns(); |
||||
298 | } else { |
||||
299 | $ignoreColumns = []; |
||||
300 | } |
||||
301 | |||||
302 | $beanPropertyDescriptors = []; |
||||
303 | foreach ($table->getColumns() as $column) { |
||||
304 | if (in_array($column->getName(), $ignoreColumns, true)) { |
||||
305 | continue; |
||||
306 | } |
||||
307 | |||||
308 | $fk = $this->isPartOfForeignKey($table, $column); |
||||
309 | if ($fk !== null) { |
||||
310 | // Check that previously added descriptors are not added on same FK (can happen with multi key FK). |
||||
311 | foreach ($beanPropertyDescriptors as $beanDescriptor) { |
||||
312 | if ($beanDescriptor instanceof ObjectBeanPropertyDescriptor && $beanDescriptor->getForeignKey() === $fk) { |
||||
313 | continue 2; |
||||
314 | } |
||||
315 | } |
||||
316 | $propertyDescriptor = new ObjectBeanPropertyDescriptor($table, $fk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser, $this->registry->getBeanForTableName($fk->getForeignTableName()), $this->resultIteratorNamespace); |
||||
317 | // Check that this property is not an inheritance relationship |
||||
318 | $parentRelationship = $this->schemaAnalyzer->getParentRelationship($table->getName()); |
||||
319 | if ($parentRelationship !== null && $parentRelationship->getName() === $fk->getName()) { |
||||
320 | $beanPropertyDescriptors[] = new InheritanceReferencePropertyDescriptor( |
||||
321 | $table, |
||||
322 | $column, |
||||
323 | $this->namingStrategy, |
||||
324 | $this->annotationParser, |
||||
325 | $propertyDescriptor |
||||
326 | ); |
||||
327 | } else { |
||||
328 | $beanPropertyDescriptors[] = $propertyDescriptor; |
||||
329 | } |
||||
330 | } else { |
||||
331 | $beanPropertyDescriptors[] = new ScalarBeanPropertyDescriptor($table, $column, $this->namingStrategy, $this->annotationParser); |
||||
332 | } |
||||
333 | } |
||||
334 | |||||
335 | // Now, let's get the name of all properties and let's check there is no duplicate. |
||||
336 | /* @var $names AbstractBeanPropertyDescriptor[] */ |
||||
337 | $names = []; |
||||
338 | foreach ($beanPropertyDescriptors as $beanDescriptor) { |
||||
339 | $name = $beanDescriptor->getGetterName(); |
||||
340 | if (isset($names[$name])) { |
||||
341 | $names[$name]->useAlternativeName(); |
||||
342 | $beanDescriptor->useAlternativeName(); |
||||
343 | } else { |
||||
344 | $names[$name] = $beanDescriptor; |
||||
345 | } |
||||
346 | } |
||||
347 | |||||
348 | // Final check (throw exceptions if problem arises) |
||||
349 | $names = []; |
||||
350 | foreach ($beanPropertyDescriptors as $beanDescriptor) { |
||||
351 | $name = $beanDescriptor->getGetterName(); |
||||
352 | if (isset($names[$name])) { |
||||
353 | throw new TDBMException('Unsolvable name conflict while generating method name "' . $name . '"'); |
||||
354 | } else { |
||||
355 | $names[$name] = $beanDescriptor; |
||||
356 | } |
||||
357 | } |
||||
358 | |||||
359 | // Last step, let's rebuild the list with a map: |
||||
360 | $beanPropertyDescriptorsMap = []; |
||||
361 | foreach ($beanPropertyDescriptors as $beanDescriptor) { |
||||
362 | $beanPropertyDescriptorsMap[$beanDescriptor->getVariableName()] = $beanDescriptor; |
||||
363 | } |
||||
364 | |||||
365 | return $beanPropertyDescriptorsMap; |
||||
366 | } |
||||
367 | |||||
368 | private function generateBeanConstructor(): MethodGenerator |
||||
369 | { |
||||
370 | $constructorProperties = $this->getConstructorProperties(); |
||||
371 | |||||
372 | $constructor = new MethodGenerator('__construct', [], MethodGenerator::FLAG_PUBLIC); |
||||
373 | $constructorDocBlock = new DocBlockGenerator('The constructor takes all compulsory arguments.'); |
||||
374 | $constructorDocBlock->setWordWrap(false); |
||||
375 | $constructor->setDocBlock($constructorDocBlock); |
||||
376 | |||||
377 | $assigns = []; |
||||
378 | $parentConstructorArguments = []; |
||||
379 | |||||
380 | foreach ($constructorProperties as $property) { |
||||
381 | $parameter = new ParameterGenerator(ltrim($property->getSafeVariableName(), '$')); |
||||
382 | if ($property->isTypeHintable()) { |
||||
383 | $parameter->setType($property->getPhpType()); |
||||
384 | } |
||||
385 | $constructor->setParameter($parameter); |
||||
386 | |||||
387 | $constructorDocBlock->setTag($property->getParamAnnotation()); |
||||
388 | |||||
389 | if ($property->getTable()->getName() === $this->table->getName()) { |
||||
390 | $assigns[] = $property->getConstructorAssignCode()."\n"; |
||||
391 | } else { |
||||
392 | $parentConstructorArguments[] = $property->getSafeVariableName(); |
||||
393 | } |
||||
394 | } |
||||
395 | |||||
396 | $parentConstructorCode = sprintf("parent::__construct(%s);\n", implode(', ', $parentConstructorArguments)); |
||||
397 | |||||
398 | foreach ($this->getPropertiesWithDefault() as $property) { |
||||
399 | $assigns[] = $property->assignToDefaultCode()."\n"; |
||||
400 | } |
||||
401 | |||||
402 | $body = $parentConstructorCode . implode('', $assigns); |
||||
403 | |||||
404 | $constructor->setBody($body); |
||||
405 | |||||
406 | return $constructor; |
||||
407 | } |
||||
408 | |||||
409 | /** |
||||
410 | * Returns the descriptors of one-to-many relationships (the foreign keys pointing on this beans) |
||||
411 | * |
||||
412 | * @return DirectForeignKeyMethodDescriptor[] |
||||
413 | */ |
||||
414 | private function getDirectForeignKeysDescriptors(): array |
||||
415 | { |
||||
416 | if ($this->directForeignKeysDescriptors !== null) { |
||||
417 | return $this->directForeignKeysDescriptors; |
||||
418 | } |
||||
419 | $fks = $this->tdbmSchemaAnalyzer->getIncomingForeignKeys($this->table->getName()); |
||||
420 | |||||
421 | $descriptors = []; |
||||
422 | |||||
423 | foreach ($fks as $fk) { |
||||
424 | $desc = new DirectForeignKeyMethodDescriptor($fk, $this->table, $this->namingStrategy, $this->annotationParser, $this->beanNamespace, $this->resultIteratorNamespace); |
||||
425 | $this->checkForDuplicate($desc); |
||||
426 | $descriptors[] = $desc; |
||||
427 | } |
||||
428 | |||||
429 | $this->directForeignKeysDescriptors = $descriptors; |
||||
430 | return $this->directForeignKeysDescriptors; |
||||
431 | } |
||||
432 | |||||
433 | /** |
||||
434 | * @return PivotTableMethodsDescriptor[] |
||||
435 | */ |
||||
436 | private function getPivotTableDescriptors(): array |
||||
437 | { |
||||
438 | if ($this->pivotTableDescriptors !== null) { |
||||
439 | return $this->pivotTableDescriptors; |
||||
440 | } |
||||
441 | $descs = []; |
||||
442 | foreach ($this->schemaAnalyzer->detectJunctionTables(true) as $table) { |
||||
443 | // There are exactly 2 FKs since this is a pivot table. |
||||
444 | $fks = array_values($table->getForeignKeys()); |
||||
445 | |||||
446 | if ($fks[0]->getForeignTableName() === $this->table->getName()) { |
||||
447 | list($localFk, $remoteFk) = $fks; |
||||
448 | $desc = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->annotationParser, $this->beanNamespace, $this->resultIteratorNamespace); |
||||
449 | $this->checkForDuplicate($desc); |
||||
450 | $descs[] = $desc; |
||||
451 | } |
||||
452 | if ($fks[1]->getForeignTableName() === $this->table->getName()) { |
||||
453 | list($remoteFk, $localFk) = $fks; |
||||
454 | $desc = new PivotTableMethodsDescriptor($table, $localFk, $remoteFk, $this->namingStrategy, $this->annotationParser, $this->beanNamespace, $this->resultIteratorNamespace); |
||||
455 | $this->checkForDuplicate($desc); |
||||
456 | $descs[] = $desc; |
||||
457 | } |
||||
458 | } |
||||
459 | |||||
460 | $this->pivotTableDescriptors = $descs; |
||||
461 | return $this->pivotTableDescriptors; |
||||
462 | } |
||||
463 | |||||
464 | /** |
||||
465 | * Check the method name isn't already used and flag the associated descriptors to use their alternative names if it is the case |
||||
466 | */ |
||||
467 | private function checkForDuplicate(MethodDescriptorInterface $descriptor): void |
||||
468 | { |
||||
469 | $name = strtolower($descriptor->getName()); |
||||
470 | if (!isset($this->descriptorsByMethodName[$name])) { |
||||
471 | $this->descriptorsByMethodName[$name] = []; |
||||
472 | } |
||||
473 | $this->descriptorsByMethodName[$name][] = $descriptor; |
||||
474 | $descriptors = $this->descriptorsByMethodName[$name]; |
||||
475 | if (count($descriptors) > 1) { |
||||
476 | $properties = array_filter($descriptors, function ($descriptor) { |
||||
477 | return $descriptor instanceof AbstractBeanPropertyDescriptor; |
||||
478 | }); |
||||
479 | $renameProperties = count($properties) > 1; |
||||
480 | |||||
481 | foreach ($descriptors as $descriptor) { |
||||
482 | if ($renameProperties || !$descriptor instanceof AbstractBeanPropertyDescriptor) { |
||||
483 | $descriptor->useAlternativeName(); |
||||
484 | } |
||||
485 | } |
||||
486 | } |
||||
487 | } |
||||
488 | |||||
489 | /** |
||||
490 | * Returns the list of method descriptors (and applies the alternative name if needed). |
||||
491 | * |
||||
492 | * @return RelationshipMethodDescriptorInterface[] |
||||
493 | */ |
||||
494 | public function getMethodDescriptors(): array |
||||
495 | { |
||||
496 | $directForeignKeyDescriptors = $this->getDirectForeignKeysDescriptors(); |
||||
497 | $pivotTableDescriptors = $this->getPivotTableDescriptors(); |
||||
498 | |||||
499 | return array_merge($directForeignKeyDescriptors, $pivotTableDescriptors); |
||||
500 | } |
||||
501 | |||||
502 | public function generateJsonSerialize(): MethodGenerator |
||||
503 | { |
||||
504 | $tableName = $this->table->getName(); |
||||
505 | $parentFk = $this->schemaAnalyzer->getParentRelationship($tableName); |
||||
506 | |||||
507 | $method = new MethodGenerator('jsonSerialize'); |
||||
508 | $method->setDocBlock(new DocBlockGenerator( |
||||
509 | 'Serializes the object for JSON encoding.', |
||||
510 | null, |
||||
511 | [ |
||||
512 | new ParamTag('$stopRecursion', ['bool'], 'Parameter used internally by TDBM to stop embedded objects from embedding other objects.'), |
||||
513 | new ReturnTag(['array']) |
||||
514 | ] |
||||
515 | )); |
||||
516 | $method->setParameter(new ParameterGenerator('stopRecursion', 'bool', false)); |
||||
517 | $method->setReturnType("mixed"); |
||||
518 | |||||
519 | if ($parentFk !== null) { |
||||
520 | $body = '$array = parent::jsonSerialize($stopRecursion);'; |
||||
521 | } else { |
||||
522 | $body = '$array = [];'; |
||||
523 | } |
||||
524 | |||||
525 | foreach ($this->getExposedProperties() as $beanPropertyDescriptor) { |
||||
526 | $propertyCode = $beanPropertyDescriptor->getJsonSerializeCode(); |
||||
527 | if (!empty($propertyCode)) { |
||||
528 | $body .= PHP_EOL . $propertyCode; |
||||
529 | } |
||||
530 | } |
||||
531 | |||||
532 | // Many2many relationships |
||||
533 | foreach ($this->getMethodDescriptors() as $methodDescriptor) { |
||||
534 | $methodCode = $methodDescriptor->getJsonSerializeCode(); |
||||
535 | if (!empty($methodCode)) { |
||||
536 | $body .= PHP_EOL . $methodCode; |
||||
537 | } |
||||
538 | } |
||||
539 | |||||
540 | $body .= PHP_EOL . 'return $array;'; |
||||
541 | |||||
542 | $method->setBody($body); |
||||
543 | |||||
544 | return $method; |
||||
545 | } |
||||
546 | |||||
547 | /** |
||||
548 | * Returns as an array the class we need to extend from and the list of use statements. |
||||
549 | * |
||||
550 | * @param ForeignKeyConstraint|null $parentFk |
||||
551 | * @return string[] |
||||
552 | */ |
||||
553 | private function generateExtendsAndUseStatements(ForeignKeyConstraint $parentFk = null): array |
||||
554 | { |
||||
555 | $classes = []; |
||||
556 | if ($parentFk !== null) { |
||||
557 | $extends = $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName()); |
||||
558 | $classes[] = $extends; |
||||
559 | } |
||||
560 | |||||
561 | foreach ($this->getBeanPropertyDescriptors() as $beanPropertyDescriptor) { |
||||
562 | $className = $beanPropertyDescriptor->getClassName(); |
||||
563 | if (null !== $className) { |
||||
564 | $classes[] = $className; |
||||
565 | } |
||||
566 | } |
||||
567 | |||||
568 | foreach ($this->getMethodDescriptors() as $descriptor) { |
||||
569 | $classes = array_merge($classes, $descriptor->getUsedClasses()); |
||||
570 | } |
||||
571 | |||||
572 | $classes = array_unique($classes); |
||||
573 | |||||
574 | return $classes; |
||||
575 | } |
||||
576 | |||||
577 | /** |
||||
578 | * Returns the representation of the PHP bean file with all getters and setters. |
||||
579 | * |
||||
580 | * @return ?FileGenerator |
||||
581 | */ |
||||
582 | public function generatePhpCode(): ?FileGenerator |
||||
583 | { |
||||
584 | $file = new FileGenerator(); |
||||
585 | $class = new ClassGenerator(); |
||||
586 | $class->setAbstract(true); |
||||
587 | $file->setClass($class); |
||||
588 | $file->setNamespace($this->generatedBeanNamespace); |
||||
589 | |||||
590 | $tableName = $this->table->getName(); |
||||
591 | $baseClassName = $this->namingStrategy->getBaseBeanClassName($tableName); |
||||
592 | $className = $this->namingStrategy->getBeanClassName($tableName); |
||||
593 | $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName()); |
||||
594 | |||||
595 | $classes = $this->generateExtendsAndUseStatements($parentFk); |
||||
596 | |||||
597 | foreach ($classes as $useClass) { |
||||
598 | $file->setUse($this->beanNamespace.'\\'.$useClass); |
||||
599 | } |
||||
600 | |||||
601 | /*$uses = array_map(function ($className) { |
||||
602 | return 'use '.$this->beanNamespace.'\\'.$className.";\n"; |
||||
603 | }, $classes); |
||||
604 | $use = implode('', $uses);*/ |
||||
605 | |||||
606 | $extends = $this->getExtendedBeanClassName(); |
||||
607 | if ($extends === null) { |
||||
608 | $class->setExtendedClass(AbstractTDBMObject::class); |
||||
609 | $file->setUse(AbstractTDBMObject::class); |
||||
610 | } else { |
||||
611 | /** @var class-string $extends */ |
||||
612 | $class->setExtendedClass($extends); |
||||
613 | } |
||||
614 | |||||
615 | $file->setUse(ResultIterator::class); |
||||
616 | $file->setUse(AlterableResultIterator::class); |
||||
617 | $file->setUse(Uuid::class); |
||||
618 | $file->setUse(JsonSerializable::class); |
||||
619 | $file->setUse(ForeignKeys::class); |
||||
620 | |||||
621 | $class->setName($baseClassName); |
||||
622 | |||||
623 | $file->setDocBlock(new DocBlockGenerator( |
||||
624 | 'This file has been automatically generated by TDBM.', |
||||
625 | <<<EOF |
||||
626 | DO NOT edit this file, as it might be overwritten. |
||||
627 | If you need to perform changes, edit the $className class instead! |
||||
628 | EOF |
||||
629 | )); |
||||
630 | |||||
631 | $class->setDocBlock(new DocBlockGenerator("The $baseClassName class maps the '$tableName' table in database.")); |
||||
632 | |||||
633 | /** @var AddInterface[] $addInterfaceAnnotations */ |
||||
634 | $addInterfaceAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations(AddInterface::class); |
||||
635 | |||||
636 | $interfaces = [ JsonSerializable::class ]; |
||||
637 | foreach ($addInterfaceAnnotations as $annotation) { |
||||
638 | /** @phpstan-var class-string $className */ |
||||
639 | $className = $annotation->getName(); |
||||
640 | $interfaces[] = $className; |
||||
641 | } |
||||
642 | |||||
643 | $class->setImplementedInterfaces($interfaces); |
||||
644 | |||||
645 | $this->registerTraits($class, AddTrait::class); |
||||
646 | |||||
647 | $method = $this->generateBeanConstructor(); |
||||
648 | $method = $this->codeGeneratorListener->onBaseBeanConstructorGenerated($method, $this, $this->configuration, $class); |
||||
649 | if ($method) { |
||||
650 | $class->addMethodFromGenerator($this->generateBeanConstructor()); |
||||
651 | } |
||||
652 | |||||
653 | $fks = []; |
||||
654 | foreach ($this->getExposedProperties() as $property) { |
||||
655 | if ($property instanceof ObjectBeanPropertyDescriptor) { |
||||
656 | $fks[] = $property->getForeignKey(); |
||||
657 | } |
||||
658 | [$getter, $setter] = $property->getGetterSetterCode(); |
||||
659 | [$getter, $setter] = $this->codeGeneratorListener->onBaseBeanPropertyGenerated($getter, $setter, $property, $this, $this->configuration, $class); |
||||
660 | if ($getter !== null) { |
||||
661 | $class->addMethodFromGenerator($getter); |
||||
662 | } |
||||
663 | if ($setter !== null) { |
||||
664 | $class->addMethodFromGenerator($setter); |
||||
665 | } |
||||
666 | } |
||||
667 | |||||
668 | $pivotTableMethodsDescriptors = []; |
||||
669 | foreach ($this->getMethodDescriptors() as $methodDescriptor) { |
||||
670 | if ($methodDescriptor instanceof DirectForeignKeyMethodDescriptor) { |
||||
671 | [$method] = $methodDescriptor->getCode(); |
||||
672 | $method = $this->codeGeneratorListener->onBaseBeanOneToManyGenerated($method, $methodDescriptor, $this, $this->configuration, $class); |
||||
673 | if ($method) { |
||||
674 | $class->addMethodFromGenerator($method); |
||||
675 | } |
||||
676 | } elseif ($methodDescriptor instanceof PivotTableMethodsDescriptor) { |
||||
677 | $pivotTableMethodsDescriptors[] = $methodDescriptor; |
||||
678 | [ $getter, $adder, $remover, $has, $setter ] = $methodDescriptor->getCode(); |
||||
679 | $methods = $this->codeGeneratorListener->onBaseBeanManyToManyGenerated($getter, $adder, $remover, $has, $setter, $methodDescriptor, $this, $this->configuration, $class); |
||||
680 | foreach ($methods as $method) { |
||||
681 | if ($method) { |
||||
682 | $class->addMethodFromGenerator($method); |
||||
683 | } |
||||
684 | } |
||||
685 | } else { |
||||
686 | throw new \RuntimeException('Unexpected instance'); // @codeCoverageIgnore |
||||
687 | } |
||||
688 | } |
||||
689 | |||||
690 | $manyToManyRelationshipCode = $this->generateGetManyToManyRelationshipDescriptorCode($pivotTableMethodsDescriptors); |
||||
691 | if ($manyToManyRelationshipCode !== null) { |
||||
692 | $class->addMethodFromGenerator($manyToManyRelationshipCode); |
||||
693 | } |
||||
694 | $manyToManyRelationshipKeysCode = $this->generateGetManyToManyRelationshipDescriptorKeysCode($pivotTableMethodsDescriptors); |
||||
695 | if ($manyToManyRelationshipKeysCode !== null) { |
||||
696 | $class->addMethodFromGenerator($manyToManyRelationshipKeysCode); |
||||
697 | } |
||||
698 | |||||
699 | $foreignKeysProperty = new PropertyGenerator('foreignKeys'); |
||||
700 | $foreignKeysProperty->setStatic(true); |
||||
701 | $foreignKeysProperty->setVisibility(AbstractMemberGenerator::VISIBILITY_PRIVATE); |
||||
702 | $foreignKeysProperty->setDocBlock(new DocBlockGenerator(null, null, [new VarTag(null, ['\\'.ForeignKeys::class])])); |
||||
703 | $class->addPropertyFromGenerator($foreignKeysProperty); |
||||
704 | |||||
705 | $method = $this->generateGetForeignKeys($fks); |
||||
706 | $class->addMethodFromGenerator($method); |
||||
707 | |||||
708 | $method = $this->generateJsonSerialize(); |
||||
709 | $method = $this->codeGeneratorListener->onBaseBeanJsonSerializeGenerated($method, $this, $this->configuration, $class); |
||||
710 | if ($method !== null) { |
||||
711 | $class->addMethodFromGenerator($method); |
||||
712 | } |
||||
713 | |||||
714 | $class->addMethodFromGenerator($this->generateGetUsedTablesCode()); |
||||
715 | $onDeleteCode = $this->generateOnDeleteCode(); |
||||
716 | if ($onDeleteCode) { |
||||
717 | $class->addMethodFromGenerator($onDeleteCode); |
||||
718 | } |
||||
719 | $cloneCode = $this->generateCloneCode($pivotTableMethodsDescriptors); |
||||
720 | $cloneCode = $this->codeGeneratorListener->onBaseBeanCloneGenerated($cloneCode, $this, $this->configuration, $class); |
||||
721 | if ($cloneCode) { |
||||
722 | $class->addMethodFromGenerator($cloneCode); |
||||
723 | } |
||||
724 | |||||
725 | $file = $this->codeGeneratorListener->onBaseBeanGenerated($file, $this, $this->configuration); |
||||
726 | |||||
727 | return $file; |
||||
728 | } |
||||
729 | |||||
730 | private function registerTraits(ClassGenerator $class, string $annotationClass): void |
||||
731 | { |
||||
732 | /** @var AbstractTraitAnnotation[] $addTraitAnnotations */ |
||||
733 | $addTraitAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations($annotationClass); |
||||
734 | |||||
735 | foreach ($addTraitAnnotations as $annotation) { |
||||
736 | $class->addTrait($annotation->getName()); |
||||
737 | } |
||||
738 | |||||
739 | foreach ($addTraitAnnotations as $annotation) { |
||||
740 | foreach ($annotation->getInsteadOf() as $method => $replacedTrait) { |
||||
741 | $class->addTraitOverride($method, $replacedTrait); |
||||
742 | } |
||||
743 | foreach ($annotation->getAs() as $method => $replacedMethod) { |
||||
744 | $class->addTraitAlias($method, $replacedMethod); |
||||
745 | } |
||||
746 | } |
||||
747 | } |
||||
748 | |||||
749 | /** |
||||
750 | * Writes the representation of the PHP DAO file. |
||||
751 | * |
||||
752 | * @return ?FileGenerator |
||||
753 | */ |
||||
754 | public function generateDaoPhpCode(): ?FileGenerator |
||||
755 | { |
||||
756 | $file = new FileGenerator(); |
||||
757 | $class = new ClassGenerator(); |
||||
758 | $class->setAbstract(true); |
||||
759 | $file->setClass($class); |
||||
760 | $file->setNamespace($this->generatedDaoNamespace); |
||||
761 | |||||
762 | $tableName = $this->table->getName(); |
||||
763 | |||||
764 | $primaryKeyColumns = TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($this->table); |
||||
765 | |||||
766 | list($defaultSort, $defaultSortDirection) = $this->getDefaultSortColumnFromAnnotation($this->table); |
||||
767 | |||||
768 | $className = $this->namingStrategy->getDaoClassName($tableName); |
||||
769 | $baseClassName = $this->namingStrategy->getBaseDaoClassName($tableName); |
||||
770 | $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName); |
||||
771 | $beanClassName = $this->beanNamespace.'\\'.$beanClassWithoutNameSpace; |
||||
772 | $resultIteratorClassWithoutNameSpace = $this->getResultIteratorClassName(); |
||||
773 | $resultIteratorClass = $this->resultIteratorNamespace.'\\'.$resultIteratorClassWithoutNameSpace; |
||||
774 | |||||
775 | $findByDaoCodeMethods = $this->generateFindByDaoCode($this->beanNamespace, $beanClassWithoutNameSpace, $class); |
||||
776 | |||||
777 | $usedBeans[] = $beanClassName; |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||||
778 | // Let's suppress duplicates in used beans (if any) |
||||
779 | $usedBeans = array_flip(array_flip($usedBeans)); |
||||
780 | foreach ($usedBeans as $usedBean) { |
||||
781 | $class->addUse($usedBean); |
||||
782 | } |
||||
783 | |||||
784 | $file->setDocBlock(new DocBlockGenerator( |
||||
785 | <<<EOF |
||||
786 | This file has been automatically generated by TDBM. |
||||
787 | DO NOT edit this file, as it might be overwritten. |
||||
788 | If you need to perform changes, edit the $className class instead! |
||||
789 | EOF |
||||
790 | )); |
||||
791 | |||||
792 | $file->setNamespace($this->generatedDaoNamespace); |
||||
793 | |||||
794 | $class->addUse(TDBMService::class); |
||||
795 | $class->addUse(ResultIterator::class); |
||||
796 | $class->addUse(TDBMException::class); |
||||
797 | |||||
798 | $class->setName($baseClassName); |
||||
799 | |||||
800 | $class->setDocBlock(new DocBlockGenerator("The $baseClassName class will maintain the persistence of $beanClassWithoutNameSpace class into the $tableName table.")); |
||||
801 | |||||
802 | /** @var AddInterfaceOnDao[] $addInterfaceOnDaoAnnotations */ |
||||
803 | $addInterfaceOnDaoAnnotations = $this->annotationParser->getTableAnnotations($this->table)->findAnnotations(AddInterfaceOnDao::class); |
||||
804 | |||||
805 | $interfaces = []; |
||||
806 | foreach ($addInterfaceOnDaoAnnotations as $annotation) { |
||||
807 | /** @phpstan-var class-string $className */ |
||||
808 | $className = $annotation->getName(); |
||||
809 | $interfaces[] = $className; |
||||
810 | } |
||||
811 | |||||
812 | $class->setImplementedInterfaces($interfaces); |
||||
813 | |||||
814 | $this->registerTraits($class, AddTraitOnDao::class); |
||||
815 | |||||
816 | $tdbmServiceProperty = new PropertyGenerator('tdbmService'); |
||||
817 | $tdbmServiceProperty->setDocBlock(new DocBlockGenerator(null, null, [new VarTag(null, ['\\'.TDBMService::class])])); |
||||
818 | $class->addPropertyFromGenerator($tdbmServiceProperty); |
||||
819 | |||||
820 | $defaultSortProperty = new PropertyGenerator('defaultSort', $defaultSort); |
||||
821 | $defaultSortProperty->setDocBlock(new DocBlockGenerator('The default sort column.', null, [new VarTag(null, ['string', 'null'])])); |
||||
822 | $class->addPropertyFromGenerator($defaultSortProperty); |
||||
823 | |||||
824 | $defaultSortPropertyDirection = new PropertyGenerator('defaultDirection', $defaultSort && $defaultSortDirection ? $defaultSortDirection : 'asc'); |
||||
825 | $defaultSortPropertyDirection->setDocBlock(new DocBlockGenerator('The default sort direction.', null, [new VarTag(null, ['string'])])); |
||||
826 | $class->addPropertyFromGenerator($defaultSortPropertyDirection); |
||||
827 | |||||
828 | $constructorMethod = new MethodGenerator( |
||||
829 | '__construct', |
||||
830 | [ new ParameterGenerator('tdbmService', TDBMService::class) ], |
||||
831 | MethodGenerator::FLAG_PUBLIC, |
||||
832 | '$this->tdbmService = $tdbmService;', |
||||
833 | 'Sets the TDBM service used by this DAO.' |
||||
834 | ); |
||||
835 | $constructorMethod = $this->codeGeneratorListener->onBaseDaoConstructorGenerated($constructorMethod, $this, $this->configuration, $class); |
||||
836 | if ($constructorMethod !== null) { |
||||
837 | $class->addMethodFromGenerator($constructorMethod); |
||||
838 | } |
||||
839 | |||||
840 | $saveMethod = new MethodGenerator( |
||||
841 | 'save', |
||||
842 | [ new ParameterGenerator('obj', $beanClassName) ], |
||||
843 | MethodGenerator::FLAG_PUBLIC, |
||||
844 | '$this->tdbmService->save($obj);', |
||||
845 | (new DocBlockGenerator( |
||||
846 | "Persist the $beanClassWithoutNameSpace instance.", |
||||
847 | null, |
||||
848 | [ |
||||
849 | new ParamTag('obj', [$beanClassWithoutNameSpace], 'The bean to save.') |
||||
850 | ] |
||||
851 | ))->setWordWrap(false) |
||||
852 | ); |
||||
853 | $saveMethod->setReturnType('void'); |
||||
854 | |||||
855 | $saveMethod = $this->codeGeneratorListener->onBaseDaoSaveGenerated($saveMethod, $this, $this->configuration, $class); |
||||
856 | if ($saveMethod !== null) { |
||||
857 | $class->addMethodFromGenerator($saveMethod); |
||||
858 | } |
||||
859 | |||||
860 | $findAllBody = <<<EOF |
||||
861 | if (\$this->defaultSort) { |
||||
862 | \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection; |
||||
863 | } else { |
||||
864 | \$orderBy = null; |
||||
865 | } |
||||
866 | return \$this->tdbmService->findObjects('$tableName', null, [], \$orderBy, [], null, \\$beanClassName::class, \\$resultIteratorClass::class); |
||||
867 | EOF; |
||||
868 | |||||
869 | $findAllMethod = new MethodGenerator( |
||||
870 | 'findAll', |
||||
871 | [], |
||||
872 | MethodGenerator::FLAG_PUBLIC, |
||||
873 | $findAllBody, |
||||
874 | (new DocBlockGenerator("Get all $beanClassWithoutNameSpace records."))->setWordWrap(false) |
||||
875 | ); |
||||
876 | $findAllMethod->setReturnType($resultIteratorClass); |
||||
877 | $findAllMethod = $this->codeGeneratorListener->onBaseDaoFindAllGenerated($findAllMethod, $this, $this->configuration, $class); |
||||
878 | if ($findAllMethod !== null) { |
||||
879 | $class->addMethodFromGenerator($findAllMethod); |
||||
880 | } |
||||
881 | |||||
882 | if (count($primaryKeyColumns) > 0) { |
||||
883 | $lazyLoadingParameterName = 'lazyLoading'; |
||||
884 | $parameters = []; |
||||
885 | $parametersTag = []; |
||||
886 | $primaryKeyFilter = []; |
||||
887 | |||||
888 | foreach ($primaryKeyColumns as $primaryKeyColumn) { |
||||
889 | if ($primaryKeyColumn === $lazyLoadingParameterName) { |
||||
890 | throw new TDBMException('Primary Column name `' . $lazyLoadingParameterName . '` is not allowed.'); |
||||
891 | } |
||||
892 | $phpType = TDBMDaoGenerator::dbalTypeToPhpType($this->table->getColumn($primaryKeyColumn)->getType()); |
||||
893 | $parameters[] = new ParameterGenerator($primaryKeyColumn, $phpType); |
||||
894 | $parametersTag[] = new ParamTag($primaryKeyColumn, [$phpType]); |
||||
895 | $primaryKeyFilter[] = "'$primaryKeyColumn' => \$$primaryKeyColumn"; |
||||
896 | } |
||||
897 | $parameters[] = new ParameterGenerator($lazyLoadingParameterName, 'bool', false); |
||||
898 | $parametersTag[] = new ParamTag($lazyLoadingParameterName, ['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.'); |
||||
899 | $parametersTag[] = new ReturnTag(['\\'.$beanClassName]); |
||||
900 | $parametersTag[] = new ThrowsTag('\\'.TDBMException::class); |
||||
901 | |||||
902 | $getByIdMethod = new MethodGenerator( |
||||
903 | 'getById', |
||||
904 | $parameters, |
||||
905 | MethodGenerator::FLAG_PUBLIC, |
||||
906 | "return \$this->tdbmService->findObjectByPk('$tableName', [" . implode(', ', $primaryKeyFilter) . "], [], \$$lazyLoadingParameterName, \\$beanClassName::class, \\$resultIteratorClass::class);", |
||||
907 | (new DocBlockGenerator( |
||||
908 | "Get $beanClassWithoutNameSpace specified by its ID (its primary key).", |
||||
909 | 'If the primary key does not exist, an exception is thrown.', |
||||
910 | $parametersTag |
||||
911 | ))->setWordWrap(false) |
||||
912 | ); |
||||
913 | $getByIdMethod->setReturnType($beanClassName); |
||||
914 | $getByIdMethod = $this->codeGeneratorListener->onBaseDaoGetByIdGenerated($getByIdMethod, $this, $this->configuration, $class); |
||||
915 | if ($getByIdMethod) { |
||||
916 | $class->addMethodFromGenerator($getByIdMethod); |
||||
917 | } |
||||
918 | } |
||||
919 | |||||
920 | $deleteMethodBody = <<<EOF |
||||
921 | if (\$cascade === true) { |
||||
922 | \$this->tdbmService->deleteCascade(\$obj); |
||||
923 | } else { |
||||
924 | \$this->tdbmService->delete(\$obj); |
||||
925 | } |
||||
926 | EOF; |
||||
927 | |||||
928 | |||||
929 | $deleteMethod = new MethodGenerator( |
||||
930 | 'delete', |
||||
931 | [ |
||||
932 | new ParameterGenerator('obj', $beanClassName), |
||||
933 | new ParameterGenerator('cascade', 'bool', false) |
||||
934 | ], |
||||
935 | MethodGenerator::FLAG_PUBLIC, |
||||
936 | $deleteMethodBody, |
||||
937 | (new DocBlockGenerator( |
||||
938 | "Get all $beanClassWithoutNameSpace records.", |
||||
939 | null, |
||||
940 | [ |
||||
941 | new ParamTag('obj', ['\\'.$beanClassName], 'The object to delete'), |
||||
942 | new ParamTag('cascade', ['bool'], 'If true, it will delete all objects linked to $obj'), |
||||
943 | ] |
||||
944 | ))->setWordWrap(false) |
||||
945 | ); |
||||
946 | $deleteMethod->setReturnType('void'); |
||||
947 | $deleteMethod = $this->codeGeneratorListener->onBaseDaoDeleteGenerated($deleteMethod, $this, $this->configuration, $class); |
||||
948 | if ($deleteMethod !== null) { |
||||
949 | $class->addMethodFromGenerator($deleteMethod); |
||||
950 | } |
||||
951 | |||||
952 | $findMethodBody = <<<EOF |
||||
953 | if (\$this->defaultSort && \$orderBy == null) { |
||||
954 | \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection; |
||||
955 | } |
||||
956 | return \$this->tdbmService->findObjects('$tableName', \$filter, \$parameters, \$orderBy, \$additionalTablesFetch, \$mode, \\$beanClassName::class, \\$resultIteratorClass::class); |
||||
957 | EOF; |
||||
958 | |||||
959 | |||||
960 | $findMethod = new MethodGenerator( |
||||
961 | 'find', |
||||
962 | [ |
||||
963 | (new ParameterGenerator('filter'))->setDefaultValue(null), |
||||
964 | new ParameterGenerator('parameters', 'array', []), |
||||
965 | (new ParameterGenerator('orderBy'))->setDefaultValue(null), |
||||
966 | new ParameterGenerator('additionalTablesFetch', 'array', []), |
||||
967 | (new ParameterGenerator('mode', '?int'))->setDefaultValue(null), |
||||
968 | ], |
||||
969 | MethodGenerator::FLAG_PROTECTED, |
||||
970 | $findMethodBody, |
||||
971 | (new DocBlockGenerator( |
||||
972 | "Get all $beanClassWithoutNameSpace records.", |
||||
973 | null, |
||||
974 | [ |
||||
975 | new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'), |
||||
976 | new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'), |
||||
977 | new ParamTag('orderBy', ['mixed'], 'The order string'), |
||||
978 | new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'), |
||||
979 | new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.') |
||||
980 | ] |
||||
981 | ))->setWordWrap(false) |
||||
982 | ); |
||||
983 | $findMethod->setReturnType($resultIteratorClass); |
||||
984 | $findMethod = $this->codeGeneratorListener->onBaseDaoFindGenerated($findMethod, $this, $this->configuration, $class); |
||||
985 | if ($findMethod !== null) { |
||||
986 | $class->addMethodFromGenerator($findMethod); |
||||
987 | } |
||||
988 | |||||
989 | $findFromSqlMethodBody = <<<EOF |
||||
990 | if (\$this->defaultSort && \$orderBy == null) { |
||||
991 | \$orderBy = '$tableName.'.\$this->defaultSort.' '.\$this->defaultDirection; |
||||
992 | } |
||||
993 | return \$this->tdbmService->findObjectsFromSql('$tableName', \$from, \$filter, \$parameters, \$orderBy, \$mode, \\$beanClassName::class, \\$resultIteratorClass::class); |
||||
994 | EOF; |
||||
995 | |||||
996 | $findFromSqlMethod = new MethodGenerator( |
||||
997 | 'findFromSql', |
||||
998 | [ |
||||
999 | new ParameterGenerator('from', 'string'), |
||||
1000 | (new ParameterGenerator('filter'))->setDefaultValue(null), |
||||
1001 | new ParameterGenerator('parameters', 'array', []), |
||||
1002 | (new ParameterGenerator('orderBy'))->setDefaultValue(null), |
||||
1003 | new ParameterGenerator('additionalTablesFetch', 'array', []), |
||||
1004 | (new ParameterGenerator('mode', '?int'))->setDefaultValue(null), |
||||
1005 | ], |
||||
1006 | MethodGenerator::FLAG_PROTECTED, |
||||
1007 | $findFromSqlMethodBody, |
||||
1008 | (new DocBlockGenerator( |
||||
1009 | "Get a list of $beanClassWithoutNameSpace specified by its filters.", |
||||
1010 | "Unlike the `find` method that guesses the FROM part of the statement, here you can pass the \$from part. |
||||
1011 | |||||
1012 | You should not put an alias on the main table name. So your \$from variable should look like: |
||||
1013 | |||||
1014 | \"$tableName JOIN ... ON ...\"", |
||||
1015 | [ |
||||
1016 | new ParamTag('from', ['string'], 'The sql from statement'), |
||||
1017 | new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'), |
||||
1018 | new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'), |
||||
1019 | new ParamTag('orderBy', ['mixed'], 'The order string'), |
||||
1020 | new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'), |
||||
1021 | new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.') |
||||
1022 | ] |
||||
1023 | ))->setWordWrap(false) |
||||
1024 | ); |
||||
1025 | $findFromSqlMethod->setReturnType($resultIteratorClass); |
||||
1026 | $findFromSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromSqlGenerated($findFromSqlMethod, $this, $this->configuration, $class); |
||||
1027 | if ($findFromSqlMethod !== null) { |
||||
1028 | $class->addMethodFromGenerator($findFromSqlMethod); |
||||
1029 | } |
||||
1030 | |||||
1031 | $findFromRawSqlMethodBody = <<<EOF |
||||
1032 | return \$this->tdbmService->findObjectsFromRawSql('$tableName', \$sql, \$parameters, \$mode, \\$beanClassName::class, \$countSql, \\$resultIteratorClass::class); |
||||
1033 | EOF; |
||||
1034 | |||||
1035 | $findFromRawSqlMethod = new MethodGenerator( |
||||
1036 | 'findFromRawSql', |
||||
1037 | [ |
||||
1038 | new ParameterGenerator('sql', 'string'), |
||||
1039 | new ParameterGenerator('parameters', 'array', []), |
||||
1040 | (new ParameterGenerator('countSql', '?string'))->setDefaultValue(null), |
||||
1041 | (new ParameterGenerator('mode', '?int'))->setDefaultValue(null), |
||||
1042 | ], |
||||
1043 | MethodGenerator::FLAG_PROTECTED, |
||||
1044 | $findFromRawSqlMethodBody, |
||||
1045 | (new DocBlockGenerator( |
||||
1046 | "Get a list of $beanClassWithoutNameSpace from a SQL query.", |
||||
1047 | "Unlike the `find` and `findFromSql` methods, here you can pass the whole \$sql query. |
||||
1048 | |||||
1049 | 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: |
||||
1050 | |||||
1051 | \"SELECT $tableName .* FROM ...\"", |
||||
1052 | [ |
||||
1053 | new ParamTag('sql', ['string'], 'The sql query'), |
||||
1054 | new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the query'), |
||||
1055 | new ParamTag('countSql', ['string', 'null'], 'The sql query that provides total count of rows (automatically computed if not provided)'), |
||||
1056 | new ParamTag('mode', ['int', 'null'], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.') |
||||
1057 | ] |
||||
1058 | ))->setWordWrap(false) |
||||
1059 | ); |
||||
1060 | $findFromRawSqlMethod->setReturnType($resultIteratorClass); |
||||
1061 | $findFromRawSqlMethod = $this->codeGeneratorListener->onBaseDaoFindFromRawSqlGenerated($findFromRawSqlMethod, $this, $this->configuration, $class); |
||||
1062 | if ($findFromRawSqlMethod !== null) { |
||||
1063 | $class->addMethodFromGenerator($findFromRawSqlMethod); |
||||
1064 | } |
||||
1065 | |||||
1066 | $findOneMethodBody = <<<EOF |
||||
1067 | return \$this->tdbmService->findObject('$tableName', \$filter, \$parameters, \$additionalTablesFetch, \\$beanClassName::class, \\$resultIteratorClass::class); |
||||
1068 | EOF; |
||||
1069 | |||||
1070 | |||||
1071 | $findOneMethod = new MethodGenerator( |
||||
1072 | 'findOne', |
||||
1073 | [ |
||||
1074 | (new ParameterGenerator('filter'))->setDefaultValue(null), |
||||
1075 | new ParameterGenerator('parameters', 'array', []), |
||||
1076 | new ParameterGenerator('additionalTablesFetch', 'array', []), |
||||
1077 | ], |
||||
1078 | MethodGenerator::FLAG_PROTECTED, |
||||
1079 | $findOneMethodBody, |
||||
1080 | (new DocBlockGenerator( |
||||
1081 | "Get a single $beanClassWithoutNameSpace specified by its filters.", |
||||
1082 | null, |
||||
1083 | [ |
||||
1084 | new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'), |
||||
1085 | new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'), |
||||
1086 | new ParamTag('additionalTablesFetch', ['string[]'], 'A list of additional tables to fetch (for performance improvement)'), |
||||
1087 | new ReturnTag(['\\'.$beanClassName, 'null']) |
||||
1088 | ] |
||||
1089 | ))->setWordWrap(false) |
||||
1090 | ); |
||||
1091 | $findOneMethod->setReturnType("?$beanClassName"); |
||||
1092 | $findOneMethod = $this->codeGeneratorListener->onBaseDaoFindOneGenerated($findOneMethod, $this, $this->configuration, $class); |
||||
1093 | if ($findOneMethod !== null) { |
||||
1094 | $class->addMethodFromGenerator($findOneMethod); |
||||
1095 | } |
||||
1096 | |||||
1097 | $findOneFromSqlMethodBody = <<<EOF |
||||
1098 | return \$this->tdbmService->findObjectFromSql('$tableName', \$from, \$filter, \$parameters, \\$beanClassName::class, \\$resultIteratorClass::class); |
||||
1099 | EOF; |
||||
1100 | |||||
1101 | $findOneFromSqlMethod = new MethodGenerator( |
||||
1102 | 'findOneFromSql', |
||||
1103 | [ |
||||
1104 | new ParameterGenerator('from', 'string'), |
||||
1105 | (new ParameterGenerator('filter'))->setDefaultValue(null), |
||||
1106 | new ParameterGenerator('parameters', 'array', []), |
||||
1107 | ], |
||||
1108 | MethodGenerator::FLAG_PROTECTED, |
||||
1109 | $findOneFromSqlMethodBody, |
||||
1110 | (new DocBlockGenerator( |
||||
1111 | "Get a single $beanClassWithoutNameSpace specified by its filters.", |
||||
1112 | "Unlike the `findOne` method that guesses the FROM part of the statement, here you can pass the \$from part. |
||||
1113 | |||||
1114 | You should not put an alias on the main table name. So your \$from variable should look like: |
||||
1115 | |||||
1116 | \"$tableName JOIN ... ON ...\"", |
||||
1117 | [ |
||||
1118 | new ParamTag('from', ['string'], 'The sql from statement'), |
||||
1119 | new ParamTag('filter', ['mixed'], 'The filter bag (see TDBMService::findObjects for complete description)'), |
||||
1120 | new ParamTag('parameters', ['mixed[]'], 'The parameters associated with the filter'), |
||||
1121 | new ReturnTag(['\\'.$beanClassName, 'null']) |
||||
1122 | ] |
||||
1123 | ))->setWordWrap(false) |
||||
1124 | ); |
||||
1125 | $findOneFromSqlMethod->setReturnType("?$beanClassName"); |
||||
1126 | $findOneFromSqlMethod = $this->codeGeneratorListener->onBaseDaoFindOneFromSqlGenerated($findOneFromSqlMethod, $this, $this->configuration, $class); |
||||
1127 | if ($findOneFromSqlMethod !== null) { |
||||
1128 | $class->addMethodFromGenerator($findOneFromSqlMethod); |
||||
1129 | } |
||||
1130 | |||||
1131 | |||||
1132 | $setDefaultSortMethod = new MethodGenerator( |
||||
1133 | 'setDefaultSort', |
||||
1134 | [ |
||||
1135 | new ParameterGenerator('defaultSort', 'string'), |
||||
1136 | ], |
||||
1137 | MethodGenerator::FLAG_PUBLIC, |
||||
1138 | '$this->defaultSort = $defaultSort;', |
||||
1139 | new DocBlockGenerator( |
||||
1140 | "Sets the default column for default sorting.", |
||||
1141 | null, |
||||
1142 | [ |
||||
1143 | new ParamTag('defaultSort', ['string']), |
||||
1144 | ] |
||||
1145 | ) |
||||
1146 | ); |
||||
1147 | $setDefaultSortMethod->setReturnType('void'); |
||||
1148 | $setDefaultSortMethod = $this->codeGeneratorListener->onBaseDaoSetDefaultSortGenerated($setDefaultSortMethod, $this, $this->configuration, $class); |
||||
1149 | if ($setDefaultSortMethod !== null) { |
||||
1150 | $class->addMethodFromGenerator($setDefaultSortMethod); |
||||
1151 | } |
||||
1152 | |||||
1153 | foreach ($findByDaoCodeMethods as $method) { |
||||
1154 | $class->addMethodFromGenerator($method); |
||||
1155 | } |
||||
1156 | |||||
1157 | $file = $this->codeGeneratorListener->onBaseDaoGenerated($file, $this, $this->configuration); |
||||
1158 | |||||
1159 | return $file; |
||||
1160 | } |
||||
1161 | |||||
1162 | /** |
||||
1163 | * Writes the representation of the PHP ResultIterator file. |
||||
1164 | */ |
||||
1165 | public function generateResultIteratorPhpCode(): ?FileGenerator |
||||
1166 | { |
||||
1167 | $file = new FileGenerator(); |
||||
1168 | $class = new ClassGenerator(); |
||||
1169 | $class->setAbstract(true); |
||||
1170 | $file->setClass($class); |
||||
1171 | $file->setNamespace($this->generatedResultIteratorNamespace); |
||||
1172 | |||||
1173 | $tableName = $this->table->getName(); |
||||
1174 | |||||
1175 | $classNameWithoutNamespace = $this->namingStrategy->getResultIteratorClassName($tableName); |
||||
1176 | $className = $this->namingStrategy->getResultIteratorClassName($tableName); |
||||
0 ignored issues
–
show
|
|||||
1177 | $baseClassName = $this->namingStrategy->getBaseResultIteratorClassName($tableName); |
||||
1178 | $beanClassWithoutNameSpace = $this->namingStrategy->getBeanClassName($tableName); |
||||
1179 | $beanClassName = $this->beanNamespace.'\\'.$beanClassWithoutNameSpace; |
||||
1180 | |||||
1181 | $file->setDocBlock(new DocBlockGenerator( |
||||
1182 | <<<EOF |
||||
1183 | This file has been automatically generated by TDBM. |
||||
1184 | DO NOT edit this file, as it might be overwritten. |
||||
1185 | If you need to perform changes, edit the $classNameWithoutNamespace class instead! |
||||
1186 | EOF |
||||
1187 | )); |
||||
1188 | $class->setName($baseClassName); |
||||
1189 | $extends = $this->getExtendedResultIteratorClassName(); |
||||
1190 | if ($extends === null) { |
||||
1191 | $class->addUse(ResultIterator::class); |
||||
1192 | $class->setExtendedClass(ResultIterator::class); |
||||
1193 | } else { |
||||
1194 | $class->addUse($this->resultIteratorNamespace . '\\' . $extends); |
||||
1195 | /** @var class-string $extends */ |
||||
1196 | $class->setExtendedClass($extends); |
||||
1197 | } |
||||
1198 | |||||
1199 | $class->setDocBlock((new DocBlockGenerator( |
||||
1200 | "The $baseClassName class will iterate over results of $beanClassWithoutNameSpace class.", |
||||
1201 | null, |
||||
1202 | [new Tag\MethodTag('getIterator', ['\\' . $beanClassName . '[]']), new Tag\MethodTag('toArray', ['\\' . $beanClassName . '[]'])] |
||||
1203 | ))->setWordWrap(false)); |
||||
1204 | |||||
1205 | $file = $this->codeGeneratorListener->onBaseResultIteratorGenerated($file, $this, $this->configuration); |
||||
1206 | |||||
1207 | return $file; |
||||
1208 | } |
||||
1209 | |||||
1210 | /** |
||||
1211 | * Tries to find a @defaultSort annotation in one of the columns. |
||||
1212 | * |
||||
1213 | * @param Table $table |
||||
1214 | * |
||||
1215 | * @return mixed[] First item: column name, Second item: column order (asc/desc) |
||||
1216 | */ |
||||
1217 | private function getDefaultSortColumnFromAnnotation(Table $table): array |
||||
1218 | { |
||||
1219 | $defaultSort = null; |
||||
1220 | $defaultSortDirection = null; |
||||
1221 | foreach ($table->getColumns() as $column) { |
||||
1222 | $comments = $column->getComment(); |
||||
1223 | $matches = []; |
||||
1224 | if ($comments !== null && preg_match('/@defaultSort(\((desc|asc)\))*/', $comments, $matches) != 0) { |
||||
1225 | $defaultSort = $column->getName(); |
||||
1226 | if (count($matches) === 3) { |
||||
1227 | $defaultSortDirection = $matches[2]; |
||||
1228 | } else { |
||||
1229 | $defaultSortDirection = 'ASC'; |
||||
1230 | } |
||||
1231 | } |
||||
1232 | } |
||||
1233 | |||||
1234 | return [$defaultSort, $defaultSortDirection]; |
||||
1235 | } |
||||
1236 | |||||
1237 | /** |
||||
1238 | * @param string $beanNamespace |
||||
1239 | * @param string $beanClassName |
||||
1240 | * |
||||
1241 | * @return MethodGenerator[] |
||||
1242 | */ |
||||
1243 | private function generateFindByDaoCode(string $beanNamespace, string $beanClassName, ClassGenerator $class): array |
||||
1244 | { |
||||
1245 | $methods = []; |
||||
1246 | foreach ($this->removeDuplicateIndexes($this->table->getIndexes()) as $index) { |
||||
1247 | if (!$index->isPrimary()) { |
||||
1248 | $method = $this->generateFindByDaoCodeForIndex($index, $beanNamespace, $beanClassName); |
||||
1249 | |||||
1250 | if ($method !== null) { |
||||
1251 | $method = $this->codeGeneratorListener->onBaseDaoFindByIndexGenerated($method, $index, $this, $this->configuration, $class); |
||||
1252 | if ($method !== null) { |
||||
1253 | $methods[] = $method; |
||||
1254 | } |
||||
1255 | } |
||||
1256 | } |
||||
1257 | } |
||||
1258 | usort($methods, static function (MethodGenerator $methodA, MethodGenerator $methodB) { |
||||
1259 | return $methodA->getName() <=> $methodB->getName(); |
||||
1260 | }); |
||||
1261 | |||||
1262 | return $methods; |
||||
1263 | } |
||||
1264 | |||||
1265 | /** |
||||
1266 | * Remove identical indexes (indexes on same columns) |
||||
1267 | * |
||||
1268 | * @param Index[] $indexes |
||||
1269 | * @return Index[] |
||||
1270 | */ |
||||
1271 | private function removeDuplicateIndexes(array $indexes): array |
||||
1272 | { |
||||
1273 | $indexesByKey = []; |
||||
1274 | foreach ($indexes as $index) { |
||||
1275 | $key = implode('__`__', $index->getUnquotedColumns()); |
||||
1276 | // Unique Index have precedence over non unique one |
||||
1277 | if (!isset($indexesByKey[$key]) || $index->isUnique()) { |
||||
1278 | $indexesByKey[$key] = $index; |
||||
1279 | } |
||||
1280 | } |
||||
1281 | |||||
1282 | return array_values($indexesByKey); |
||||
1283 | } |
||||
1284 | |||||
1285 | /** |
||||
1286 | * @param Index $index |
||||
1287 | * @param string $beanNamespace |
||||
1288 | * @param string $beanClassName |
||||
1289 | * |
||||
1290 | * @return MethodGenerator|null |
||||
1291 | */ |
||||
1292 | private function generateFindByDaoCodeForIndex(Index $index, string $beanNamespace, string $beanClassName): ?MethodGenerator |
||||
1293 | { |
||||
1294 | $columns = $index->getColumns(); |
||||
1295 | $usedBeans = []; |
||||
1296 | |||||
1297 | /** |
||||
1298 | * The list of elements building this index (expressed as columns or foreign keys) |
||||
1299 | * @var AbstractBeanPropertyDescriptor[] |
||||
1300 | */ |
||||
1301 | $elements = []; |
||||
1302 | |||||
1303 | foreach ($columns as $column) { |
||||
1304 | $fk = $this->isPartOfForeignKey($this->table, $this->table->getColumn($column)); |
||||
1305 | if ($fk !== null) { |
||||
1306 | if (!isset($elements[$fk->getName()])) { |
||||
1307 | $elements[$fk->getName()] = new ObjectBeanPropertyDescriptor($this->table, $fk, $this->namingStrategy, $this->beanNamespace, $this->annotationParser, $this->registry->getBeanForTableName($fk->getForeignTableName()), $this->resultIteratorNamespace); |
||||
1308 | } |
||||
1309 | } else { |
||||
1310 | $elements[] = new ScalarBeanPropertyDescriptor($this->table, $this->table->getColumn($column), $this->namingStrategy, $this->annotationParser); |
||||
1311 | } |
||||
1312 | } |
||||
1313 | $elements = array_values($elements); |
||||
1314 | |||||
1315 | // If the index is actually only a foreign key, let's bypass it entirely. |
||||
1316 | if (count($elements) === 1 && $elements[0] instanceof ObjectBeanPropertyDescriptor) { |
||||
1317 | return null; |
||||
1318 | } |
||||
1319 | |||||
1320 | $parameters = []; |
||||
1321 | //$functionParameters = []; |
||||
1322 | $first = true; |
||||
1323 | /** @var AbstractBeanPropertyDescriptor $element */ |
||||
1324 | foreach ($elements as $element) { |
||||
1325 | $parameter = new ParameterGenerator(ltrim($element->getSafeVariableName(), '$')); |
||||
1326 | if (!$first && !($element->isCompulsory() && $index->isUnique())) { |
||||
1327 | $parameterType = '?'; |
||||
1328 | //$functionParameter = '?'; |
||||
1329 | } else { |
||||
1330 | $parameterType = ''; |
||||
1331 | //$functionParameter = ''; |
||||
1332 | } |
||||
1333 | $parameterType .= $element->getPhpType(); |
||||
1334 | $parameter->setType($parameterType); |
||||
1335 | if (!$first && !($element->isCompulsory() && $index->isUnique())) { |
||||
1336 | $parameter->setDefaultValue(null); |
||||
1337 | } |
||||
1338 | //$functionParameter .= $element->getPhpType(); |
||||
1339 | $elementClassName = $element->getClassName(); |
||||
1340 | if ($elementClassName) { |
||||
1341 | $usedBeans[] = $beanNamespace.'\\'.$elementClassName; |
||||
1342 | } |
||||
1343 | //$functionParameter .= ' '.$element->getVariableName(); |
||||
1344 | if ($first) { |
||||
1345 | $first = false; |
||||
1346 | } /*else { |
||||
1347 | $functionParameter .= ' = null'; |
||||
1348 | }*/ |
||||
1349 | //$functionParameters[] = $functionParameter; |
||||
1350 | $parameters[] = $parameter; |
||||
1351 | } |
||||
1352 | |||||
1353 | //$functionParametersString = implode(', ', $functionParameters); |
||||
1354 | |||||
1355 | $count = 0; |
||||
1356 | |||||
1357 | $params = []; |
||||
1358 | $filterArrayCode = ''; |
||||
1359 | $commentArguments = []; |
||||
1360 | $first = true; |
||||
1361 | foreach ($elements as $element) { |
||||
1362 | $params[] = $element->getParamAnnotation(); |
||||
1363 | if ($element instanceof ScalarBeanPropertyDescriptor) { |
||||
1364 | $typeName = $element->getDatabaseType()->getName(); |
||||
0 ignored issues
–
show
The function
Doctrine\DBAL\Types\Type::getName() has been deprecated: this method will be removed in Doctrine DBAL 4.0, use {@see TypeRegistry::lookupName()} instead.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This function has been deprecated. The supplier of the function has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead. ![]() |
|||||
1365 | if ($typeName === Types::DATETIME_IMMUTABLE) { |
||||
1366 | $filterArrayCode .= sprintf( |
||||
1367 | " %s => \$this->tdbmService->getConnection()->convertToDatabaseValue(%s, %s),\n", |
||||
1368 | var_export($element->getColumnName(), true), |
||||
1369 | $element->getSafeVariableName(), |
||||
1370 | var_export($typeName, true) |
||||
1371 | ); |
||||
1372 | } else { |
||||
1373 | $filterArrayCode .= ' '.var_export($element->getColumnName(), true).' => '.$element->getSafeVariableName().",\n"; |
||||
1374 | } |
||||
1375 | } elseif ($element instanceof ObjectBeanPropertyDescriptor) { |
||||
1376 | $foreignKey = $element->getForeignKey(); |
||||
1377 | $columns = array_combine($foreignKey->getUnquotedLocalColumns(), $foreignKey->getUnquotedForeignColumns()); |
||||
1378 | ++$count; |
||||
1379 | $foreignTable = $this->schema->getTable($foreignKey->getForeignTableName()); |
||||
1380 | foreach ($columns as $localColumn => $foreignColumn) { |
||||
1381 | // 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. |
||||
1382 | $targetedElement = new ScalarBeanPropertyDescriptor($foreignTable, $foreignTable->getColumn($foreignColumn), $this->namingStrategy, $this->annotationParser); |
||||
1383 | if ($first || $element->isCompulsory() && $index->isUnique()) { |
||||
1384 | // First parameter for index is not nullable |
||||
1385 | $filterArrayCode .= ' '.var_export($localColumn, true).' => '.$element->getSafeVariableName().'->'.$targetedElement->getGetterName()."(),\n"; |
||||
1386 | } else { |
||||
1387 | // Other parameters for index is not nullable |
||||
1388 | $filterArrayCode .= ' '.var_export($localColumn, true).' => ('.$element->getSafeVariableName().' !== null) ? '.$element->getSafeVariableName().'->'.$targetedElement->getGetterName()."() : null,\n"; |
||||
1389 | } |
||||
1390 | } |
||||
1391 | } |
||||
1392 | $commentArguments[] = substr($element->getSafeVariableName(), 1); |
||||
1393 | if ($first) { |
||||
1394 | $first = false; |
||||
1395 | } |
||||
1396 | } |
||||
1397 | |||||
1398 | //$paramsString = implode("\n", $params); |
||||
1399 | |||||
1400 | |||||
1401 | $methodName = $this->namingStrategy->getFindByIndexMethodName($index, $elements); |
||||
1402 | |||||
1403 | $method = new MethodGenerator($methodName); |
||||
1404 | |||||
1405 | if ($index->isUnique()) { |
||||
1406 | $parameters[] = new ParameterGenerator('additionalTablesFetch', 'array', []); |
||||
1407 | $params[] = new ParamTag('additionalTablesFetch', [ 'string[]' ], 'A list of additional tables to fetch (for performance improvement)'); |
||||
1408 | $params[] = new ReturnTag([ '\\'.$beanNamespace.'\\'.$beanClassName, 'null' ]); |
||||
1409 | $method->setReturnType('?\\'.$beanNamespace.'\\'.$beanClassName); |
||||
1410 | |||||
1411 | $docBlock = new DocBlockGenerator("Get a $beanClassName filtered by ".implode(', ', $commentArguments). '.', null, $params); |
||||
1412 | $docBlock->setWordWrap(false); |
||||
1413 | |||||
1414 | $body = "\$filter = [ |
||||
1415 | ".$filterArrayCode."]; |
||||
1416 | return \$this->findOne(\$filter, [], \$additionalTablesFetch); |
||||
1417 | "; |
||||
1418 | } else { |
||||
1419 | $parameters[] = (new ParameterGenerator('orderBy'))->setDefaultValue(null); |
||||
1420 | $params[] = new ParamTag('orderBy', [ 'mixed' ], 'The order string'); |
||||
1421 | $parameters[] = new ParameterGenerator('additionalTablesFetch', 'array', []); |
||||
1422 | $params[] = new ParamTag('additionalTablesFetch', [ 'string[]' ], 'A list of additional tables to fetch (for performance improvement)'); |
||||
1423 | $parameters[] = (new ParameterGenerator('mode', '?int'))->setDefaultValue(null); |
||||
1424 | $params[] = new ParamTag('mode', [ 'int', 'null' ], 'Either TDBMService::MODE_ARRAY or TDBMService::MODE_CURSOR (for large datasets). Defaults to TDBMService::MODE_ARRAY.'); |
||||
1425 | $method->setReturnType($this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName()); |
||||
1426 | |||||
1427 | $docBlock = new DocBlockGenerator("Get a list of $beanClassName filtered by ".implode(', ', $commentArguments).".", null, $params); |
||||
1428 | $docBlock->setWordWrap(false); |
||||
1429 | |||||
1430 | $body = "\$filter = [ |
||||
1431 | ".$filterArrayCode."]; |
||||
1432 | return \$this->find(\$filter, [], \$orderBy, \$additionalTablesFetch, \$mode); |
||||
1433 | "; |
||||
1434 | } |
||||
1435 | |||||
1436 | $method->setParameters($parameters); |
||||
1437 | $method->setDocBlock($docBlock); |
||||
1438 | $method->setBody($body); |
||||
1439 | |||||
1440 | return $method; |
||||
1441 | } |
||||
1442 | |||||
1443 | /** |
||||
1444 | * Generates the code for the getUsedTable protected method. |
||||
1445 | * |
||||
1446 | * @return MethodGenerator |
||||
1447 | */ |
||||
1448 | private function generateGetUsedTablesCode(): MethodGenerator |
||||
1449 | { |
||||
1450 | $hasParentRelationship = $this->schemaAnalyzer->getParentRelationship($this->table->getName()) !== null; |
||||
1451 | if ($hasParentRelationship) { |
||||
1452 | $code = sprintf('$tables = parent::getUsedTables(); |
||||
1453 | $tables[] = %s; |
||||
1454 | |||||
1455 | return $tables;', var_export($this->table->getName(), true)); |
||||
1456 | } else { |
||||
1457 | $code = sprintf(' return [ %s ];', var_export($this->table->getName(), true)); |
||||
1458 | } |
||||
1459 | |||||
1460 | $method = new MethodGenerator('getUsedTables'); |
||||
1461 | $method->setDocBlock(new DocBlockGenerator( |
||||
1462 | 'Returns an array of used tables by this bean (from parent to child relationship).', |
||||
1463 | null, |
||||
1464 | [new ReturnTag(['string[]'])] |
||||
1465 | )); |
||||
1466 | $method->setReturnType('array'); |
||||
1467 | $method->setBody($code); |
||||
1468 | |||||
1469 | return $method; |
||||
1470 | } |
||||
1471 | |||||
1472 | private function generateOnDeleteCode(): ?MethodGenerator |
||||
1473 | { |
||||
1474 | $setRefsToNullCode = ['parent::onDelete();']; |
||||
1475 | $relationships = $this->getPropertiesForTable($this->table); |
||||
1476 | foreach ($relationships as $relationship) { |
||||
1477 | if ($relationship instanceof ObjectBeanPropertyDescriptor) { |
||||
1478 | $tdbmFk = ForeignKey::createFromFk($relationship->getForeignKey()); |
||||
1479 | $foreignTableName = $tdbmFk->getForeignTableName(); |
||||
1480 | $setRefsToNullCode[] = sprintf( |
||||
1481 | '$this->setRef(%s, %s, %s, %s, %s);', |
||||
1482 | var_export($tdbmFk->getCacheKey(), true), |
||||
1483 | 'null', |
||||
1484 | var_export($this->table->getName(), true), |
||||
1485 | '\\' . $this->beanNamespace . '\\' . $this->namingStrategy->getBeanClassName($foreignTableName) . '::class', |
||||
1486 | '\\' . $this->resultIteratorNamespace . '\\' . $this->namingStrategy->getResultIteratorClassName($foreignTableName) . '::class' |
||||
1487 | ); |
||||
1488 | } |
||||
1489 | } |
||||
1490 | |||||
1491 | if (count($setRefsToNullCode) === 1) { |
||||
1492 | return null; |
||||
1493 | } |
||||
1494 | |||||
1495 | $method = new MethodGenerator('onDelete'); |
||||
1496 | $method->setDocBlock(new DocBlockGenerator('Method called when the bean is removed from database.')); |
||||
1497 | $method->setReturnType('void'); |
||||
1498 | $method->setBody(implode(PHP_EOL, $setRefsToNullCode)); |
||||
1499 | |||||
1500 | return $method; |
||||
1501 | } |
||||
1502 | |||||
1503 | /** |
||||
1504 | * @param PivotTableMethodsDescriptor[] $pivotTableMethodsDescriptors |
||||
1505 | * @return MethodGenerator |
||||
1506 | */ |
||||
1507 | private function generateGetManyToManyRelationshipDescriptorCode(array $pivotTableMethodsDescriptors): ?MethodGenerator |
||||
1508 | { |
||||
1509 | if (empty($pivotTableMethodsDescriptors)) { |
||||
1510 | return null; |
||||
1511 | } |
||||
1512 | |||||
1513 | $method = new MethodGenerator('_getManyToManyRelationshipDescriptor'); |
||||
1514 | $method->setVisibility(AbstractMemberGenerator::VISIBILITY_PUBLIC); |
||||
1515 | $method->setDocBlock(new DocBlockGenerator( |
||||
1516 | 'Get the paths used for many to many relationships methods.', |
||||
1517 | null, |
||||
1518 | [new GenericTag('internal')] |
||||
1519 | )); |
||||
1520 | $method->setReturnType(ManyToManyRelationshipPathDescriptor::class); |
||||
1521 | |||||
1522 | $parameter = new ParameterGenerator('pathKey'); |
||||
1523 | $parameter->setType('string'); |
||||
1524 | $method->setParameter($parameter); |
||||
1525 | |||||
1526 | $code = 'switch ($pathKey) {'."\n"; |
||||
1527 | foreach ($pivotTableMethodsDescriptors as $pivotTableMethodsDescriptor) { |
||||
1528 | $code .= ' case '.var_export($pivotTableMethodsDescriptor->getManyToManyRelationshipKey(), true).":\n"; |
||||
1529 | $code .= ' return '.$pivotTableMethodsDescriptor->getManyToManyRelationshipInstantiationCode().";\n"; |
||||
1530 | } |
||||
1531 | $code .= " default:\n"; |
||||
1532 | $code .= " return parent::_getManyToManyRelationshipDescriptor(\$pathKey);\n"; |
||||
1533 | $code .= "}\n"; |
||||
1534 | |||||
1535 | $method->setBody($code); |
||||
1536 | |||||
1537 | return $method; |
||||
1538 | } |
||||
1539 | |||||
1540 | /** |
||||
1541 | * @param PivotTableMethodsDescriptor[] $pivotTableMethodsDescriptors |
||||
1542 | * @return MethodGenerator |
||||
1543 | */ |
||||
1544 | private function generateGetManyToManyRelationshipDescriptorKeysCode(array $pivotTableMethodsDescriptors): ?MethodGenerator |
||||
1545 | { |
||||
1546 | if (empty($pivotTableMethodsDescriptors)) { |
||||
1547 | return null; |
||||
1548 | } |
||||
1549 | |||||
1550 | $method = new MethodGenerator('_getManyToManyRelationshipDescriptorKeys'); |
||||
1551 | $method->setVisibility(AbstractMemberGenerator::VISIBILITY_PUBLIC); |
||||
1552 | $method->setReturnType('array'); |
||||
1553 | $method->setDocBlock(new DocBlockGenerator( |
||||
1554 | 'Returns the list of keys supported for many to many relationships', |
||||
1555 | null, |
||||
1556 | [new GenericTag('internal'), new ReturnTag('string[]')] |
||||
1557 | )); |
||||
1558 | |||||
1559 | $keys = []; |
||||
1560 | foreach ($pivotTableMethodsDescriptors as $pivotTableMethodsDescriptor) { |
||||
1561 | $keys[] = var_export($pivotTableMethodsDescriptor->getManyToManyRelationshipKey(), true); |
||||
1562 | } |
||||
1563 | |||||
1564 | $code = 'return array_merge(parent::_getManyToManyRelationshipDescriptorKeys(), ['.implode(', ', $keys).']);'; |
||||
1565 | |||||
1566 | $method->setBody($code); |
||||
1567 | |||||
1568 | return $method; |
||||
1569 | } |
||||
1570 | |||||
1571 | /** |
||||
1572 | * @param PivotTableMethodsDescriptor[] $pivotTableMethodsDescriptors |
||||
1573 | * @return MethodGenerator |
||||
1574 | */ |
||||
1575 | private function generateCloneCode(array $pivotTableMethodsDescriptors): MethodGenerator |
||||
1576 | { |
||||
1577 | $precode = ''; |
||||
1578 | $postcode = ''; |
||||
1579 | |||||
1580 | foreach ($this->beanPropertyDescriptors as $beanPropertyDescriptor) { |
||||
1581 | $postcode .= $beanPropertyDescriptor->getCloneRule(); |
||||
1582 | } |
||||
1583 | |||||
1584 | //cloning many to many relationships |
||||
1585 | foreach ($pivotTableMethodsDescriptors as $beanMethodDescriptor) { |
||||
1586 | $precode .= $beanMethodDescriptor->getCloneRule()."\n"; |
||||
1587 | } |
||||
1588 | |||||
1589 | $method = new MethodGenerator('__clone'); |
||||
1590 | $method->setBody($precode."parent::__clone();\n".$postcode); |
||||
1591 | |||||
1592 | return $method; |
||||
1593 | } |
||||
1594 | |||||
1595 | /** |
||||
1596 | * Returns the bean class name (without the namespace). |
||||
1597 | * |
||||
1598 | * @return string |
||||
1599 | */ |
||||
1600 | public function getBeanClassName(): string |
||||
1601 | { |
||||
1602 | return $this->namingStrategy->getBeanClassName($this->table->getName()); |
||||
1603 | } |
||||
1604 | |||||
1605 | /** |
||||
1606 | * Returns the base bean class name (without the namespace). |
||||
1607 | * |
||||
1608 | * @return string |
||||
1609 | */ |
||||
1610 | public function getBaseBeanClassName(): string |
||||
1611 | { |
||||
1612 | return $this->namingStrategy->getBaseBeanClassName($this->table->getName()); |
||||
1613 | } |
||||
1614 | |||||
1615 | /** |
||||
1616 | * Returns the DAO class name (without the namespace). |
||||
1617 | * |
||||
1618 | * @return string |
||||
1619 | */ |
||||
1620 | public function getDaoClassName(): string |
||||
1621 | { |
||||
1622 | return $this->namingStrategy->getDaoClassName($this->table->getName()); |
||||
1623 | } |
||||
1624 | |||||
1625 | /** |
||||
1626 | * Returns the base DAO class name (without the namespace). |
||||
1627 | * |
||||
1628 | * @return string |
||||
1629 | */ |
||||
1630 | public function getBaseDaoClassName(): string |
||||
1631 | { |
||||
1632 | return $this->namingStrategy->getBaseDaoClassName($this->table->getName()); |
||||
1633 | } |
||||
1634 | |||||
1635 | /** |
||||
1636 | * Returns the ResultIterator class name (without the namespace). |
||||
1637 | * |
||||
1638 | * @return string |
||||
1639 | */ |
||||
1640 | public function getResultIteratorClassName(): string |
||||
1641 | { |
||||
1642 | return $this->namingStrategy->getResultIteratorClassName($this->table->getName()); |
||||
1643 | } |
||||
1644 | |||||
1645 | /** |
||||
1646 | * Returns the base ResultIterator class name (without the namespace). |
||||
1647 | * |
||||
1648 | * @return string |
||||
1649 | */ |
||||
1650 | public function getBaseResultIteratorClassName(): string |
||||
1651 | { |
||||
1652 | return $this->namingStrategy->getBaseResultIteratorClassName($this->table->getName()); |
||||
1653 | } |
||||
1654 | |||||
1655 | /** |
||||
1656 | * Returns the table used to build this bean. |
||||
1657 | * |
||||
1658 | * @return Table |
||||
1659 | */ |
||||
1660 | public function getTable(): Table |
||||
1661 | { |
||||
1662 | return $this->table; |
||||
1663 | } |
||||
1664 | |||||
1665 | /** |
||||
1666 | * Returns the extended bean class name (without the namespace), or null if the bean is not extended. |
||||
1667 | * |
||||
1668 | * @return string |
||||
1669 | */ |
||||
1670 | public function getExtendedBeanClassName(): ?string |
||||
1671 | { |
||||
1672 | $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName()); |
||||
1673 | if ($parentFk !== null) { |
||||
1674 | return $this->namingStrategy->getBeanClassName($parentFk->getForeignTableName()); |
||||
1675 | } |
||||
1676 | return null; |
||||
1677 | } |
||||
1678 | |||||
1679 | /** |
||||
1680 | * Returns the extended result iterator class name (without the namespace), or null if the result iterator is not extended. |
||||
1681 | */ |
||||
1682 | public function getExtendedResultIteratorClassName(): ?string |
||||
1683 | { |
||||
1684 | $parentFk = $this->schemaAnalyzer->getParentRelationship($this->table->getName()); |
||||
1685 | if ($parentFk !== null) { |
||||
1686 | return $this->namingStrategy->getResultIteratorClassName($parentFk->getForeignTableName()); |
||||
1687 | } |
||||
1688 | return null; |
||||
1689 | } |
||||
1690 | |||||
1691 | /** |
||||
1692 | * @return string |
||||
1693 | */ |
||||
1694 | public function getBeanNamespace(): string |
||||
1695 | { |
||||
1696 | return $this->beanNamespace; |
||||
1697 | } |
||||
1698 | |||||
1699 | /** |
||||
1700 | * @return string |
||||
1701 | */ |
||||
1702 | public function getGeneratedBeanNamespace(): string |
||||
1703 | { |
||||
1704 | return $this->generatedBeanNamespace; |
||||
1705 | } |
||||
1706 | |||||
1707 | /** |
||||
1708 | * @param ForeignKeyConstraint[] $fks |
||||
1709 | */ |
||||
1710 | private function generateGetForeignKeys(array $fks): MethodGenerator |
||||
1711 | { |
||||
1712 | $fkArray = []; |
||||
1713 | |||||
1714 | foreach ($fks as $fk) { |
||||
1715 | $tdbmFk = ForeignKey::createFromFk($fk); |
||||
1716 | $fkArray[$tdbmFk->getCacheKey()] = [ |
||||
1717 | ForeignKey::FOREIGN_TABLE => $fk->getForeignTableName(), |
||||
1718 | ForeignKey::LOCAL_COLUMNS => $fk->getUnquotedLocalColumns(), |
||||
1719 | ForeignKey::FOREIGN_COLUMNS => $fk->getUnquotedForeignColumns(), |
||||
1720 | ]; |
||||
1721 | } |
||||
1722 | |||||
1723 | ksort($fkArray); |
||||
1724 | foreach ($fkArray as $tableFks) { |
||||
1725 | ksort($tableFks); |
||||
1726 | } |
||||
1727 | |||||
1728 | $code = <<<EOF |
||||
1729 | if (\$tableName === %s) { |
||||
1730 | if (self::\$foreignKeys === null) { |
||||
1731 | self::\$foreignKeys = new ForeignKeys(%s); |
||||
1732 | } |
||||
1733 | return self::\$foreignKeys; |
||||
1734 | } |
||||
1735 | return parent::getForeignKeys(\$tableName); |
||||
1736 | EOF; |
||||
1737 | $code = sprintf($code, var_export($this->getTable()->getName(), true), $this->psr2VarExport($fkArray, ' ')); |
||||
1738 | |||||
1739 | $method = new MethodGenerator('getForeignKeys'); |
||||
1740 | $method->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED); |
||||
1741 | $method->setStatic(true); |
||||
1742 | $method->setDocBlock(new DocBlockGenerator('Internal method used to retrieve the list of foreign keys attached to this bean.')); |
||||
1743 | $method->setReturnType(ForeignKeys::class); |
||||
1744 | |||||
1745 | $parameter = new ParameterGenerator('tableName'); |
||||
1746 | $parameter->setType('string'); |
||||
1747 | $method->setParameter($parameter); |
||||
1748 | |||||
1749 | |||||
1750 | $method->setBody($code); |
||||
1751 | |||||
1752 | return $method; |
||||
1753 | } |
||||
1754 | |||||
1755 | /** |
||||
1756 | * @param mixed $var |
||||
1757 | * @param string $indent |
||||
1758 | * @return string |
||||
1759 | */ |
||||
1760 | private function psr2VarExport($var, string $indent = ''): string |
||||
1761 | { |
||||
1762 | if (is_array($var)) { |
||||
1763 | $indexed = array_keys($var) === range(0, count($var) - 1); |
||||
1764 | $r = []; |
||||
1765 | foreach ($var as $key => $value) { |
||||
1766 | $r[] = "$indent " |
||||
1767 | . ($indexed ? '' : $this->psr2VarExport($key) . ' => ') |
||||
1768 | . $this->psr2VarExport($value, "$indent "); |
||||
1769 | } |
||||
1770 | return "[\n" . implode(",\n", $r) . "\n" . $indent . ']'; |
||||
1771 | } |
||||
1772 | return var_export($var, true); |
||||
1773 | } |
||||
1774 | } |
||||
1775 |