MapperVisitor   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 374
Duplicated Lines 0 %

Test Coverage

Coverage 68.7%

Importance

Changes 0
Metric Value
wmc 43
eloc 157
dl 0
loc 374
ccs 79
cts 115
cp 0.687
rs 8.96
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A acceptSchema() 0 3 1
A __construct() 0 5 3
A getOutput() 0 9 2
A createOutput() 0 14 2
A acceptIndex() 0 17 3
A write() 0 6 2
A getIndexOutput() 0 17 2
B acceptTable() 0 36 7
A acceptSequence() 0 2 1
F acceptColumn() 0 87 18
A getSequenceOutput() 0 16 2

How to fix   Complexity   

Complex Class

Complex classes like MapperVisitor 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 MapperVisitor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Bdf\Prime\Schema\Visitor;
4
5
use Bdf\Prime\Mapper\NameResolver\ResolverInterface;
6
use Bdf\Prime\Mapper\NameResolver\SuffixResolver;
7
use Bdf\Prime\Schema\Inflector\InflectorInterface;
8
use Bdf\Prime\Schema\Inflector\SimpleInfector;
9
use Bdf\Prime\Types\TypeInterface;
10
use Doctrine\DBAL\Schema\Column;
11
use Doctrine\DBAL\Schema\Index;
12
use Doctrine\DBAL\Schema\Schema;
13
use Doctrine\DBAL\Schema\Sequence;
14
use Doctrine\DBAL\Schema\Table;
15
use Doctrine\DBAL\Schema\Visitor\AbstractVisitor;
16
17
/**
18
 * Create a mapper output from a Schema.
19
 */
20
class MapperVisitor extends AbstractVisitor
0 ignored issues
show
Deprecated Code introduced by
The class Doctrine\DBAL\Schema\Visitor\AbstractVisitor has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

