Completed
Push — master ( 8e6f61...58c980 )
by David
13s queued 11s
created

ObjectBeanPropertyDescriptor::getForeignKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace TheCodingMachine\TDBM\Utils;
5
6
use Doctrine\DBAL\Schema\Table;
7
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
8
use TheCodingMachine\TDBM\Schema\ForeignKey;
9
use TheCodingMachine\TDBM\TDBMException;
10
use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser;
11
use TheCodingMachine\TDBM\Utils\Annotation;
12
use Zend\Code\Generator\AbstractMemberGenerator;
13
use Zend\Code\Generator\MethodGenerator;
14
use Zend\Code\Generator\ParameterGenerator;
15
16
/**
17
 * This class represent a property in a bean that points to another table.
18
 */
19
class ObjectBeanPropertyDescriptor extends AbstractBeanPropertyDescriptor
20
{
21
    use ForeignKeyAnalyzerTrait;
22
23
    /**
24
     * @var ForeignKeyConstraint
25
     */
26
    private $foreignKey;
27
    /**
28
     * @var string
29
     */
30
    private $beanNamespace;
31
32
    /**
33
     * @var AnnotationParser
34
     */
35
    private $annotationParser;
36
37
    /**
38
     * ObjectBeanPropertyDescriptor constructor.
39
     * @param Table $table
40
     * @param ForeignKeyConstraint $foreignKey
41
     * @param NamingStrategyInterface $namingStrategy
42
     */
43
    public function __construct(Table $table, ForeignKeyConstraint $foreignKey, NamingStrategyInterface $namingStrategy, string $beanNamespace, AnnotationParser $annotationParser)
44
    {
45
        parent::__construct($table, $namingStrategy);
46
        $this->foreignKey = $foreignKey;
47
        $this->beanNamespace = $beanNamespace;
48
        $this->annotationParser = $annotationParser;
49
    }
50
51
    /**
52
     * Returns the foreignkey the column is part of, if any. null otherwise.
53
     *
54
     * @return ForeignKeyConstraint
55
     */
56
    public function getForeignKey(): ForeignKeyConstraint
57
    {
58
        return $this->foreignKey;
59
    }
60
61
    /**
62
     * Returns the name of the class linked to this property or null if this is not a foreign key.
63
     *
64
     * @return string
65
     */
66
    public function getClassName(): string
67
    {
68
        return $this->namingStrategy->getBeanClassName($this->foreignKey->getForeignTableName());
69
    }
70
71
    /**
72
     * Returns the PHP type for the property (it can be a scalar like int, bool, or class names, like \DateTimeInterface, App\Bean\User....)
73
     *
74
     * @return string
75
     */
76
    public function getPhpType(): string
77
    {
78
        return '\\' . $this->beanNamespace . '\\' . $this->getClassName();
79
    }
80
81
    /**
82
     * Returns true if the property is compulsory (and therefore should be fetched in the constructor).
83
     *
84
     * @return bool
85
     */
86
    public function isCompulsory(): bool
87
    {
88
        // Are all columns nullable?
89
        foreach ($this->getLocalColumns() as $column) {
90
            if ($column->getNotnull()) {
91
                return true;
92
            }
93
        }
94
95
        return false;
96
    }
97
98
    /**
99
     * Returns true if the property has a default value.
100
     *
101
     * @return bool
102
     */
103
    public function hasDefault(): bool
104
    {
105
        return false;
106
    }
107
108
    /**
109
     * Returns the code that assigns a value to its default value.
110
     *
111
     * @return string
112
     *
113
     * @throws TDBMException
114
     */
115
    public function assignToDefaultCode(): string
116
    {
117
        throw new TDBMException('Foreign key based properties cannot be assigned a default value.');
118
    }
119
120
    /**
121
     * Returns true if the property is the primary key.
122
     *
123
     * @return bool
124
     */
125
    public function isPrimaryKey(): bool
126
    {
127
        $fkColumns = $this->foreignKey->getUnquotedLocalColumns();
128
        sort($fkColumns);
129
130
        $pkColumns = TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($this->table);
131
        sort($pkColumns);
132
133
        return $fkColumns == $pkColumns;
134
    }
135
136
    /**
137
     * Returns the PHP code for getters and setters.
138
     *
139
     * @return MethodGenerator[]
140
     */
141
    public function getGetterSetterCode(): array
142
    {
143
        $tableName = $this->table->getName();
144
        $getterName = $this->getGetterName();
145
        $setterName = $this->getSetterName();
146
        $isNullable = !$this->isCompulsory();
147
148
        $referencedBeanName = $this->namingStrategy->getBeanClassName($this->foreignKey->getForeignTableName());
149
150
        $getter = new MethodGenerator($getterName);
151
        $getter->setDocBlock('Returns the ' . $referencedBeanName . ' object bound to this object via the ' . implode(' and ', $this->foreignKey->getUnquotedLocalColumns()) . ' column.');
152
153
        /*$types = [ $referencedBeanName ];
154
        if ($isNullable) {
155
            $types[] = 'null';
156
        }
157
        $getter->getDocBlock()->setTag(new ReturnTag($types));*/
158
159
        $getter->setReturnType(($isNullable ? '?' : '') . $this->beanNamespace . '\\' . $referencedBeanName);
160
        $tdbmFk = ForeignKey::createFromFk($this->foreignKey);
161
162
        $getter->setBody('return $this->getRef(' . var_export($tdbmFk->getCacheKey(), true) . ', ' . var_export($tableName, true) . ');');
163
164
        if ($this->isGetterProtected()) {
165
            $getter->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED);
166
        }
167
168
        $setter = new MethodGenerator($setterName);
169
        $setter->setDocBlock('The setter for the ' . $referencedBeanName . ' object bound to this object via the ' . implode(' and ', $this->foreignKey->getUnquotedLocalColumns()) . ' column.');
170
171
        $setter->setParameter(new ParameterGenerator('object', ($isNullable ? '?' : '') . $this->beanNamespace . '\\' . $referencedBeanName));
172
173
        $setter->setReturnType('void');
174
175
        $setter->setBody('$this->setRef(' . var_export($tdbmFk->getCacheKey(), true) . ', $object, ' . var_export($tableName, true) . ');');
176
177
        if ($this->isSetterProtected()) {
178
            $setter->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED);
179
        }
