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