Passed
Pull Request — master (#146)
by David
03:00
created

PivotTableMethodsDescriptor   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 382
Duplicated Lines 0 %

Importance

Changes 7
Bugs 1 Features 3
Metric Value
wmc 45
eloc 149
c 7
b 1
f 3
dl 0
loc 382
rs 8.8

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