Passed
Pull Request — master (#70)
by Kevin
02:30
created

ScalarBeanPropertyDescriptor   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 317
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 49
dl 0
loc 317
rs 8.5454
c 0
b 0
f 0

22 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
A getCloneRule() 0 7 3
A isValidScalarType() 0 4 1
A getJsonSerializeCode() 0 12 3
A canBeSerialized() 0 4 1
C getGetterSetterCode() 0 55 10
A forceSetAsString() 0 3 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
        $getterAndSetterCode = '    /**
222
     * The getter for the "%s" column.
223
     *
224
     * @return %s
225
     */
226
    public function %s() %s %s%s
227
    {
228
        return $this->get(%s, %s);
229
    }
230
231
    /**
232
     * The setter for the "%s" column.
233
     *
234
     * @param %s $%s
235
     */
236
    public function %s(%s%s $%s) : void
237
    {
238
        $this->set(%s, $%s, %s);
239
    }
240
241
';
242
243
        return sprintf(
244
            $getterAndSetterCode,
245
            // Getter
246
            $this->column->getName(),
247
            $normalizedType.($isNullable ? '|null' : ''),
248
            $columnGetterName,
249
            ($this->isValidScalarType() ? ':' : ''),
250
            ($isNullable && $this->isValidScalarType() ? '?' : ''),
251
            ($this->isValidScalarType() ? $normalizedType: ''),
252
            var_export($this->column->getName(), true),
253
            var_export($this->table->getName(), true),
254
            // Setter
255
            $this->column->getName(),
256
            $normalizedType.($isNullable ? '|null' : ''),
257
            $this->column->getName(),
258
            $columnSetterName,
259
            $this->column->getNotnull() ? '' : '?',
260
            (!$this->forceSetAsString() ? $normalizedType : "string"),
261
                //$castTo,
262
            $this->column->getName(),
263
            var_export($this->column->getName(), true),
264
            $this->column->getName(),
265
            var_export($this->table->getName(), true)
266
        );
267
    }
268
269
    /**
270
     * Returns the part of code useful when doing json serialization.
271
     *
272
     * @return string
273
     */
274
    public function getJsonSerializeCode()
275
    {
276
        $normalizedType = $this->getPhpType();
277
278
        if (!$this->canBeSerialized()){
279
            return '';
280
        }
281
282
        if ($normalizedType == '\\DateTimeImmutable') {
283
            return '        $array['.var_export($this->namingStrategy->getJsonProperty($this), true).'] = ($this->'.$this->getGetterName().'() === null) ? null : $this->'.$this->getGetterName()."()->format('c');\n";
284
        } else {
285
            return '        $array['.var_export($this->namingStrategy->getJsonProperty($this), true).'] = $this->'.$this->getGetterName()."();\n";
286
        }
287
    }
288
289
    /**
290
     * Returns the column name.
291
     *
292
     * @return string
293
     */
294
    public function getColumnName()
295
    {
296
        return $this->column->getName();
297
    }
298
299
    /**
300
     * The code to past in the __clone method.
301
     * @return null|string
302
     */
303
    public function getCloneRule(): ?string
304
    {
305
        $uuidAnnotation = $this->getUuidAnnotation();
306
        if ($uuidAnnotation !== null && $this->isPrimaryKey()) {
307
            return sprintf("        \$this->%s(%s);\n", $this->getSetterName(), $this->getUuidCode($uuidAnnotation));
308
        }
309
        return null;
310
    }
311
312
    /**
313
     * tells is this type is suitable for Json Serialization
314
     *
315
     * @return string
316
     */
317
    public function canBeSerialized() : string
318
    {
319
        $type = $this->column->getType();
320
        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...
321
    }
322
323
    /**
324
     * Tells is this type is a valid scalar type (resource isn't for example)
325
     * @return bool
326
     */
327
    public function isValidScalarType() : bool
328
    {
329
        $type = $this->getPhpType();
330
        return TDBMDaoGenerator::isValidScalarType($type);
331
    }
332
333
    public function forceSetAsString(){
334
        $type = $this->getPhpType();
335
        return $type === "resource";
336
    }
337
}
338