Passed
Pull Request — master (#75)
by David
02:46
created

ScalarBeanPropertyDescriptor::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
3
namespace TheCodingMachine\TDBM\Utils;
4
5
use Doctrine\DBAL\Platforms\MySQL57Platform;
6
use Doctrine\DBAL\Schema\Column;
7
use Doctrine\DBAL\Schema\Table;
8
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
9
use Doctrine\DBAL\Types\DateTimeImmutableType;
10
use Doctrine\DBAL\Types\DateTimeType;
11
use Doctrine\DBAL\Types\Type;
12
use Ramsey\Uuid\Uuid;
13
use TheCodingMachine\TDBM\TDBMException;
14
use TheCodingMachine\TDBM\Utils\Annotation\Annotation;
15
use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser;
16
use TheCodingMachine\TDBM\Utils\Annotation\Annotations;
17
18
/**
19
 * This class represent a property in a bean (a property has a getter, a setter, etc...).
20
 */
21
class ScalarBeanPropertyDescriptor extends AbstractBeanPropertyDescriptor
22
{
23
    /**
24
     * @var Column
25
     */
26
    private $column;
27
28
    /**
29
     * @var Annotations
30
     */
31
    private $annotations;
32
33
    /**
34
     * ScalarBeanPropertyDescriptor constructor.
35
     * @param Table $table
36
     * @param Column $column
37
     * @param NamingStrategyInterface $namingStrategy
38
     */
39
    public function __construct(Table $table, Column $column, NamingStrategyInterface $namingStrategy)
40
    {
41
        parent::__construct($table, $namingStrategy);
42
        $this->table = $table;
43
        $this->column = $column;
44
    }
45
46
    /**
47
     * Returns the param annotation for this property (useful for constructor).
48
     *
49
     * @return string
50
     */
51
    public function getParamAnnotation()
52
    {
53
        $paramType = $this->getPhpType();
54
55
        $str = '     * @param %s %s';
56
57
        return sprintf($str, $paramType, $this->getVariableName());
58
    }
59
60
    /**
61
     * Returns the name of the class linked to this property or null if this is not a foreign key.
62
     *
63
     * @return null|string
64
     */
65
    public function getClassName(): ?string
66
    {
67
        return null;
68
    }
69
70
    /**
71
     * Returns the PHP type for the property (it can be a scalar like int, bool, or class names, like \DateTimeInterface, App\Bean\User....)
72
     *
73
     * @return string
74
     */
75
    public function getPhpType(): string
76
    {
77
        $type = $this->column->getType();
78
        return TDBMDaoGenerator::dbalTypeToPhpType($type);
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()
87
    {
88
        return $this->column->getNotnull() && !$this->isAutoincrement() && $this->column->getDefault() === null && !$this->hasUuidAnnotation();
89
    }
90
91
    private function isAutoincrement() : bool
92
    {
93
        return $this->column->getAutoincrement() || $this->getAutoincrementAnnotation() !== null;
94
    }
95
96
    private function hasUuidAnnotation(): bool
97
    {
98
        return $this->getUuidAnnotation() !== null;
99
    }
100
101
    private function getUuidAnnotation(): ?Annotation
102
    {
103
        return $this->getAnnotations()->findAnnotation('UUID');
104
    }
105
106
    private function getAutoincrementAnnotation(): ?Annotation
107
    {
108
        return $this->getAnnotations()->findAnnotation('Autoincrement');
109
    }
110
111
    private function getAnnotations(): Annotations
112
    {
113
        if ($this->annotations === null) {
114
            $comment = $this->column->getComment();
115
            if ($comment === null) {
116
                return new Annotations([]);
117
            }
118
            $parser = new AnnotationParser();
119
            $this->annotations = $parser->parse($comment);
120
        }
121
        return $this->annotations;
122
    }
123
124
    /**
125
     * Returns true if the property has a default value (or if the @UUID annotation is set for the column)
126
     *
127
     * @return bool
128
     */
129
    public function hasDefault()
130
    {
131
        // MariaDB 10.3 issue: it returns "NULL" (the string) instead of *null*
132
        return ($this->column->getDefault() !== null && $this->column->getDefault() !== 'NULL') || $this->hasUuidAnnotation();
133
    }
134
135
    /**
136
     * Returns the code that assigns a value to its default value.
137
     *
138
     * @return string
139
     */
140
    public function assignToDefaultCode()
141
    {
142
        $str = '        $this->%s(%s);';
143
144
        $uuidAnnotation = $this->getUuidAnnotation();
145
        if ($uuidAnnotation !== null) {
146
            $defaultCode = $this->getUuidCode($uuidAnnotation);
147
        } else {
148
            $default = $this->column->getDefault();
149
            $type = $this->column->getType();
150
151
            if (in_array($type->getName(), [
152
                'datetime',
153
                'datetime_immutable',
154
                'datetimetz',
155
                'datetimetz_immutable',
156
                'date',
157
                'date_immutable',
158
                'time',
159
                'time_immutable',
160
            ], true)) {
161
                if (in_array(strtoupper($default), ['CURRENT_TIMESTAMP' /* MySQL */, 'NOW()' /* PostgreSQL */, 'SYSDATE' /* Oracle */ , 'CURRENT_TIMESTAMP()' /* MariaDB 10.3 */], true)) {
162
                    $defaultCode = 'new \DateTimeImmutable()';
163
                } else {
164
                    throw new TDBMException('Unable to set default value for date. Database passed this default value: "'.$default.'"');
165
                }
166
            } else {
167
                $defaultValue = $type->convertToPHPValue($this->column->getDefault(), new MySQL57Platform());
168
                $defaultCode = var_export($defaultValue, true);
169
            }
170
        }
171
172
        return sprintf($str, $this->getSetterName(), $defaultCode);
173
    }
174
175
    private function getUuidCode(Annotation $uuidAnnotation): string
176
    {
177
        $comment = trim($uuidAnnotation->getAnnotationComment(), '\'"');
178
        switch ($comment) {
179
            case '':
180
            case 'v1':
181
                return '(string) Uuid::uuid1()';
182
            case 'v4':
183
                return '(string) Uuid::uuid4()';
184
            default:
185
                throw new TDBMException('@UUID annotation accepts either "v1" or "v4" parameter. Unexpected parameter: ' . $comment);
186
        }
187
    }
188
189
    /**
190
     * Returns true if the property is the primary key.
191
     *
192
     * @return bool
193
     */
194
    public function isPrimaryKey(): bool
195
    {
196
        $primaryKey = $this->table->getPrimaryKey();
197
        if ($primaryKey === null) {
198
            return false;
199
        }
200
        return in_array($this->column->getName(), $primaryKey->getUnquotedColumns());
201
    }
202
203
    /**
204
     * Returns the PHP code for getters and setters.
205
     *
206
     * @return string
207
     */
208
    public function getGetterSetterCode()
209
    {
210
        $normalizedType = $this->getPhpType();
211
212
        $columnGetterName = $this->getGetterName();
213
        $columnSetterName = $this->getSetterName();
214
215
        // A column type can be forced if it is not nullable and not auto-incrementable (for auto-increment columns, we can get "null" as long as the bean is not saved).
216
        $isNullable = !$this->column->getNotnull() || $this->isAutoincrement();
217
218
        $resourceTypeCheck = '';
219
        if ($normalizedType === 'resource') {
220
            $resourceTypeCheck .= <<<EOF
221
222
        if (!\is_resource($%s)) {
223
            throw \TheCodingMachine\TDBM\TDBMInvalidArgumentException::badType('resource', $%s, __METHOD__);
224
        }
225
EOF;
226
            $resourceTypeCheck = sprintf($resourceTypeCheck, $this->column->getName(), $this->column->getName());
227
        }
228
229
        $getterAndSetterCode = '    /**
230
     * The getter for the "%s" column.
231
     *
232
     * @return %s
233
     */
234
    public function %s()%s%s%s
235
    {
236
        return $this->get(%s, %s);
237
    }
238
239
    /**
240
     * The setter for the "%s" column.
241
     *
242
     * @param %s $%s
243
     */
244
    public function %s(%s%s$%s) : void
245
    {%s
246
        $this->set(%s, $%s, %s);
247
    }
248
249
';
250
251
        return sprintf(
252
            $getterAndSetterCode,
253
            // Getter
254
            $this->column->getName(),
255
            $normalizedType.($isNullable ? '|null' : ''),
256
            $columnGetterName,
257
            ($this->isTypeHintable() ? ' : ' : ''),
258
            ($isNullable && $this->isTypeHintable() ? '?' : ''),
259
            ($this->isTypeHintable() ? $normalizedType: ''),
260
            var_export($this->column->getName(), true),
261
            var_export($this->table->getName(), true),
262
            // Setter
263
            $this->column->getName(),
264
            $normalizedType.(($this->column->getNotnull() || !$this->isTypeHintable()) ? '' : '|null'),
265
            $this->column->getName(),
266
            $columnSetterName,
267
            ($this->column->getNotnull() || !$this->isTypeHintable()) ? '' : '?',
268
            $this->isTypeHintable() ? $normalizedType . ' ' : '',
269
                //$castTo,
270
            $this->column->getName(),
271
            $resourceTypeCheck,
272
            var_export($this->column->getName(), true),
273
            $this->column->getName(),
274
            var_export($this->table->getName(), true)
275
        );
276
    }
277
278
    /**
279
     * Returns the part of code useful when doing json serialization.
280
     *
281
     * @return string
282
     */
283
    public function getJsonSerializeCode()
284
    {
285
        $normalizedType = $this->getPhpType();
286
287
        if (!$this->canBeSerialized()) {
288
            return '';
289
        }
290
291
        if ($normalizedType == '\\DateTimeImmutable') {
292
            if ($this->column->getNotnull()) {
293
                return '        $array['.var_export($this->namingStrategy->getJsonProperty($this), true).'] = $this->'.$this->getGetterName()."()->format('c');\n";
294
            } else {
295
                return '        $array['.var_export($this->namingStrategy->getJsonProperty($this), true).'] = ($this->'.$this->getGetterName().'() === null) ? null : $this->'.$this->getGetterName()."()->format('c');\n";
296
            }
297
        } else {
298
            return '        $array['.var_export($this->namingStrategy->getJsonProperty($this), true).'] = $this->'.$this->getGetterName()."();\n";
299
        }
300
    }
301
302
    /**
303
     * Returns the column name.
304
     *
305
     * @return string
306
     */
307
    public function getColumnName()
308
    {
309
        return $this->column->getName();
310
    }
311
312
    /**
313
     * The code to past in the __clone method.
314
     * @return null|string
315
     */
316
    public function getCloneRule(): ?string
317
    {
318
        $uuidAnnotation = $this->getUuidAnnotation();
319
        if ($uuidAnnotation !== null && $this->isPrimaryKey()) {
320
            return sprintf("        \$this->%s(%s);\n", $this->getSetterName(), $this->getUuidCode($uuidAnnotation));
321
        }
322
        return null;
323
    }
324
325
    /**
326
     * tells is this type is suitable for Json Serialization
327
     *
328
     * @return bool
329
     */
330
    public function canBeSerialized() : bool
331
    {
332
        $type = $this->column->getType();
333
334
        $unserialisableTypes = [
335
            Type::BLOB,
336
            Type::BINARY
337
        ];
338
339
        return \in_array($type->getName(), $unserialisableTypes, true) === false;
340
    }
341
342
    /**
343
     * Tells if this property is a type-hintable in PHP (resource isn't for example)
344
     *
345
     * @return bool
346
     */
347
    public function isTypeHintable() : bool
348
    {
349
        $type = $this->getPhpType();
350
        $invalidScalarTypes = [
351
            'resource'
352
        ];
353
354
        return \in_array($type, $invalidScalarTypes, true) === false;
355
    }
356
}
357