Completed
Push — master ( 3633fe...906daf )
by David
17s queued 11s
created

DirectForeignKeyMethodDescriptor   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 263
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 29
eloc 105
c 2
b 0
f 0
dl 0
loc 263
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
B getJsonSerializeCode() 0 40 7
A getBeanClassName() 0 3 1
A getCode() 0 42 3
A findAnnotation() 0 9 3
A getFilters() 0 15 2
A getForeignKey() 0 3 1
A isProtected() 0 3 1
A useAlternativeName() 0 3 1
A getUsedClasses() 0 3 1
A getName() 0 12 2
A __construct() 0 12 1
A getMainTable() 0 3 1
A hasLocalUniqueIndex() 0 12 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace TheCodingMachine\TDBM\Utils;
6
7
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
8
use Doctrine\DBAL\Schema\Table;
9
use TheCodingMachine\TDBM\AlterableResultIterator;
10
use TheCodingMachine\TDBM\Schema\ForeignKey;
11
use TheCodingMachine\TDBM\TDBMException;
12
use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser;
13
use TheCodingMachine\TDBM\Utils\Annotation;
14
use Zend\Code\Generator\AbstractMemberGenerator;
15
use Zend\Code\Generator\DocBlock\Tag\ReturnTag;
16
use Zend\Code\Generator\MethodGenerator;
17
18
/**
19
 * Represents a method to get a list of beans from a direct foreign key pointing to our bean.
20
 */
21
class DirectForeignKeyMethodDescriptor implements MethodDescriptorInterface
22
{
23
    use ForeignKeyAnalyzerTrait;
24
25
    /**
26
     * @var ForeignKeyConstraint
27
     */
28
    private $foreignKey;
29
30
    private $useAlternateName = false;
31
    /**
32
     * @var Table
33
     */
34
    private $mainTable;
35
    /**
36
     * @var NamingStrategyInterface
37
     */
38
    private $namingStrategy;
39
    /**
40
     * @var AnnotationParser
41
     */
42
    private $annotationParser;
43
    /**
44
     * @var string
45
     */
46
    private $beanNamespace;
47
48
    /**
49
     * @param ForeignKeyConstraint $fk The foreign key pointing to our bean
50
     * @param Table $mainTable The main table that is pointed to
51
     * @param NamingStrategyInterface $namingStrategy
52
     * @param AnnotationParser $annotationParser
53
     * @param string $beanNamespace
54
     */
55
    public function __construct(
56
        ForeignKeyConstraint $fk,
57
        Table $mainTable,
58
        NamingStrategyInterface $namingStrategy,
59
        AnnotationParser $annotationParser,
60
        string $beanNamespace
61
    ) {
62
        $this->foreignKey = $fk;
63
        $this->mainTable = $mainTable;
64
        $this->namingStrategy = $namingStrategy;
65
        $this->annotationParser = $annotationParser;
66
        $this->beanNamespace = $beanNamespace;
67
    }
68
69
    /**
70
     * Returns the name of the method to be generated.
71
     *
72
     * @return string
73
     */
74
    public function getName() : string
75
    {
76
        if (!$this->useAlternateName) {
77
            return 'get'.TDBMDaoGenerator::toCamelCase($this->foreignKey->getLocalTableName());
78
        } else {
79
            $methodName = 'get'.TDBMDaoGenerator::toCamelCase($this->foreignKey->getLocalTableName()).'By';
80
81
            $camelizedColumns = array_map([TDBMDaoGenerator::class, 'toCamelCase'], $this->foreignKey->getUnquotedLocalColumns());
82
83
            $methodName .= implode('And', $camelizedColumns);
84
85
            return $methodName;
86
        }
87
    }
88
89
    /**
90
     * Returns the name of the class that will be returned by the getter (short name).
91
     *
92
     * @return string
93
     */
94
    public function getBeanClassName(): string
95
    {
96
        return $this->namingStrategy->getBeanClassName($this->foreignKey->getLocalTableName());
97
    }
98
99
    /**
100
     * Requests the use of an alternative name for this method.
101
     */
102
    public function useAlternativeName(): void
103
    {
104
        $this->useAlternateName = true;
105
    }
106
107
    /**
108
     * Returns the code of the method.
109
     *
110
     * @return MethodGenerator[]
111
     */
112
    public function getCode() : array
113
    {
114
        $beanClass = $this->getBeanClassName();
115
        $tdbmFk = ForeignKey::createFromFk($this->foreignKey);
116
117
        $getter = new MethodGenerator($this->getName());
118
119
        if ($this->hasLocalUniqueIndex()) {
120
            $getter->setDocBlock(sprintf('Returns the %s pointing to this bean via the %s column.', $beanClass, implode(', ', $this->foreignKey->getUnquotedLocalColumns())));
121
            $classType = '\\' . $this->beanNamespace . '\\' . $beanClass;
122
            $getter->getDocBlock()->setTag(new ReturnTag([$classType . '|null']));
123
            $getter->setReturnType('?' . $classType);
124
125
            $code = sprintf(
126
                'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s)->first();',
127
                var_export($this->foreignKey->getLocalTableName(), true),
128
                var_export($tdbmFk->getCacheKey(), true),
129
                $this->getFilters($this->foreignKey)
130
            );
131
        } else {
132
            $getter->setDocBlock(sprintf('Returns the list of %s pointing to this bean via the %s column.', $beanClass, implode(', ', $this->foreignKey->getUnquotedLocalColumns())));
133
            $getter->getDocBlock()->setTag(new ReturnTag([
134
                $beanClass . '[]',
135
                '\\' . AlterableResultIterator::class
136
            ]));
137
            $getter->setReturnType(AlterableResultIterator::class);
138
139
            $code = sprintf(
140
                'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s);',
141
                var_export($this->foreignKey->getLocalTableName(), true),
142
                var_export($tdbmFk->getCacheKey(), true),
143
                $this->getFilters($this->foreignKey)
144
            );
145
        }
146
147
        $getter->setBody($code);
148
149
        if ($this->isProtected()) {
150
            $getter->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED);
151
        }
152
153
        return [ $getter ];
154
    }
