Completed
Push — master ( 58f7a7...7b934b )
by David
9s
created

ScalarBeanPropertyDescriptor   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 329
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 50
dl 0
loc 329
rs 8.6206
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A hasDefault() 0 4 3
A getForeignKey() 0 3 1
A isPrimaryKey() 0 3 1
A getPhpType() 0 4 1
B assignToDefaultCode() 0 32 4
A isAutoincrement() 0 3 2
A hasUuidAnnotation() 0 3 1
A getUuidCode() 0 11 4
A getClassName() 0 3 1
A getParamAnnotation() 0 7 1
A isCompulsory() 0 3 4
A getAutoincrementAnnotation() 0 3 1
A __construct() 0 5 1
A getUuidAnnotation() 0 3 1
A getAnnotations() 0 11 3
A getColumnName() 0 3 1
C getGetterSetterCode() 0 67 12
A getCloneRule() 0 7 3
A isTypeHintable() 0 8 1
A getJsonSerializeCode() 0 12 3
A canBeSerialized() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like ScalarBeanPropertyDescriptor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ScalarBeanPropertyDescriptor, and based on these observations, apply Extract Interface, too.

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