Passed
Pull Request — master (#181)
by
unknown
05:45
created

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