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

PivotTableMethodsDescriptor::getLocalFk()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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