Passed
Pull Request — master (#132)
by David
05:06
created

findRemoteAnnotation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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