155
156
    private function getFilters(ForeignKeyConstraint $fk) : string
157
    {
158
        $counter = 0;
159
        $parameters = [];
160
161
        $fkForeignColumns = $fk->getUnquotedForeignColumns();
162
163
        foreach ($fk->getUnquotedLocalColumns() as $columnName) {
164
            $fkColumn = $fkForeignColumns[$counter];
165
            $parameters[] = sprintf('%s => $this->get(%s, %s)', var_export($fk->getLocalTableName().'.'.$columnName, true), var_export($fkColumn, true), var_export($this->foreignKey->getForeignTableName(), true));
166
            ++$counter;
167
        }
168
        $parametersCode = '['.implode(', ', $parameters).']';
169
170
        return $parametersCode;
171
    }
172
173
    /**
174
     * Check if the ForeignKey have an unique index
175
     *
176
     * @return bool
177
     */
178
    private function hasLocalUniqueIndex(): bool
179
    {
180
        foreach ($this->getForeignKey()->getLocalTable()->getIndexes() as $index) {
181
            if (
182
                $index->isUnique()
183
                && count($index->getUnquotedColumns()) === count($this->getForeignKey()->getUnquotedLocalColumns())
184
                && !array_diff($index->getUnquotedColumns(), $this->getForeignKey()->getUnquotedLocalColumns()) // Check for permuted columns too
185
            ) {
186
                return true;
187
            }
188
        }
189
        return false;
190
    }
191
192
    /**
193
     * Returns an array of classes that needs a "use" for this method.
194
     *
195
     * @return string[]
196
     */
197
    public function getUsedClasses() : array
198
    {
199
        return [$this->getBeanClassName()];
200
    }
201
202
    /**
203
     * Returns the code to past in jsonSerialize.
204
     *
205
     * @return string
206
     */
207
    public function getJsonSerializeCode() : string
208
    {
209
        /** @var Annotation\JsonCollection|null $jsonCollection */
210
        $jsonCollection = $this->findAnnotation(Annotation\JsonCollection::class);
211
        if ($jsonCollection === null) {
212
            return '';
213
        }
214
215
        /** @var Annotation\JsonFormat|null $jsonFormat */
216
        $jsonFormat = $this->findAnnotation(Annotation\JsonFormat::class);
217
        if ($jsonFormat !== null) {
218
            $method = $jsonFormat->method ?? 'get' . ucfirst($jsonFormat->property);
219
            $format = "$method()";
220
        } else {
221
            $stopRecursion = $this->findAnnotation(Annotation\JsonRecursive::class) ? '' : 'true';
222
            $format = "jsonSerialize($stopRecursion)";
223
        }
224
        $isIncluded = $this->findAnnotation(Annotation\JsonInclude::class) !== null;
225
        $index = $jsonCollection->key ?: lcfirst(TDBMDaoGenerator::toCamelCase($this->foreignKey->getLocalTableName()));
226
        $class = $this->getBeanClassName();
227
        $variableName = '$' . TDBMDaoGenerator::toVariableName($class);
228
        $getter = $this->getName();
229
        if ($this->hasLocalUniqueIndex()) {
230
            $code = "\$array['$index'] = (\$object = \$this->$getter()) ? \$object->$format : null;";
231
        } else {
232
            $code = <<<PHP
233
\$array['$index'] = array_map(function ($class $variableName) {
234
    return ${variableName}->$format;
235
}, \$this->$getter()->toArray());
236
PHP;
237
        }
238
        if (!$isIncluded) {
239
            $code = preg_replace('(\n)', '\0    ', $code);
240
            $code = <<<PHP
241
if (!\$stopRecursion) {
242
    $code
243
}
244
PHP;
245
        }
246
        return $code;
247
    }
248
249
    /**
250
     * @return ForeignKeyConstraint
251
     */
252
    public function getForeignKey(): ForeignKeyConstraint
253
    {
254
        return $this->foreignKey;
255
    }
256
257
    /**
258
     * Returns the table that is pointed to.
259
     * @return Table
260
     */
261
    public function getMainTable(): Table
262
    {
263
        return $this->mainTable;
264
    }
265
266
    private function isProtected(): bool
267
    {
268
        return $this->findAnnotation(Annotation\ProtectedOneToMany::class) !== null;
269
    }
270
271
    /**
272
     * @param string $type
273
     * @return null|object
274
     */
275
    private function findAnnotation(string $type)
276
    {
277
        foreach ($this->getAnnotations() as $annotations) {
278
            $annotation = $annotations->findAnnotation($type);
279
            if ($annotation !== null) {
280
                return $annotation;
281
            }
282
        }
283
        return null;
284
    }
285
}
286