Passed
Pull Request — master (#154)
by
unknown
02:40
created

getPropertyName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
c 0
b 0
f 0
dl 0
loc 7
rs 10
cc 2
nc 2
nop 0
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' . $this->getPropertyName();
78
        } else {
79
            $methodName = 'get' . $this->getPropertyName() . '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 property name in CamelCase taking into account singularization
91
     *
92
     * @return string
93
     */
94
    private function getPropertyName() : string
95
    {
96
        $name = $this->foreignKey->getLocalTableName();
97
        if ($this->hasLocalUniqueIndex()) {
98
            $name = TDBMDaoGenerator::toSingular($name);
99
        }
100
        return TDBMDaoGenerator::toCamelCase($name);
101
    }
102
103
    /**
104
     * Returns the name of the class that will be returned by the getter (short name).
105
     *
106
     * @return string
107
     */
108
    public function getBeanClassName(): string
109
    {
110
        return $this->namingStrategy->getBeanClassName($this->foreignKey->getLocalTableName());
111
    }
112
113
    /**
114
     * Requests the use of an alternative name for this method.
115
     */
116
    public function useAlternativeName(): void
117
    {
118
        $this->useAlternateName = true;
119
    }
120
121
    /**
122
     * Returns the code of the method.
123
     *
124
     * @return MethodGenerator[]
125
     */
126
    public function getCode() : array
127
    {
128
        $beanClass = $this->getBeanClassName();
129
        $tdbmFk = ForeignKey::createFromFk($this->foreignKey);
130
131
        $getter = new MethodGenerator($this->getName());
132
133
        if ($this->hasLocalUniqueIndex()) {
134
            $getter->setDocBlock(sprintf('Returns the %s pointing to this bean via the %s column.', $beanClass, implode(', ', $this->foreignKey->getUnquotedLocalColumns())));
135
            $classType = '\\' . $this->beanNamespace . '\\' . $beanClass;
136
            $getter->getDocBlock()->setTag(new ReturnTag([$classType . '|null']));
137
            $getter->setReturnType('?' . $classType);
138
139
            $code = sprintf(
140
                'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s)->first();',
141
                var_export($this->foreignKey->getLocalTableName(), true),
142
                var_export($tdbmFk->getCacheKey(), true),
143
                $this->getFilters($this->foreignKey)
144
            );
145
        } else {
146
            $getter->setDocBlock(sprintf('Returns the list of %s pointing to this bean via the %s column.', $beanClass, implode(', ', $this->foreignKey->getUnquotedLocalColumns())));
147
            $getter->getDocBlock()->setTag(new ReturnTag([
148
                $beanClass . '[]',
149
                '\\' . AlterableResultIterator::class
150
            ]));
151
            $getter->setReturnType(AlterableResultIterator::class);
152
153
            $code = sprintf(
154
                'return $this->retrieveManyToOneRelationshipsStorage(%s, %s, %s);',
155
                var_export($this->foreignKey->getLocalTableName(), true),
156
                var_export($tdbmFk->getCacheKey(), true),
157
                $this->getFilters($this->foreignKey)
158
            );
159
        }
160
161
        $getter->setBody($code);
162
163
        if ($this->isProtected()) {
164
            $getter->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED);
165
        }
166
167
        return [ $getter ];
168
    }
169
170
    private function getFilters(ForeignKeyConstraint $fk) : string
171
    {
172
        $counter = 0;
173
        $parameters = [];
174
175
        $fkForeignColumns = $fk->getUnquotedForeignColumns();
176
177
        foreach ($fk->getUnquotedLocalColumns() as $columnName) {
178
            $fkColumn = $fkForeignColumns[$counter];
179
            $parameters[] = sprintf('%s => $this->get(%s, %s)', var_export($fk->getLocalTableName().'.'.$columnName, true), var_export($fkColumn, true), var_export($this->foreignKey->getForeignTableName(), true));
180
            ++$counter;
181
        }
182
        $parametersCode = '['.implode(', ', $parameters).']';
183
184
        return $parametersCode;
185
    }
186
187
    private $hasLocalUniqueIndex;