180
181
        return [$getter, $setter];
182
    }
183
184
    /**
185
     * Returns the part of code useful when doing json serialization.
186
     *
187
     * @return string
188
     */
189
    public function getJsonSerializeCode(): string
190
    {
191
        if ($this->findAnnotation(Annotation\JsonIgnore::class)) {
192
            return '';
193
        }
194
195
        if ($this->isGetterProtected()) {
196
            return '';
197
        }
198
199
        if ($this->findAnnotation(Annotation\JsonCollection::class)) {
200
            if ($this->findAnnotation(Annotation\JsonInclude::class) ||
201
                $this->findAnnotation(Annotation\JsonRecursive::class)) {
202
                return '';
203
            }
204
            $isIncluded = false;
205
            $format = "jsonSerialize(true)";
206
        } else {
207
            $isIncluded = $this->findAnnotation(Annotation\JsonInclude::class) !== null;
208
            /** @var Annotation\JsonFormat|null $jsonFormat */
209
            $jsonFormat = $this->findAnnotation(Annotation\JsonFormat::class);
210
            if ($jsonFormat !== null) {
211
                $method = $jsonFormat->method ?? 'get' . ucfirst($jsonFormat->property);
212
                $format = "$method()";
213
            } else {
214
                $stopRecursion = $this->findAnnotation(Annotation\JsonRecursive::class) ? '' : 'true';
215
                $format = "jsonSerialize($stopRecursion)";
216
            }
217
        }
218
        /** @var Annotation\JsonKey|null $jsonKey */
219
        $jsonKey = $this->findAnnotation(Annotation\JsonKey::class);
220
        $index = $jsonKey ? $jsonKey->key : $this->namingStrategy->getJsonProperty($this);
221
        $getter = $this->getGetterName();
222
        if (!$this->isCompulsory()) {
223
            $code = "\$array['$index'] = (\$object = \$this->$getter()) ? \$object->$format : null;";
224
        } else {
225
            $code = "\$array['$index'] = \$this->$getter()->$format;";
226
        }
227
        if (!$isIncluded) {
228
            $code = <<<PHP
229
if (!\$stopRecursion) {
230
    $code
231
};
232
PHP;
233
        }
234
        return $code;
235
    }
236
237
    /**
238
     * The code to past in the __clone method.
239
     * @return null|string
240
     */
241
    public function getCloneRule(): ?string
242
    {
243
        return null;
244
    }
245
246
    /**
247
     * Tells if this property is a type-hintable in PHP (resource isn't for example)
248
     *
249
     * @return bool
250
     */
251
    public function isTypeHintable(): bool
252
    {
253
        return true;
254
    }
255
256
    private function isGetterProtected(): bool
257
    {
258
        return $this->findAnnotation(Annotation\ProtectedGetter::class) !== null;
259
    }
260
261
    private function isSetterProtected(): bool
262
    {
263
        return $this->findAnnotation(Annotation\ProtectedSetter::class) !== null;
264
    }
265
266
    /**
267
     * @param string $type
268
     * @return null|object
269
     */
270
    private function findAnnotation(string $type)
271
    {
272
        foreach ($this->getAnnotations() as $annotations) {
273
            $annotation = $annotations->findAnnotation($type);
274
            if ($annotation !== null) {
275
                return $annotation;
276
            }
277
        }
278
        return null;
279
    }
280
}
281