AbstractMySQLDriver   F
last analyzed

Complexity

Total Complexity 68

Size/Duplication

Total Lines 249
Duplicated Lines 0 %

Test Coverage

Coverage 97.17%

Importance

Changes 0
Metric Value
eloc 152
dl 0
loc 249
ccs 103
cts 106
cp 0.9717
rs 2.96
c 0
b 0
f 0
wmc 68

6 Methods

Rating   Name   Duplication   Size   Complexity  
A createDatabasePlatformForVersion() 0 19 6
A getOracleMysqlVersionNumber() 0 22 5
A getSchemaManager() 0 3 1
A getMariaDbMysqlVersionNumber() 0 14 2
A getDatabasePlatform() 0 3 1
D convertException() 0 79 53

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\DBAL\Driver;
6
7
use Doctrine\DBAL\Connection;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Doctrine\DBAL\Driver\Connection. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use Doctrine\DBAL\DBALException;
9
use Doctrine\DBAL\Driver\DriverException as DriverExceptionInterface;
10
use Doctrine\DBAL\Exception;
11
use Doctrine\DBAL\Exception\DriverException;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Doctrine\DBAL\Driver\DriverException. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
12
use Doctrine\DBAL\Platforms\AbstractPlatform;
13
use Doctrine\DBAL\Platforms\Exception\InvalidPlatformVersion;
14
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
15
use Doctrine\DBAL\Platforms\MySQL57Platform;
16
use Doctrine\DBAL\Platforms\MySQL80Platform;
17
use Doctrine\DBAL\Platforms\MySqlPlatform;
18
use Doctrine\DBAL\Schema\AbstractSchemaManager;
19
use Doctrine\DBAL\Schema\MySqlSchemaManager;
20
use Doctrine\DBAL\VersionAwarePlatformDriver;
21
use function preg_match;
22
use function stripos;
23
use function version_compare;
24
25
/**
26
 * Abstract base implementation of the {@link Doctrine\DBAL\Driver} interface for MySQL based drivers.
27
 */
