MysqliMetadataReader::readColumnsMetadata()   F
last analyzed

Complexity

Conditions 18
Paths 867

Size

Total Lines 90

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 47
CRAP Score 18.0219

Importance

Changes 0
Metric Value
dl 0
loc 90
ccs 47
cts 49
cp 0.9592
rs 0.9259
c 0
b 0
f 0
cc 18
nc 867
nop 1
crap 18.0219

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Soluble\Metadata\Reader;
6
7
use Soluble\Metadata\ColumnsMetadata;
8
use Soluble\Metadata\Exception;
9
use Soluble\Metadata\Reader\Capability\ReaderCapabilityInterface;
10
use Soluble\Metadata\Reader\Mapping\MysqliMapping;
11
use Soluble\Datatype\Column;
12
13
class MysqliMetadataReader extends AbstractMetadataReader
14
{
15
    /**
16
     * @var \mysqli
17
     */
18
    protected $mysqli;
19
20
    /**
21
     * @var array
22
     */
23
    protected static $metadata_cache = [];
24
25
    /**
26
     * @param \mysqli $mysqli
27
     */
28 15
    public function __construct(\mysqli $mysqli)
29
    {
30 15
        $this->mysqli = $mysqli;
31 15
        $this->setupCapabilities();
32 15
    }
33
34 15
    protected function setupCapabilities(): void
35
    {
36
        $caps = [
37 15
            ReaderCapabilityInterface::DETECT_GROUP_FUNCTION,
38 15
            ReaderCapabilityInterface::DETECT_PRIMARY_KEY,
39 15
            ReaderCapabilityInterface::DETECT_NUMERIC_UNSIGNED,
40 15
            ReaderCapabilityInterface::DETECT_AUTOINCREMENT,
41
            //ReaderCapabilityInterface::DETECT_COLUMN_DEFAULT,
42
            //ReaderCapabilityInterface::DETECT_CHAR_MAX_LENGTH,
43
        ];
44 15
        foreach ($caps as $cap) {
45 15
            $this->addCapability($cap);
46
        }
47 15
    }
48
49
    /**
50
     * {@inheritdoc}
51
     *
52
     * @throws \Soluble\Metadata\Exception\ConnectionException
53
     * @throws \Soluble\Metadata\Exception\EmptyQueryException
54
     * @throws \Soluble\Metadata\Exception\InvalidQueryException
55
     */
56 8
    protected function readColumnsMetadata(string $sql): ColumnsMetadata
57
    {
58 8
        $metadata = new ColumnsMetadata();
59 8
        $fields = $this->readFields($sql);
60 4
        $type_map = MysqliMapping::getDatatypeMapping();
61
62 4
        foreach ($fields as $idx => $field) {
63 4
            $name = $field->orgname === '' ? $field->name : $field->orgname;
64 4
            $tableName = $field->orgtable;
65 4
            $schemaName = $field->db;
66
67 4
            $type = $field->type;
68
69 4
            if (!$type_map->offsetExists($type)) {
70
                $msg = "Cannot get type for field '$name'. Mapping for native type [$type] cannot be resolved into a valid type for driver: " . __CLASS__;
71
                throw new Exception\UnsupportedTypeException($msg);
72
            }
73
74 4
            $datatype = $type_map->offsetGet($type);
75
76 4
            $column = Column\Type::createColumnDefinition($datatype['type'], $name, $tableName, $schemaName);
77
78 4
            $column->setAlias($field->name);
79 4
            $column->setTableAlias($field->table);
80 4
            $column->setCatalog($field->catalog);
81 4
            $column->setOrdinalPosition($idx + 1);
82 4
            $column->setDataType($datatype['type']);
83 4
            $column->setIsNullable(!(($field->flags & MYSQLI_NOT_NULL_FLAG) > 0) && ($field->orgtable !== '' || $field->orgtable !== null));
84 4
            $column->setIsPrimary(($field->flags & MYSQLI_PRI_KEY_FLAG) > 0);
85
86 4
            $column->setColumnDefault($field->def);
87
88 4
            if (($field->flags & MYSQLI_SET_FLAG) > 0) {
89 1
                $column->setNativeDataType('SET');
90 4
            } elseif (($field->flags & MYSQLI_ENUM_FLAG) > 0) {
91 1
                $column->setNativeDataType('ENUM');
92
            } else {
93 4
                $column->setNativeDataType($datatype['native']);
94
            }
95
96 4
            if ($field->table === '' || $field->table === null) {
97 1
                $column->setIsGroup(($field->flags & MYSQLI_GROUP_FLAG) > 0);
98
            }
99
100 4
            if ($column instanceof Column\Definition\NumericColumnInterface) {
101 4
                $column->setNumericUnsigned(($field->flags & MYSQLI_UNSIGNED_FLAG) > 0);
102
            }
103
104 4
            if ($column instanceof Column\Definition\IntegerColumn) {
105 4
                $column->setIsAutoIncrement(($field->flags & MYSQLI_AUTO_INCREMENT_FLAG) > 0);
106 3
            } elseif ($column instanceof Column\Definition\DecimalColumn) {
107
                // salary DECIMAL(5,2)
108
                // In this example, 5 is the precision and 2 is the scale.
109
                // Standard SQL requires that DECIMAL(5,2) be able to store any value
110
                // with five digits and two decimals, so values that can be stored in
111
                // the salary column range from -999.99 to 999.99.
112 2
                $column->setNumericScale((int) ($field->length - $field->decimals + 1));
113 2
                $column->setNumericPrecision($field->decimals);
114
            }
115
116 4
            if ($column instanceof Column\Definition\StringColumn) {
117 3
                $column->setCharacterMaximumLength($field->length);
118
            }
119
120 4
            if ($column instanceof Column\Definition\BlobColumn) {
121 1
                $column->setCharacterOctetLength($field->length);
122
            }
123
124 4
            $alias = $column->getAlias();
125
126 4
            if ($metadata->offsetExists($alias)) {
127 1
                $prev_column = $metadata->offsetGet($alias);
128 1
                $prev_def = $prev_column->toArray();
129 1
                $curr_def = $column->toArray();
130 1
                if ($prev_def['dataType'] !== $curr_def['dataType'] || $prev_def['nativeDataType'] !== $curr_def['nativeDataType']) {
131 1
                    throw new Exception\AmbiguousColumnException("Cannot get column metadata, non unique column found '$alias' in query with different definitions.");
132
                }
133
134
                // If the the previous definition, was a prev_def
135
                /*
136
                if ($prev_def['isPrimary']) {
137
                    $column = $prev_column;
138
                }*/
139
            }
140
141 4
            $metadata->offsetSet($alias, $column);
142
        }
143
144 3
        return $metadata;
145
    }
146
147
    /**
148
     * @throws Exception\ConnectionException
149
     * @throws \Soluble\Metadata\Exception\EmptyQueryException
150
     * @throws \Soluble\Metadata\Exception\InvalidQueryException
151
     *
152
     * @return array<int, mixed>
0 ignored issues
show
Documentation introduced by
The doc-type array<int, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
153
     */
154 8
    protected function readFields(string $sql): array
155
    {
156 8
        if (trim($sql) === '') {
157 1
            throw new Exception\EmptyQueryException('Cannot read fields for an empty query');
158
        }
159
160 7
        $sql = $this->getEmptiedQuery($sql);
161 7
        $stmt = $this->mysqli->prepare($sql);
162
163 7
        if ($stmt === false) {
164 3
            $message = $this->mysqli->error;
165 3
            throw new Exception\InvalidQueryException(
166 3
                sprintf('Invalid query: %s (%s)', $sql, $message)
167
            );
168
        }
169 4
        $stmt->execute();
170
171 4
        $result = $stmt->result_metadata();
172 4
        if ($result === false) {
173
            $message = $this->mysqli->error;
174
            throw new Exception\InvalidQueryException(
175
                sprintf('Cannot get metadata: %s (%s)', $sql, $message)
176
            );
177
        }
178
179 4
        $metaFields = $result->fetch_fields();
180
181 4
        if ($metaFields === false) {
182
            $result->close();
183
            $message = $this->mysqli->error;
184
            throw new Exception\InvalidQueryException(
185
                sprintf('Cannot fetch metadata fields: %s (%s)', $sql, $message)
186
            );
187
        }
188
189 4
        $result->close();
190 4
        $stmt->close();
191
192 4
        return $metaFields;
193
    }
194
}
195