PivotTableMethodsDescriptor::getFkAnnotations()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
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\ForeignKeyConstraint;
9
use Doctrine\DBAL\Schema\Table;
10
use Laminas\Code\Generator\DocBlockGenerator;
11
use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser;
12
use TheCodingMachine\TDBM\Utils\Annotation\Annotations;
13
use Laminas\Code\Generator\DocBlock\Tag\ParamTag;
14
use Laminas\Code\Generator\DocBlock\Tag\ReturnTag;
15
use Laminas\Code\Generator\MethodGenerator;
16
use Laminas\Code\Generator\ParameterGenerator;
17
18
use function implode;
19
use function sprintf;
20
use function var_export;
21
22
class PivotTableMethodsDescriptor implements RelationshipMethodDescriptorInterface
23
{
24
    /** @var Table */
25
    private $pivotTable;
26
27
    /** @var bool */
28
    private $useAlternateName = false;
29
30
    /** @var ForeignKeyConstraint */
31
    private $localFk;
32
33
    /** @var ForeignKeyConstraint */
34
    private $remoteFk;
35
    /** @var NamingStrategyInterface */
36
    private $namingStrategy;
37
    /** @var string */
38
    private $beanNamespace;
39
    /** @var AnnotationParser */
40
    private $annotationParser;
41
42
    /** @var array */
43
    private $localAnnotations;
44
    /** @var array */
45
    private $remoteAnnotations;
46
    /** @var string */
47
    private $pathKey;
48
    /**
49
     * @var string
50
     */
51
    private $resultIteratorNamespace;
52
53
    /**
54
     * @param Table $pivotTable The pivot table
55
     * @param ForeignKeyConstraint $localFk
56
     * @param ForeignKeyConstraint $remoteFk
57
     * @param NamingStrategyInterface $namingStrategy
58
     */
59
    public function __construct(
60
        Table $pivotTable,
61
        ForeignKeyConstraint $localFk,
62
        ForeignKeyConstraint $remoteFk,
63
        NamingStrategyInterface $namingStrategy,
64
        AnnotationParser $annotationParser,
65
        string $beanNamespace,
66
        string $resultIteratorNamespace
67
    ) {
68
        $this->pivotTable = $pivotTable;
69
        $this->localFk = $localFk;
70
        $this->remoteFk = $remoteFk;
71
        $this->namingStrategy = $namingStrategy;
72
        $this->annotationParser = $annotationParser;
73
        $this->beanNamespace = $beanNamespace;
74
        $this->resultIteratorNamespace = $resultIteratorNamespace;
75
76
        $this->pathKey = ManyToManyRelationshipPathDescriptor::generateModelKey($this->remoteFk, $this->localFk);
77
    }
78
79
    /**
80
     * Requests the use of an alternative name for this method.
81
     */
82
    public function useAlternativeName(): void
83
    {
84
        $this->useAlternateName = true;
85
    }
86
87
    /**
88
     * Returns the name of the method to be generated.
89
     *
90
     * @return string
91
     */
92
    public function getName(): string
93
    {
94
        return 'get'.$this->getPluralName();
95
    }
96
97
    /**
98
     * Returns the name of the class that will be returned by the getter (short name).
99
     *
100
     * @return string
101
     */
102
    public function getBeanClassName(): string
103
    {
104
        return $this->namingStrategy->getBeanClassName($this->remoteFk->getForeignTableName());
105
    }
106
107
    /**
108
     * Returns the name of the class that will be returned by the getter (short name).
109
     *
110
     * @return string
111
     */
112
    public function getResultIteratorClassName(): string
113
    {
114
        return $this->namingStrategy->getResultIteratorClassName($this->remoteFk->getForeignTableName());
115
    }
116
117
    /**
118
     * Returns the plural name.
119
     *
120
     * @return string
121
     */
122
    private function getPluralName(): string
123
    {
124
        if ($this->isAutoPivot()) {
125
            $name = TDBMDaoGenerator::toPlural($this->namingStrategy->getAutoPivotEntityName($this->remoteFk, false));
126
            if ($this->useAlternateName) {
127
                $name .= 'By_'.$this->pivotTable->getName();
128
            }
129
        } elseif (!$this->useAlternateName) {
130
            $name = $this->remoteFk->getForeignTableName();
131
        } else {
132
            $name = $this->remoteFk->getForeignTableName().'By_'.$this->pivotTable->getName();
133
        }
134
        return TDBMDaoGenerator::toCamelCase($name);
135
    }
136
137
    /**
138
     * Returns the singular name.
139
     *
140
     * @return string
141
     */
142
    private function getSingularName(): string
143
    {
144
        if ($this->isAutoPivot()) {
145
            $name = $this->namingStrategy->getAutoPivotEntityName($this->remoteFk, false);
146
            if ($this->useAlternateName) {
147
                $name .= 'By_'.$this->pivotTable->getName();
148
            }
149
        } elseif (!$this->useAlternateName) {
150
            $name = TDBMDaoGenerator::toSingular($this->remoteFk->getForeignTableName());
151
        } else {
152
            $name = TDBMDaoGenerator::toSingular($this->remoteFk->getForeignTableName()).'By_'.$this->pivotTable->getName();
153
        }
154
        return TDBMDaoGenerator::toCamelCase($name);
155
    }
156
157
    private function isAutoPivot(): bool
158
    {
159
        return $this->localFk->getForeignTableName() === $this->remoteFk->getForeignTableName();
160
    }
161
162
    public function getManyToManyRelationshipInstantiationCode(): string
163
    {
164
        return sprintf(
165
            "new \\TheCodingMachine\\TDBM\\Utils\\ManyToManyRelationshipPathDescriptor(%s, %s, %s, %s, %s, %s)",
166
            var_export($this->remoteFk->getForeignTableName(), true),
167
            var_export($this->remoteFk->getLocalTableName(), true),
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Schema\For...nt::getLocalTableName() has been deprecated: Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

167
            var_export(/** @scrutinizer ignore-deprecated */ $this->remoteFk->getLocalTableName(), true),

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.

Loading history...
168
            $this->getArrayInlineCode($this->remoteFk->getUnquotedForeignColumns()),
169
            $this->getArrayInlineCode($this->remoteFk->getUnquotedLocalColumns()),
170
            $this->getArrayInlineCode($this->localFk->getUnquotedLocalColumns()),
171
            '\\' . $this->resultIteratorNamespace . '\\' . $this->getResultIteratorClassName() . '::class'
172
        );
173
    }
174
175
    /**
176
     * @param string[] $values
177
     * @return string
178
     */
179
    private function getArrayInlineCode(array $values): string
180
    {
181
        $items = [];
182
        foreach ($values as $value) {
183
            $items[] = var_export($value, true);
184
        }
185
        return '['.implode(', ', $items).']';
186
    }
187
188
    public function getManyToManyRelationshipKey(): string
189
    {
190
        return $this->remoteFk->getLocalTableName().".".implode("__", $this->localFk->getUnquotedLocalColumns());
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Schema\For...nt::getLocalTableName() has been deprecated: Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

190
        return /** @scrutinizer ignore-deprecated */ $this->remoteFk->getLocalTableName().".".implode("__", $this->localFk->getUnquotedLocalColumns());

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.

Loading history...
191
    }
192
193
    /**
194
     * Returns the code of the method.
195
     *
196
     * @return MethodGenerator[]
197
     */
198
    public function getCode(): array
199
    {
200
        $singularName = $this->getSingularName();
201
        $pluralName = $this->getPluralName();
202
        $remoteBeanName = $this->getBeanClassName();
203
        $variableName = TDBMDaoGenerator::toVariableName($remoteBeanName);
204
        $fqcnRemoteBeanName = '\\'.$this->beanNamespace.'\\'.$remoteBeanName;
205
        $pluralVariableName = $variableName.'s';
206
207
        $pathKey = var_export($this->pathKey, true);
208
209
        $localTableName = var_export($this->remoteFk->getLocalTableName(), true);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Schema\For...nt::getLocalTableName() has been deprecated: Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

209
        $localTableName = var_export(/** @scrutinizer ignore-deprecated */ $this->remoteFk->getLocalTableName(), true);

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.

Loading history...
210
211
        $getter = new MethodGenerator($this->getName());
212
        $getterDocBlock = new DocBlockGenerator(sprintf('Returns the list of %s associated to this bean via the %s pivot table.', $remoteBeanName, $this->pivotTable->getName()));
213
        $getterDocBlock->setTag(new ReturnTag([ $fqcnRemoteBeanName.'[]' ]));
214
        $getterDocBlock->setWordWrap(false);
215
        $getter->setDocBlock($getterDocBlock);
216
        $getter->setReturnType('array');
217
        $getter->setBody(sprintf('return $this->_getRelationships(%s);', $pathKey));
218
219
220
        $adder = new MethodGenerator('add'.$singularName);
221
        $adderDocBlock = new DocBlockGenerator(sprintf('Adds a relationship with %s associated to this bean via the %s pivot table.', $remoteBeanName, $this->pivotTable->getName()));
222
        $adderDocBlock->setTag(new ParamTag($variableName, [ $fqcnRemoteBeanName ]));
223
        $adderDocBlock->setWordWrap(false);
224
        $adder->setDocBlock($adderDocBlock);
225
        $adder->setReturnType('void');
226
        $adder->setParameter(new ParameterGenerator($variableName, $fqcnRemoteBeanName));
227
        $adder->setBody(sprintf('$this->addRelationship(%s, $%s);', $localTableName, $variableName));
228
229
        $remover = new MethodGenerator('remove'.$singularName);
230
        $removerDocBlock = new DocBlockGenerator(sprintf('Deletes the relationship with %s associated to this bean via the %s pivot table.', $remoteBeanName, $this->pivotTable->getName()));
231
        $removerDocBlock->setTag(new ParamTag($variableName, [ $fqcnRemoteBeanName ]));
232
        $removerDocBlock->setWordWrap(false);
233
        $remover->setDocBlock($removerDocBlock);
234
        $remover->setReturnType('void');
235
        $remover->setParameter(new ParameterGenerator($variableName, $fqcnRemoteBeanName));
236
        $remover->setBody(sprintf('$this->_removeRelationship(%s, $%s);', $localTableName, $variableName));
237
238
        $has = new MethodGenerator('has'.$singularName);
239
        $hasDocBlock = new DocBlockGenerator(sprintf('Returns whether this bean is associated with %s via the %s pivot table.', $remoteBeanName, $this->pivotTable->getName()));
240
        $hasDocBlock->setTag(new ParamTag($variableName, [ $fqcnRemoteBeanName ]));
241
        $hasDocBlock->setTag(new ReturnTag([ 'bool' ]));
242
        $hasDocBlock->setWordWrap(false);
243
        $has->setDocBlock($hasDocBlock);
244
        $has->setReturnType('bool');
245
        $has->setParameter(new ParameterGenerator($variableName, $fqcnRemoteBeanName));
246
        $has->setBody(sprintf('return $this->hasRelationship(%s, $%s);', $pathKey, $variableName));
247
248
        $setter = new MethodGenerator('set'.$pluralName);
249
        $setterDocBlock = new DocBlockGenerator(sprintf('Sets all relationships with %s associated to this bean via the %s pivot table.
250
Exiting relationships will be removed and replaced by the provided relationships.', $remoteBeanName, $this->pivotTable->getName()));
251
        $setterDocBlock->setTag(new ParamTag($pluralVariableName, [ $fqcnRemoteBeanName.'[]' ]));
252
        $setterDocBlock->setTag(new ReturnTag([ 'void' ]));
253
        $setterDocBlock->setWordWrap(false);
254
        $setter->setDocBlock($setterDocBlock);
255
        $setter->setReturnType('void');
256
        $setter->setParameter(new ParameterGenerator($pluralVariableName, 'array'));
257
        $setter->setBody(sprintf('$this->setRelationships(%s, $%s);', $pathKey, $pluralVariableName));
258
259
        return [ $getter, $adder, $remover, $has, $setter ];
260
    }
261
262
    /**
263
     * Returns an array of classes that needs a "use" for this method.
264
     *
265
     * @return string[]
266
     */
267
    public function getUsedClasses(): array
268
    {
269
        return [$this->getBeanClassName()];
270
    }
271
272
    /**
273
     * Returns the code to past in jsonSerialize.
274
     *
275
     * @return string
276
     */
277
    public function getJsonSerializeCode(): string
278
    {
279
        if ($this->findRemoteAnnotation(Annotation\JsonIgnore::class) ||
280
            $this->findLocalAnnotation(Annotation\JsonInclude::class) ||
281
            $this->findLocalAnnotation(Annotation\JsonRecursive::class)) {
282
            return '';
283
        }
284
285
        /** @var Annotation\JsonFormat|null $jsonFormat */
286
        $jsonFormat = $this->findRemoteAnnotation(Annotation\JsonFormat::class);
287
        if ($jsonFormat !== null) {
288
            $method = $jsonFormat->method ?? 'get' . ucfirst($jsonFormat->property);
289
            $format = "$method()";
290
        } else {
291
            $stopRecursion = $this->findRemoteAnnotation(Annotation\JsonRecursive::class) ? '' : 'true';
292
            $format = "jsonSerialize($stopRecursion)";
293
        }
294
        $isIncluded = $this->findRemoteAnnotation(Annotation\JsonInclude::class) !== null;
295
        /** @var Annotation\JsonKey|null $jsonKey */
296
        $jsonKey = $this->findRemoteAnnotation(Annotation\JsonKey::class);
297
        $index = $jsonKey ? $jsonKey->key : lcfirst($this->getPluralName());
298
        $class = $this->getBeanClassName();
299
        $getter = $this->getName();
300
        $code = <<<PHP
301
\$array['$index'] = array_map(function ($class \$object) {
302
    return \$object->$format;
303
}, \$this->$getter());
304
PHP;
305
        if (!$isIncluded) {
306
            $code = preg_replace('(\n)', '\0    ', $code);
307
            $code = <<<PHP
308
if (!\$stopRecursion) {
309
    $code
310
};
311
PHP;
312
        }
313
        return $code;
314
    }
315
316
    /**
317
     * @return Table
318
     */
319
    public function getPivotTable(): Table
320
    {
321
        return $this->pivotTable;
322
    }
323
324
    /**
325
     * @return ForeignKeyConstraint
326
     */
327
    public function getLocalFk(): ForeignKeyConstraint
328
    {
329
        return $this->localFk;
330
    }
331
332
    /**
333
     * @return ForeignKeyConstraint
334
     */
335
    public function getRemoteFk(): ForeignKeyConstraint
336
    {
337
        return $this->remoteFk;
338
    }
339
340
    /**
341
     * @param string $type
342
     * @return null|object
343
     */
344
    private function findLocalAnnotation(string $type)
345
    {
346
        foreach ($this->getLocalAnnotations() as $annotations) {
347
            $annotation = $annotations->findAnnotation($type);
348
            if ($annotation !== null) {
349
                return $annotation;
350
            }
351
        }
352
        return null;
353
    }
354
355
    /**
356
     * @param string $type
357
     * @return null|object
358
     */
359
    private function findRemoteAnnotation(string $type)
360
    {
361
        foreach ($this->getRemoteAnnotations() as $annotations) {
362
            $annotation = $annotations->findAnnotation($type);
363
            if ($annotation !== null) {
364
                return $annotation;
365
            }
366
        }
367
        return null;
368
    }
369
370
    /**
371
     * @return Annotations[]
372
     */
373
    private function getLocalAnnotations(): array
374
    {
375
        if ($this->localAnnotations === null) {
376
            $this->localAnnotations = $this->getFkAnnotations($this->localFk);
377
        }
378
        return $this->localAnnotations;
379
    }
380
381
    /**
382
     * @return Annotations[]
383
     */
384
    private function getRemoteAnnotations(): array
385
    {
386
        if ($this->remoteAnnotations === null) {
387
            $this->remoteAnnotations = $this->getFkAnnotations($this->remoteFk);
388
        }
389
        return $this->remoteAnnotations;
390
    }
391
392
    /**
393
     * @param ForeignKeyConstraint $fk
394
     * @return Annotations[]
395
     */
396
    private function getFkAnnotations(ForeignKeyConstraint $fk): array
397
    {
398
        $annotations = [];
399
        foreach ($this->getFkColumns($fk) as $column) {
400
            $annotations[] = $this->annotationParser->getColumnAnnotations($column, $fk->getLocalTable());
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Schema\For...traint::getLocalTable() has been deprecated: Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

400
            $annotations[] = $this->annotationParser->getColumnAnnotations($column, /** @scrutinizer ignore-deprecated */ $fk->getLocalTable());

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.

Loading history...
401
        }
402
        return $annotations;
403
    }
404
405
    /**
406
     * @param ForeignKeyConstraint $fk
407
     * @return Column[]
408
     */
409
    private function getFkColumns(ForeignKeyConstraint $fk): array
410
    {
411
        $table = $fk->getLocalTable();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Schema\For...traint::getLocalTable() has been deprecated: Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

411
        $table = /** @scrutinizer ignore-deprecated */ $fk->getLocalTable();

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.

Loading history...
412
        $columns = [];
413
        foreach ($fk->getUnquotedLocalColumns() as $column) {
414
            $columns[] = $table->getColumn($column);
415
        }
416
        return $columns;
417
    }
418
419
    public function getCloneRule(): string
420
    {
421
        return sprintf("\$this->%s();\n", $this->getName());
422
    }
423
}
424