Passed
Pull Request — master (#146)
by David
02:27
created

PivotTableMethodsDescriptor   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 376
Duplicated Lines 0 %

Importance

Changes 7
Bugs 1 Features 3
Metric Value
wmc 43
eloc 145
c 7
b 1
f 3
dl 0
loc 376
rs 8.96

23 Methods

Rating   Name   Duplication   Size   Complexity  
A getPivotTable() 0 3 1
A getManyToManyRelationshipInstantiationCode() 0 8 1
A isAutoPivot() 0 3 1
A findRemoteAnnotation() 0 9 3
A getFkAnnotations() 0 7 2
A getPluralName() 0 10 3
A useAlternativeName() 0 3 1
A getUsedClasses() 0 3 1
A getSingularName() 0 10 3
A getName() 0 3 1
A getRemoteAnnotations() 0 6 2
A __construct() 0 10 1
A getRemoteFk() 0 3 1
B getJsonSerializeCode() 0 37 8
A getBeanClassName() 0 3 1
A getLocalAnnotations() 0 6 2
A getManyToManyRelationshipKey() 0 3 1
A findLocalAnnotation() 0 9 3
A getCode() 0 52 1
A getFkColumns() 0 8 2
A getLocalFk() 0 3 1
A getCloneRule() 0 3 1
A getArrayInlineCode() 0 7 2

How to fix   Complexity   

Complex Class

Complex classes like PivotTableMethodsDescriptor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PivotTableMethodsDescriptor, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
4
namespace TheCodingMachine\TDBM\Utils;
5
6
use Doctrine\DBAL\Schema\Column;
7
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
8
use Doctrine\DBAL\Schema\Table;
9
use function implode;
10
use function sprintf;
11
use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser;
12
use TheCodingMachine\TDBM\Utils\Annotation\Annotations;
13
use Zend\Code\Generator\DocBlock\Tag\ParamTag;
14
use Zend\Code\Generator\DocBlock\Tag\ReturnTag;
15
use Zend\Code\Generator\MethodGenerator;
16
use Zend\Code\Generator\ParameterGenerator;
17
use function var_export;
18
19
class PivotTableMethodsDescriptor implements MethodDescriptorInterface
20
{
21
    /**
22
     * @var Table
23
     */
24
    private $pivotTable;
25
26
    private $useAlternateName = false;
27
28
    /**
29
     * @var ForeignKeyConstraint
30
     */
31
    private $localFk;
32
33
    /**
34
     * @var ForeignKeyConstraint
35
     */
36
    private $remoteFk;
37
    /**
38
     * @var NamingStrategyInterface
39
     */
40
    private $namingStrategy;
41
    /**
42
     * @var string
43
     */
44
    private $beanNamespace;
45
    /**
46
     * @var AnnotationParser
47
     */
48
    private $annotationParser;
49
50
    /**
51
     * @var array
52
     */
53
    private $localAnnotations;
54
    /**
55
     * @var array
56
     */
57
    private $remoteAnnotations;
58
    /**
59
     * @var string
60
     */
61
    private $pathKey;
62
63
    /**
64
     * @param Table $pivotTable The pivot table
65
     * @param ForeignKeyConstraint $localFk
66
     * @param ForeignKeyConstraint $remoteFk
67
     * @param NamingStrategyInterface $namingStrategy
68
     */
69
    public function __construct(Table $pivotTable, ForeignKeyConstraint $localFk, ForeignKeyConstraint $remoteFk, NamingStrategyInterface $namingStrategy, string $beanNamespace, AnnotationParser $annotationParser)
70
    {
71
        $this->pivotTable = $pivotTable;
72
        $this->localFk = $localFk;
73
        $this->remoteFk = $remoteFk;
74
        $this->namingStrategy = $namingStrategy;
75
        $this->beanNamespace = $beanNamespace;
76
        $this->annotationParser = $annotationParser;
77
78
        $this->pathKey = ManyToManyRelationshipPathDescriptor::generateModelKey($this->remoteFk, $this->localFk);
79
    }
80
81
    /**
82
     * Requests the use of an alternative name for this method.
83
     */
84
    public function useAlternativeName(): void
85
    {
86
        $this->useAlternateName = true;
87
    }
88
89
    /**
90
     * Returns the name of the method to be generated.
91
     *
92
     * @return string
93
     */
94
    public function getName() : string
95
    {
96
        return 'get'.$this->getPluralName();
97
    }
98
99
    /**
100
     * Returns the name of the class that will be returned by the getter (short name).
101
     *
102
     * @return string
103
     */
104
    public function getBeanClassName(): string
105
    {
106
        return $this->namingStrategy->getBeanClassName($this->remoteFk->getForeignTableName());
107
    }
108
109
    /**
110
     * Returns the plural name.
111
     *
112
     * @return string
113
     */
114
    private function getPluralName() : string
115
    {
116
        if ($this->isAutoPivot()) {
117
            $name = $this->remoteFk->getForeignTableName().'By_'.$this->pivotTable->getName().'Via_'.implode('And_', $this->localFk->getUnquotedLocalColumns());
118
        } elseif (!$this->useAlternateName) {
119
            $name = $this->remoteFk->getForeignTableName();
120
        } else {
121
            $name = $this->remoteFk->getForeignTableName().'By_'.$this->pivotTable->getName();
122
        }
123
        return TDBMDaoGenerator::toCamelCase($name);
124
    }
125
126
    /**
127
     * Returns the singular name.
128
     *
129
     * @return string
130
     */
131
    private function getSingularName() : string
132
    {
133
        if ($this->isAutoPivot()) {
134
            $name = TDBMDaoGenerator::toSingular($this->remoteFk->getForeignTableName()).'By_'.$this->pivotTable->getName().'Via_'.implode('And_', $this->localFk->getUnquotedLocalColumns());
135
        } elseif (!$this->useAlternateName) {
136
            $name = TDBMDaoGenerator::toSingular($this->remoteFk->getForeignTableName());
137
        } else {
138
            $name = TDBMDaoGenerator::toSingular($this->remoteFk->getForeignTableName()).'By_'.$this->pivotTable->getName();
139
        }
140
        return TDBMDaoGenerator::toCamelCase($name);
141
    }
142
143
    private function isAutoPivot(): bool
144
    {
145
        return $this->localFk->getForeignTableName() === $this->remoteFk->getForeignTableName();
146
    }
147
148
    public function getManyToManyRelationshipInstantiationCode(): string
149
    {
150
        return 'new \TheCodingMachine\TDBM\Utils\ManyToManyRelationshipPathDescriptor('.var_export($this->remoteFk->getForeignTableName(), true).
151
            ', '.var_export($this->remoteFk->getLocalTableName(), true).
152
            ', '.$this->getArrayInlineCode($this->remoteFk->getUnquotedForeignColumns()).
153
            ', '.$this->getArrayInlineCode($this->remoteFk->getUnquotedLocalColumns()).
154
            ', '.$this->getArrayInlineCode($this->localFk->getUnquotedLocalColumns()).
155
            ')';
156
    }
157
158
    /**
159
     * @param string[] $values
160
     * @return string
161
     */
162
    private function getArrayInlineCode(array $values): string
163
    {
164
        $items = [];
165
        foreach ($values as $value) {
166
            $items[] = var_export($value, true);
167
        }
168
        return '['.implode(', ', $items).']';
169
    }
170
171
    public function getManyToManyRelationshipKey(): string
172
    {
173
        return $this->remoteFk->getLocalTableName().".".implode("__", $this->localFk->getUnquotedLocalColumns());
174
    }
175
176
    /**
177
     * Returns the code of the method.
178
     *
179
     * @return MethodGenerator[]
180
     */
181
    public function getCode() : array
182
    {
183
        $singularName = $this->getSingularName();
184
        $pluralName = $this->getPluralName();
185
        $remoteBeanName = $this->getBeanClassName();
186
        $variableName = TDBMDaoGenerator::toVariableName($remoteBeanName);
187
        $fqcnRemoteBeanName = '\\'.$this->beanNamespace.'\\'.$remoteBeanName;
188
        $pluralVariableName = $variableName.'s';
189
190
        $pathKey = var_export($this->pathKey, true);
191
192
        $localTableName = var_export($this->remoteFk->getLocalTableName(), true);
193
194
        $getter = new MethodGenerator($this->getName());
195
        $getter->setDocBlock(sprintf('Returns the list of %s associated to this bean via the %s pivot table.', $remoteBeanName, $this->pivotTable->getName()));
196
        $getter->getDocBlock()->setTag(new ReturnTag([ $fqcnRemoteBeanName.'[]' ]));
197
        $getter->setReturnType('array');
198
        $getter->setBody(sprintf('return $this->_getRelationships(%s);', $pathKey));
199
200
201
        $adder = new MethodGenerator('add'.$singularName);
202
        $adder->setDocBlock(sprintf('Adds a relationship with %s associated to this bean via the %s pivot table.', $remoteBeanName, $this->pivotTable->getName()));
203
        $adder->getDocBlock()->setTag(new ParamTag($variableName, [ $fqcnRemoteBeanName ]));
204
        $adder->setReturnType('void');
205
        $adder->setParameter(new ParameterGenerator($variableName, $fqcnRemoteBeanName));
206
        $adder->setBody(sprintf('$this->addRelationship(%s, $%s);', $localTableName, $variableName));
207
208
        $remover = new MethodGenerator('remove'.$singularName);
209
        $remover->setDocBlock(sprintf('Deletes the relationship with %s associated to this bean via the %s pivot table.', $remoteBeanName, $this->pivotTable->getName()));
210
        $remover->getDocBlock()->setTag(new ParamTag($variableName, [ $fqcnRemoteBeanName ]));
211
        $remover->setReturnType('void');
212
        $remover->setParameter(new ParameterGenerator($variableName, $fqcnRemoteBeanName));
213
        $remover->setBody(sprintf('$this->_removeRelationship(%s, $%s);', $localTableName, $variableName));
214
215
        $has = new MethodGenerator('has'.$singularName);
216
        $has->setDocBlock(sprintf('Returns whether this bean is associated with %s via the %s pivot table.', $remoteBeanName, $this->pivotTable->getName()));
217
        $has->getDocBlock()->setTag(new ParamTag($variableName, [ $fqcnRemoteBeanName ]));
218
        $has->getDocBlock()->setTag(new ReturnTag([ 'bool' ]));
219
        $has->setReturnType('bool');
220
        $has->setParameter(new ParameterGenerator($variableName, $fqcnRemoteBeanName));
221
        $has->setBody(sprintf('return $this->hasRelationship(%s, $%s);', $pathKey, $variableName));
222
223
        $setter = new MethodGenerator('set'.$pluralName);
224
        $setter->setDocBlock(sprintf('Sets all relationships with %s associated to this bean via the %s pivot table.
225
Exiting relationships will be removed and replaced by the provided relationships.', $remoteBeanName, $this->pivotTable->getName()));
226
        $setter->getDocBlock()->setTag(new ParamTag($pluralVariableName, [ $fqcnRemoteBeanName.'[]' ]));
227
        $setter->getDocBlock()->setTag(new ReturnTag([ 'void' ]));
228
        $setter->setReturnType('void');
229
        $setter->setParameter(new ParameterGenerator($pluralVariableName, 'array'));
230
        $setter->setBody(sprintf('$this->setRelationships(%s, $%s);', $pathKey, $pluralVariableName));
231
232
        return [ $getter, $adder, $remover, $has, $setter ];
233
    }
234
235
    /**
236
     * Returns an array of classes that needs a "use" for this method.
237
     *
238
     * @return string[]
239
     */
240
    public function getUsedClasses() : array
241
    {
242
        return [$this->getBeanClassName()];
243
    }
244
245
    /**
246
     * Returns the code to past in jsonSerialize.
247
     *
248
     * @return string
249
     */
250
    public function getJsonSerializeCode() : string
251
    {
252
        if ($this->findRemoteAnnotation(Annotation\JsonIgnore::class) ||
253
            $this->findLocalAnnotation(Annotation\JsonInclude::class) ||
254
            $this->findLocalAnnotation(Annotation\JsonRecursive::class)) {
255
            return '';
256
        }
257
258
        /** @var Annotation\JsonFormat|null $jsonFormat */
259
        $jsonFormat = $this->findRemoteAnnotation(Annotation\JsonFormat::class);
260
        if ($jsonFormat !== null) {
261
            $method = $jsonFormat->method ?? 'get' . ucfirst($jsonFormat->property);
262
            $format = "$method()";
263
        } else {
264
            $stopRecursion = $this->findRemoteAnnotation(Annotation\JsonRecursive::class) ? '' : 'true';
265
            $format = "jsonSerialize($stopRecursion)";
266
        }
267
        $isIncluded = $this->findRemoteAnnotation(Annotation\JsonInclude::class) !== null;
268
        /** @var Annotation\JsonKey|null $jsonKey */
269
        $jsonKey = $this->findRemoteAnnotation(Annotation\JsonKey::class);
270
        $index = $jsonKey ? $jsonKey->key : lcfirst($this->getPluralName());
271
        $class = $this->getBeanClassName();
272
        $getter = $this->getName();
273
        $code = <<<PHP
274
\$array['$index'] = array_map(function ($class \$object) {
275
    return \$object->$format;
276
}, \$this->$getter());
277
PHP;
278
        if (!$isIncluded) {
279
            $code = preg_replace('(\n)', '\0    ', $code);
280
            $code = <<<PHP
281
if (!\$stopRecursion) {
282
    $code
283
};
284
PHP;
285
        }
286
        return $code;
287
    }
288
289
    /**
290
     * @return Table
291
     */
292
    public function getPivotTable(): Table
293
    {
294
        return $this->pivotTable;
295
    }
296
297
    /**
298
     * @return ForeignKeyConstraint
299
     */
300
    public function getLocalFk(): ForeignKeyConstraint
301
    {
302
        return $this->localFk;
303
    }
304
305
    /**
306
     * @return ForeignKeyConstraint
307
     */
308
    public function getRemoteFk(): ForeignKeyConstraint
309
    {
310
        return $this->remoteFk;
311
    }
312
313
    /**
314
     * @param string $type
315
     * @return null|object
316
     */
317
    private function findLocalAnnotation(string $type)
318
    {
319
        foreach ($this->getLocalAnnotations() as $annotations) {
320
            $annotation = $annotations->findAnnotation($type);
321
            if ($annotation !== null) {
322
                return $annotation;
323
            }
324
        }
325
        return null;
326
    }
327
328
    /**
329
     * @param string $type
330
     * @return null|object
331
     */
332
    private function findRemoteAnnotation(string $type)
333
    {
334
        foreach ($this->getRemoteAnnotations() as $annotations) {
335
            $annotation = $annotations->findAnnotation($type);
336
            if ($annotation !== null) {
337
                return $annotation;
338
            }
339
        }
340
        return null;
341
    }
342
343
    /**
344
     * @return Annotations[]
345
     */
346
    private function getLocalAnnotations(): array
347
    {
348
        if ($this->localAnnotations === null) {
349
            $this->localAnnotations = $this->getFkAnnotations($this->localFk);
350
        }
351
        return $this->localAnnotations;
352
    }
353
354
    /**
355
     * @return Annotations[]
356
     */
357
    private function getRemoteAnnotations(): array
358
    {
359
        if ($this->remoteAnnotations === null) {
360
            $this->remoteAnnotations = $this->getFkAnnotations($this->remoteFk);
361
        }
362
        return $this->remoteAnnotations;
363
    }
364
365
    /**
366
     * @param ForeignKeyConstraint $fk
367
     * @return Annotations[]
368
     */
369
    private function getFkAnnotations(ForeignKeyConstraint $fk): array
370
    {
371
        $annotations = [];
372
        foreach ($this->getFkColumns($fk) as $column) {
373
            $annotations[] = $this->annotationParser->getColumnAnnotations($column, $fk->getLocalTable());
374
        }
375
        return $annotations;
376
    }
377
378
    /**
379
     * @param ForeignKeyConstraint $fk
380
     * @return Column[]
381
     */
382
    private function getFkColumns(ForeignKeyConstraint $fk): array
383
    {
384
        $table = $fk->getLocalTable();
385
        $columns = [];
386
        foreach ($fk->getUnquotedLocalColumns() as $column) {
387
            $columns[] = $table->getColumn($column);
388
        }
389
        return $columns;
390
    }
391
392
    public function getCloneRule(): string
393
    {
394
        return sprintf("\$this->%s();\n", $this->getName());
395
    }
396
}
397