28
abstract class AbstractMySQLDriver implements ExceptionConverterDriver, VersionAwarePlatformDriver
29
{
30
    /**#@+
31
     * MySQL server error codes.
32
     *
33
     * @link https://dev.mysql.com/doc/refman/8.0/en/server-error-reference.html
34
     */
35
    private const ER_DBACCESS_DENIED_ERROR            = 1044;
36
    private const ER_ACCESS_DENIED_ERROR              = 1045;
37
    private const ER_NO_DB_ERROR                      = 1046;
38
    private const ER_BAD_NULL_ERROR                   = 1048;
39
    private const ER_BAD_DB_ERROR                     = 1049;
40
    private const ER_TABLE_EXISTS_ERROR               = 1050;
41
    private const ER_BAD_TABLE_ERROR                  = 1051;
42
    private const ER_NON_UNIQ_ERROR                   = 1052;
43
    private const ER_BAD_FIELD_ERROR                  = 1054;
44
    private const ER_DUP_FIELDNAME                    = 1060;
45
    private const ER_DUP_ENTRY                        = 1062;
46
    private const ER_PARSE_ERROR                      = 1064;
47
    private const ER_KILL_DENIED_ERROR                = 1095;
48
    private const ER_FIELD_SPECIFIED_TWICE            = 1110;
49
    private const ER_NULL_COLUMN_IN_INDEX             = 1121;
50
    private const ER_INVALID_USE_OF_NULL              = 1138;
51
    private const ER_TABLEACCESS_DENIED_ERROR         = 1142;
52
    private const ER_COLUMNACCESS_DENIED_ERROR        = 1143;
53
    private const ER_NO_SUCH_TABLE                    = 1146;
54
    private const ER_SYNTAX_ERROR                     = 1149;
55
    private const ER_WRONG_COLUMN_NAME                = 1166;
56
    private const ER_PRIMARY_CANT_HAVE_NULL           = 1171;
57
    private const ER_LOCK_WAIT_TIMEOUT                = 1205;
58
    private const ER_LOCK_DEADLOCK                    = 1213;
59
    private const ER_NO_REFERENCED_ROW                = 1216;
60
    private const ER_ROW_IS_REFERENCED                = 1217;
61
    private const ER_SPECIFIC_ACCESS_DENIED_ERROR     = 1227;
62
    private const ER_SPATIAL_CANT_HAVE_NULL           = 1252;
63
    private const ER_WARN_NULL_TO_NOTNULL             = 1263;
64
    private const ER_WARN_DEPRECATED_SYNTAX           = 1287;
65
    private const ER_FPARSER_BAD_HEADER               = 1341;
66
    private const ER_FPARSER_EOF_IN_COMMENT           = 1342;
67
    private const ER_FPARSER_ERROR_IN_PARAMETER       = 1343;
68
    private const ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344;
69
    private const ER_NO_DEFAULT_FOR_FIELD             = 1364;
70
    private const ER_PROCACCESS_DENIED_ERROR          = 1370;
71
    private const ER_RESERVED_SYNTAX                  = 1382;
72
    private const ER_CONNECT_TO_FOREIGN_DATA_SOURCE   = 1429;
73
    private const ER_ROW_IS_REFERENCED_2              = 1451;
74
    private const ER_NO_REFERENCED_ROW_2              = 1452;
75
    private const ER_PARTITION_REQUIRES_VALUES_ERROR  = 1479;
76
    private const ER_EVENT_DROP_FAILED                = 1541;
77
    private const ER_WARN_DEPRECATED_SYNTAX_WITH_VER  = 1554;
78
    private const ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557;
79
    private const ER_NULL_IN_VALUES_LESS_THAN         = 1566;
80
    private const ER_DUP_ENTRY_AUTOINCREMENT_CASE     = 1569;
81
    private const ER_DUP_ENTRY_WITH_KEY_NAME          = 1586;
82
    private const ER_LOAD_DATA_INVALID_COLUMN         = 1611;
83
    private const ER_CONFLICT_FN_PARSE_ERROR          = 1626;
84
    private const ER_TRUNCATE_ILLEGAL_FK              = 1701;
85
    /**#@-*/
86
87
    /**#@+
88
     * MySQL client error codes.
89
     *
90
     * @link https://dev.mysql.com/doc/refman/8.0/en/client-error-reference.html
91
     */
92
    private const CR_CONNECTION_ERROR = 2002;
93
    private const CR_UNKNOWN_HOST     = 2005;
94
    /**#@-*/
95
96 2282
    public function convertException(string $message, DriverExceptionInterface $exception) : DriverException
97
    {
98 2282
        switch ($exception->getCode()) {
99 2282
            case self::ER_LOCK_DEADLOCK:
100 22
                return new Exception\DeadlockException($message, $exception);
101
102 2260
            case self::ER_LOCK_WAIT_TIMEOUT:
103 22
                return new Exception\LockWaitTimeoutException($message, $exception);
104
105 2238
            case self::ER_TABLE_EXISTS_ERROR:
106 142
                return new Exception\TableExistsException($message, $exception);
107
108 2096
            case self::ER_BAD_TABLE_ERROR:
109 1894
            case self::ER_NO_SUCH_TABLE:
110 924
                return new Exception\TableNotFoundException($message, $exception);
111
112 1860
            case self::ER_NO_REFERENCED_ROW:
113 1838
            case self::ER_ROW_IS_REFERENCED:
114 1816
            case self::ER_ROW_IS_REFERENCED_2:
115 1770
            case self::ER_NO_REFERENCED_ROW_2:
116 1736
            case self::ER_TRUNCATE_ILLEGAL_FK:
117 136
                return new Exception\ForeignKeyConstraintViolationException($message, $exception);
118
119 1724
            case self::ER_DUP_ENTRY:
120 1678
            case self::ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED:
121 1656
            case self::ER_DUP_ENTRY_AUTOINCREMENT_CASE:
122 1634
            case self::ER_DUP_ENTRY_WITH_KEY_NAME:
123 112
                return new Exception\UniqueConstraintViolationException($message, $exception);
124
125 1612
            case self::ER_BAD_FIELD_ERROR:
126 1578
            case self::ER_WRONG_COLUMN_NAME:
127 1556
            case self::ER_LOAD_DATA_INVALID_COLUMN:
128 78
                return new Exception\InvalidFieldNameException($message, $exception);
129
130 1534
            case self::ER_NON_UNIQ_ERROR:
131 1500
            case self::ER_DUP_FIELDNAME:
132 1478
            case self::ER_FIELD_SPECIFIED_TWICE:
133 78
                return new Exception\NonUniqueFieldNameException($message, $exception);
134
135 1456
            case self::ER_PARSE_ERROR:
136 1422
            case self::ER_SYNTAX_ERROR:
137 1400
            case self::ER_WARN_DEPRECATED_SYNTAX:
138 1378
            case self::ER_FPARSER_BAD_HEADER:
139 1356
            case self::ER_FPARSER_EOF_IN_COMMENT:
140 1334
            case self::ER_FPARSER_ERROR_IN_PARAMETER:
141 1312
            case self::ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER:
142 1290
            case self::ER_RESERVED_SYNTAX:
143 1268
            case self::ER_PARTITION_REQUIRES_VALUES_ERROR:
144 1246
            case self::ER_EVENT_DROP_FAILED:
145 1224
            case self::ER_WARN_DEPRECATED_SYNTAX_WITH_VER:
146 1202
            case self::ER_CONFLICT_FN_PARSE_ERROR:
147 276
                return new Exception\SyntaxErrorException($message, $exception);
148
149 1180
            case self::ER_DBACCESS_DENIED_ERROR:
150 1158
            case self::ER_ACCESS_DENIED_ERROR:
151 1114
            case self::ER_NO_DB_ERROR:
152 1092
            case self::ER_BAD_DB_ERROR:
153 1070
            case self::ER_KILL_DENIED_ERROR:
154 1048
            case self::ER_TABLEACCESS_DENIED_ERROR:
155 1026
            case self::ER_COLUMNACCESS_DENIED_ERROR:
156 1004
            case self::ER_SPECIFIC_ACCESS_DENIED_ERROR:
157 982
            case self::ER_PROCACCESS_DENIED_ERROR:
158 960
            case self::ER_CONNECT_TO_FOREIGN_DATA_SOURCE:
159 960
            case self::CR_CONNECTION_ERROR:
160 926
            case self::CR_UNKNOWN_HOST:
161 276
                return new Exception\ConnectionException($message, $exception);
162
163 904
            case self::ER_BAD_NULL_ERROR:
164 870
            case self::ER_NULL_COLUMN_IN_INDEX:
165 848
            case self::ER_INVALID_USE_OF_NULL:
166 826
            case self::ER_PRIMARY_CANT_HAVE_NULL:
167 804
            case self::ER_SPATIAL_CANT_HAVE_NULL:
168 782
            case self::ER_WARN_NULL_TO_NOTNULL:
169 760
            case self::ER_NO_DEFAULT_FOR_FIELD:
170 738
            case self::ER_NULL_IN_VALUES_LESS_THAN:
171 188
                return new Exception\NotNullConstraintViolationException($message, $exception);
172
        }
173
174 716
        return new DriverException($message, $exception);
175
    }
176
177
    /**
178
     * {@inheritdoc}
179
     *
180
     * @throws DBALException
181
     */
182 296
    public function createDatabasePlatformForVersion(string $version) : AbstractPlatform
183
    {
184 296
        $mariadb = stripos($version, 'mariadb') !== false;
185 296
        if ($mariadb && version_compare($this->getMariaDbMysqlVersionNumber($version), '10.2.7', '>=')) {
186 106
            return new MariaDb1027Platform();
187
        }
188
189 212
        if (! $mariadb) {
190 128
            $oracleMysqlVersion = $this->getOracleMysqlVersionNumber($version);
191 106
            if (version_compare($oracleMysqlVersion, '8', '>=')) {
192 64
                return new MySQL80Platform();
193
            }
194
195 64
            if (version_compare($oracleMysqlVersion, '5.7.9', '>=')) {
196 64
                return new MySQL57Platform();
197
            }
198
        }
199
200 106
        return $this->getDatabasePlatform();
201
    }
202
203
    /**
204
     * Get a normalized 'version number' from the server string
205
     * returned by Oracle MySQL servers.
206
     *
207
     * @param string $versionString Version string returned by the driver, i.e. '5.7.10'
208
     *
209
     * @throws DBALException
210
     */
211 128
    private function getOracleMysqlVersionNumber(string $versionString) : string
212
    {
213 128
        if (preg_match(
214 2
            '/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?)?/',
215 128
            $versionString,
216 128
            $versionParts
217 128
        ) === 0) {
218 22
            throw InvalidPlatformVersion::new(
219 1
                $versionString,
220 22
                '<major_version>.<minor_version>.<patch_version>'
221
            );
222
        }
223
224 106
        $majorVersion = $versionParts['major'];
225 106
        $minorVersion = $versionParts['minor'] ?? 0;
226 106
        $patchVersion = $versionParts['patch'] ?? null;
227
228 106
        if ($majorVersion === '5' && $minorVersion === '7' && $patchVersion === null) {
229 22
            $patchVersion = '9';
230
        }
231
232 106
        return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
233
    }
234
235
    /**
236
     * Detect MariaDB server version, including hack for some mariadb distributions
237
     * that starts with the prefix '5.5.5-'
238
     *
239
     * @param string $versionString Version string as returned by mariadb server, i.e. '5.5.5-Mariadb-10.0.8-xenial'
240
     *
241
     * @throws DBALException
242
     */
243 190
    private function getMariaDbMysqlVersionNumber(string $versionString) : string
244
    {
245 190
        if (preg_match(
246 1
            '/^(?:5\.5\.5-)?(mariadb-)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)/i',
247 190
            $versionString,
248 190
            $versionParts
249 190
        ) === 0) {
250
            throw InvalidPlatformVersion::new(
251
                $versionString,
252
                '^(?:5\.5\.5-)?(mariadb-)?<major_version>.<minor_version>.<patch_version>'
253
            );
254
        }
255
256 190
        return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch'];
257
    }
258
259
    /**
260
     * {@inheritdoc}
261
     *
262
     * @return MySqlPlatform
263
     */
264 128
    public function getDatabasePlatform() : AbstractPlatform
265
    {
266 128
        return new MySqlPlatform();
267
    }
268
269
    /**
270
     * {@inheritdoc}
271
     *
272
     * @return MySqlSchemaManager
273
     */
274 154
    public function getSchemaManager(Connection $conn) : AbstractSchemaManager
275
    {
276 154
        return new MySqlSchemaManager($conn);
277
    }
278
}
279