20
class MapperVisitor extends /** @scrutinizer ignore-deprecated */ AbstractVisitor
Loading history...
21
{
22
    /**
23
     * The connection name
24
     *
25
     * @var string|null
26
     */
27
    private $connectionName;
28
29
    /**
30
     * The mappers string representation
31
     *
32
     * @var array<string, array{
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, array{ at position 6 could not be parsed: the token is null at position 6.
Loading history...
33
     *    class:class-string,
34
     *    table:string,
35
     *    primaries:array<string,'autoincrement'|'sequence'|'primary'>,
36
     *    sequence:string|null,
37
     *    properties:array,
38
     *    indexes:array
39
     * }>
40
     */
41
    private $mappers = [];
42
43
    /**
44
     * The doctrine schema
45
     *
46
     * @var Schema
47
     */
48
    private $schema;
49
50
    /**
51
     * The stubs content
52
     *
53
     * @var string
54
     */
55
    private $prototype;
56
57
    /**
58
     * The mapper name resolver
59
     *
60
     * @var ResolverInterface
61
     */
62
    private $nameResolver;
63
64
    /**
65
     * The stubs content
66
     *
67
     * @var InflectorInterface
68
     */
69
    private $inflector;
70
71
    /**
72
     * The mapping between db types and builder methods
73
     *
74
     * @var array
75
     */
76
    private $typeAlias = [
77
        TypeInterface::STRING => 'string',
78
        TypeInterface::TEXT => 'text',
79
        TypeInterface::INTEGER => 'integer',
80
        TypeInterface::BIGINT => 'bigint',
81
        TypeInterface::SMALLINT => 'smallint',
82
        TypeInterface::TINYINT => 'tinyint',
83
        TypeInterface::FLOAT => 'float',
84
        TypeInterface::DOUBLE => 'double',
85
        TypeInterface::DECIMAL => 'decimal',
86
        TypeInterface::BOOLEAN => 'boolean',
87
        TypeInterface::DATE => 'date',
88
        TypeInterface::DATETIME => 'dateTime',
89
        TypeInterface::DATETIMETZ => 'dateTimeTz',
90
        TypeInterface::TIME => 'time',
91
        TypeInterface::TIMESTAMP => 'timestamp',
92
        TypeInterface::BINARY => 'binary',
93
        TypeInterface::BLOB => 'blob',
94
        TypeInterface::GUID => 'guid',
95
        TypeInterface::JSON => 'json',
96
        TypeInterface::TARRAY => 'simpleArray',
97
        TypeInterface::ARRAY_OBJECT => 'arrayObject',
98
        TypeInterface::OBJECT => 'object',
99
    ];
100
101
    /**
102
     * MapperVisitor constructor.
103
     *
104
     * @param string|null $connectionName
105
     * @param ResolverInterface|null $nameResolver
106
     * @param InflectorInterface|null $inflector
107
     */
108 1
    public function __construct($connectionName = null, ResolverInterface $nameResolver = null, InflectorInterface $inflector = null)
109
    {
110 1
        $this->connectionName = $connectionName;
111 1
        $this->nameResolver = $nameResolver ?: new SuffixResolver();
112 1
        $this->inflector = $inflector ?: new SimpleInfector();
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118 1
    public function acceptSchema(Schema $schema)
119
    {
120 1
        $this->schema = $schema;
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126 1
    public function acceptTable(Table $table)
127
    {
128 1
        $primaries = [];
129 1
        $sequence = null;
130 1
        $tableName = $table->getName();
131
132
        // Evaluate metadata for primary keys
133 1
        if ($table->hasPrimaryKey()) {
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Schema\Table::hasPrimaryKey() has been deprecated: Use {@see getPrimaryKey()} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

133
        if (/** @scrutinizer ignore-deprecated */ $table->hasPrimaryKey()) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
134
            // prepare sequence info for the method Mapper::sequence() and the metadata primary
135 1
            $sequence = $this->inflector->getSequenceName($tableName);
136 1
            if (!$this->schema->hasTable($sequence)) {
137 1
                $sequence = null;
138
            }
139
140
            // get the type of primary
141 1
            foreach ($table->getPrimaryKeyColumns() as $primary) {
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Schema\Table::getPrimaryKeyColumns() has been deprecated: Use {@see getPrimaryKey()} and {@see Index::getColumns()} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

141
            foreach (/** @scrutinizer ignore-deprecated */ $table->getPrimaryKeyColumns() as $primary) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
142 1
                $primary = $primary->getName();
143 1
                $column = $table->getColumn($primary);
144
145 1
                if ($column->getAutoincrement()) {
146 1
                    $primaries[$primary] = 'autoincrement';
147
                } elseif ($sequence !== null && empty($primaries)) {
148
                    $primaries[$primary] = 'sequence';
149
                } else {
150
                    $primaries[$primary] = 'primary';
151
                }
152
            }
153
        }
154
155 1
        $this->mappers[$tableName] = [
156 1
            'class'      => $this->nameResolver->resolve($this->inflector->getClassName($tableName)),
157 1
            'table'      => $tableName,
158 1
            'primaries'  => $primaries,
159 1
            'sequence'   => $sequence,
160 1
            'properties' => [],
161 1
            'indexes'    => [],
162 1
        ];
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168 1
    public function acceptColumn(Table $table, Column $column)
169
    {
170 1
        $field = $column->getName();
171 1
        $type = $column->getType()->getName();
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Types\Type::getName() has been deprecated: this method will be removed in Doctrine DBAL 4.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

171
        $type = /** @scrutinizer ignore-deprecated */ $column->getType()->getName();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
172 1
        $default = $column->getDefault();
173 1
        $length = $column->getLength();
174 1
        $tableName = $table->getName();
175
176 1
        $property = $this->inflector->getPropertyName($tableName, $field);
177 1
        $primaries = $this->mappers[$tableName]['primaries'];
178
179
        // default
180 1
        if ($default !== null) {
181
            switch ($type) {
182
                case TypeInterface::BOOLEAN:
183
                    $default = (bool)$default;
184
                    break;
185
186
                case TypeInterface::TINYINT:
187
                case TypeInterface::SMALLINT:
188
                case TypeInterface::INTEGER:
189
                    $default = (int)$default;
190
                    break;
191
192
                case TypeInterface::FLOAT:
193
                    $default = (float)$default;
194
                    break;
195
196
                case TypeInterface::DOUBLE:
197
                    $default = (float)$default;
198
                    break;
199
            }
200
201
            $default = ', '.var_export($default, true);
202
        }
203
204
        // type
205 1
        if ($type === TypeInterface::STRING) {
206 1
            $builder = "\$builder->string('$property', {$length}$default)";
207
            // string method has a length property.
208
            // We set the length to null to avoid the call of length() method
209 1
            $length = null;
210 1
        } elseif (isset($this->typeAlias[$type])) {
211 1
            $type = $this->typeAlias[$type];
212 1
            $builder = "\$builder->$type('$property'$default)";
213
        } else {
214
            $builder = "\$builder->add('$property', '$type'$default)";
215
        }
216
217
        // primary
218 1
        if (isset($primaries[$field])) {
219 1
            $builder .= "->{$primaries[$field]}()";
220
        }
221
222
        // TODO unique
223
224
        // length
225 1
        if ($length !== null) {
226
            $builder .= "->length($length)";
227
        }
228
229
        // nillable
230 1
        if (!$column->getNotnull()) {
231 1
            $builder .= "->nillable()";
232
        }
233
234
        // unsigned
235 1
        if ($column->getUnsigned()) {
236
            $builder .= "->unsigned()";
237
        }
238
239
        // precision and scale
240 1
        if ($column->getPrecision() !== 10 || $column->getScale() !== 0) {
241
            $builder .= "->precision({$column->getPrecision()}, {$column->getScale()})";
242
        }
243
244
        // fixed
245 1
        if ($column->getFixed()) {
246
            $builder .= "->fixed()";
247
        }
248
249
        // alias
250 1
        if ($property !== $field) {
251 1
            $builder .= "->alias('$field')";
252
        }
253
254 1
        $this->mappers[$tableName]['properties'][] = $builder.';';
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260 1
    public function acceptIndex(Table $table, Index $index)
261
    {
262 1
        if (!$index->isSimpleIndex()) {
263 1
            return;
264
        }
265
266
        $columns = [];
267
        $tableName = $table->getName();
268
269
        foreach ($index->getColumns() as $column) {
270
            $columns[] = $this->inflector->getPropertyName($tableName, $column);
271
        }
272
273
        $name = $index->getName();
274
        $indexes = implode("', '", $columns);
275
276
        $this->mappers[$tableName]['indexes'][] = "'$name' => ['$indexes'],";
277
    }
278
279
    /**
280
     * {@inheritdoc}
281
     */
282
    public function acceptSequence(Sequence $sequence)
283
    {
284
    }
285
286
    /**
287
     * Get mappers output
288
     *
289
     * @return string
290
     */
291 1
    public function getOutput()
292
    {
293 1
        $output = '';
294
295 1
        foreach ($this->mappers as $metadata) {
296 1
            $output .= $this->createOutput($metadata).PHP_EOL;
297
        }
298
299 1
        return $output;
300
    }
301
302
    /**
303
     * Writes mappers files in path directory
304
     *
305
     * @param string $path
306
     *
307
     * @return void
308
     */
309
    public function write($path): void
310
    {
311
        $path = rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
312
313
        foreach ($this->mappers as $metadata) {
314
            file_put_contents($path.$metadata['class'].'.php', $this->createOutput($metadata));
315
        }
316
    }
317
318
    /**
319
     * Create output from table metadata
320
     *
321
     * @param array $metadata
322
     *
323
     * @return string
324
     */
325 1
    private function createOutput($metadata)
326
    {
327 1
        if ($this->prototype === null) {
328 1
            $this->prototype = file_get_contents(__DIR__.'/stubs/mapper.stub');
329
        }
330
331 1
        return strtr($this->prototype, [
332 1
            '<className>'  => $metadata['class'],
333 1
            '<connection>' => $this->connectionName,
334 1
            '<database>'   => $this->connectionName,
335 1
            '<tableName>'  => $metadata['table'],
336 1
            '<fields>'     => implode("\n        ", $metadata['properties']),
337 1
            '<sequence>'   => $this->getSequenceOutput($metadata['sequence']),
338 1
            '<indexes>'    => $this->getIndexOutput($metadata['indexes']),
339 1
        ]);
340
    }
341
342
    /**
343
     * Get the sequence output
344
     *
345
     * @param string $table
346
     *
347
     * @return string
348
     */
349 1
    private function getSequenceOutput($table)
350
    {
351 1
        if ($table === null) {
0 ignored issues
show
introduced by
The condition $table === null is always false.
Loading history...
352 1
            return '';
353
        }
354
355
        return <<<EOF
356
        
357
        
358
    /**
359
     * {@inheritdoc}
360
     */
361
    public function sequence(): array
362
    {
363
        return [
364
            'table' => {$table},
365
        ];
366
    }
367
EOF;
368
    }
369
370
    /**
371
     * Get the index output
372
     *
373
     * @param array $indexes
374
     *
375
     * @return string
376
     */
377 1
    private function getIndexOutput(array $indexes)
378
    {
379 1
        if (empty($indexes)) {
380 1
            return '';
381
        }
382
383
        $indexes = implode("\n            ", $indexes);
384
        return <<<EOF
385
        
386
        
387
    /**
388
     * {@inheritdoc}
389
     */
390
    public function indexes(): array
391
    {
392
        return [
393
            $indexes
394
        ];
395
    }
396
EOF;
397
    }
398
}
399