188
    /**
189
     * Check if the ForeignKey have an unique index
190
     *
191
     * @return bool
192
     */
193
    private function hasLocalUniqueIndex(): bool
194
    {
195
        if ($this->hasLocalUniqueIndex !== null) {
196
            return $this->hasLocalUniqueIndex;
197
        }
198
        foreach ($this->getForeignKey()->getLocalTable()->getIndexes() as $index) {
199
            if (
200
                $index->isUnique()
201
                && count($index->getUnquotedColumns()) === count($this->getForeignKey()->getUnquotedLocalColumns())
202
                && !array_diff($index->getUnquotedColumns(), $this->getForeignKey()->getUnquotedLocalColumns()) // Check for permuted columns too
203
            ) {
204
                $this->hasLocalUniqueIndex = true;
205
                return true;
206
            }
207
        }
208
        $this->hasLocalUniqueIndex = false;
209
        return false;
210
    }
211
212
    /**
213
     * Returns an array of classes that needs a "use" for this method.
214
     *
215
     * @return string[]
216
     */
217
    public function getUsedClasses() : array
218
    {
219
        return [$this->getBeanClassName()];
220
    }
221
222
    /**
223
     * Returns the code to past in jsonSerialize.
224
     *
225
     * @return string
226
     */
227
    public function getJsonSerializeCode() : string
228
    {
229
        /** @var Annotation\JsonCollection|null $jsonCollection */
230
        $jsonCollection = $this->findAnnotation(Annotation\JsonCollection::class);
231
        if ($jsonCollection === null) {
232
            return '';
233
        }
234
235
        /** @var Annotation\JsonFormat|null $jsonFormat */
236
        $jsonFormat = $this->findAnnotation(Annotation\JsonFormat::class);
237
        if ($jsonFormat !== null) {
238
            $method = $jsonFormat->method ?? 'get' . ucfirst($jsonFormat->property);
239
            $format = "$method()";
240
        } else {
241
            $stopRecursion = $this->findAnnotation(Annotation\JsonRecursive::class) ? '' : 'true';
242
            $format = "jsonSerialize($stopRecursion)";
243
        }
244
        $isIncluded = $this->findAnnotation(Annotation\JsonInclude::class) !== null;
245
        $index = $jsonCollection->key ?: lcfirst($this->getPropertyName());
246
        $class = $this->getBeanClassName();
247
        $variableName = '$' . TDBMDaoGenerator::toVariableName($class);
248
        $getter = $this->getName();
249
        if ($this->hasLocalUniqueIndex()) {
250
            $code = "\$array['$index'] = (\$object = \$this->$getter()) ? \$object->$format : null;";
251
        } else {
252
            $code = <<<PHP
253
\$array['$index'] = array_map(function ($class $variableName) {
254
    return ${variableName}->$format;
255
}, \$this->$getter()->toArray());
256
PHP;
257
        }
258
        if (!$isIncluded) {
259
            $code = preg_replace('(\n)', '\0    ', $code);
260
            $code = <<<PHP
261
if (!\$stopRecursion) {
262
    $code
263
}
264
PHP;
265
        }
266
        return $code;
267
    }
268
269
    /**
270
     * @return ForeignKeyConstraint
271
     */
272
    public function getForeignKey(): ForeignKeyConstraint
273
    {
274
        return $this->foreignKey;
275
    }
276
277
    /**
278
     * Returns the table that is pointed to.
279
     * @return Table
280
     */
281
    public function getMainTable(): Table
282
    {
283
        return $this->mainTable;
284
    }
285
286
    private function isProtected(): bool
287
    {
288
        return $this->findAnnotation(Annotation\ProtectedOneToMany::class) !== null;
289
    }
290
291
    /**
292
     * @param string $type
293
     * @return null|object
294
     */
295
    private function findAnnotation(string $type)
296
    {
297
        foreach ($this->getAnnotations() as $annotations) {
298
            $annotation = $annotations->findAnnotation($type);
299
            if ($annotation !== null) {
300
                return $annotation;
301
            }
302
        }
303
        return null;
304
    }
305
}
306