Completed
Push — master ( 8575c2...a53269 )
by Marco
02:31 queued 01:15
created

DriverManager::_checkParams()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 18
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 7

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
rs 8.2222
ccs 7
cts 7
cp 1
cc 7
eloc 6
nc 4
nop 1
crap 7
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL;
21
22
use Doctrine\Common\EventManager;
23
24
/**
25
 * Factory for creating Doctrine\DBAL\Connection instances.
26
 *
27
 * @author Roman Borschel <[email protected]>
28
 * @since 2.0
29
 */
30
final class DriverManager
31
{
32
    /**
33
     * List of supported drivers and their mappings to the driver classes.
34
     *
35
     * To add your own driver use the 'driverClass' parameter to
36
     * {@link DriverManager::getConnection()}.
37
     *
38
     * @var array
39
     */
40
     private static $_driverMap = [
41
         'pdo_mysql'          => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
42
         'pdo_sqlite'         => 'Doctrine\DBAL\Driver\PDOSqlite\Driver',
43
         'pdo_pgsql'          => 'Doctrine\DBAL\Driver\PDOPgSql\Driver',
44
         'pdo_oci'            => 'Doctrine\DBAL\Driver\PDOOracle\Driver',
45
         'oci8'               => 'Doctrine\DBAL\Driver\OCI8\Driver',
46
         'ibm_db2'            => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver',
47
         'pdo_sqlsrv'         => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver',
48
         'mysqli'             => 'Doctrine\DBAL\Driver\Mysqli\Driver',
49
         'drizzle_pdo_mysql'  => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver',
50
         'sqlanywhere'        => 'Doctrine\DBAL\Driver\SQLAnywhere\Driver',
51
         'sqlsrv'             => 'Doctrine\DBAL\Driver\SQLSrv\Driver',
52
    ];
53
54
    /**
55
     * List of URL schemes from a database URL and their mappings to driver.
56
     */
57
    private static $driverSchemeAliases = [
58
        'db2'        => 'ibm_db2',
59
        'mssql'      => 'pdo_sqlsrv',
60
        'mysql'      => 'pdo_mysql',
61
        'mysql2'     => 'pdo_mysql', // Amazon RDS, for some weird reason
62
        'postgres'   => 'pdo_pgsql',
63
        'postgresql' => 'pdo_pgsql',
64
        'pgsql'      => 'pdo_pgsql',
65
        'sqlite'     => 'pdo_sqlite',
66
        'sqlite3'    => 'pdo_sqlite',
67
    ];
68
69
    /**
70
     * Private constructor. This class cannot be instantiated.
71
     */
72
    private function __construct()
73
    {
74
    }
75
76
    /**
77
     * Creates a connection object based on the specified parameters.
78
     * This method returns a Doctrine\DBAL\Connection which wraps the underlying
79
     * driver connection.
80
     *
81
     * $params must contain at least one of the following.
82
     *
83
     * Either 'driver' with one of the following values:
84
     *
85
     *     pdo_mysql
86
     *     pdo_sqlite
87
     *     pdo_pgsql
88
     *     pdo_oci (unstable)
89
     *     pdo_sqlsrv
90
     *     pdo_sqlsrv
91
     *     mysqli
92
     *     sqlanywhere
93
     *     sqlsrv
94
     *     ibm_db2 (unstable)
95
     *     drizzle_pdo_mysql
96
     *
97
     * OR 'driverClass' that contains the full class name (with namespace) of the
98
     * driver class to instantiate.
99
     *
100
     * Other (optional) parameters:
101
     *
102
     * <b>user (string)</b>:
103
     * The username to use when connecting.
104
     *
105
     * <b>password (string)</b>:
106
     * The password to use when connecting.
107
     *
108
     * <b>driverOptions (array)</b>:
109
     * Any additional driver-specific options for the driver. These are just passed
110
     * through to the driver.
111
     *
112
     * <b>pdo</b>:
113
     * You can pass an existing PDO instance through this parameter. The PDO
114
     * instance will be wrapped in a Doctrine\DBAL\Connection.
115
     *
116
     * <b>wrapperClass</b>:
117
     * You may specify a custom wrapper class through the 'wrapperClass'
118
     * parameter but this class MUST inherit from Doctrine\DBAL\Connection.
119
     *
120
     * <b>driverClass</b>:
121
     * The driver class to use.
122
     *
123
     * @param array                              $params       The parameters.
124
     * @param \Doctrine\DBAL\Configuration|null  $config       The configuration to use.
125
     * @param \Doctrine\Common\EventManager|null $eventManager The event manager to use.
126
     *
127
     * @return \Doctrine\DBAL\Connection
128
     *
129
     * @throws \Doctrine\DBAL\DBALException
130
     */
131 138
    public static function getConnection(
132
            array $params,
133
            Configuration $config = null,
134
            EventManager $eventManager = null): Connection
135
    {
136
        // create default config and event manager, if not set
137 138
        if ( ! $config) {
138 133
            $config = new Configuration();
139
        }
140 138
        if ( ! $eventManager) {
141 133
            $eventManager = new EventManager();
142
        }
143
144 138
        $params = self::parseDatabaseUrl($params);
145
146
        // check for existing pdo object
147 135
        if (isset($params['pdo']) && ! $params['pdo'] instanceof \PDO) {
148 1
            throw DBALException::invalidPdoInstance();
149 134
        } elseif (isset($params['pdo'])) {
150 5
            $params['pdo']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
151 5
            $params['driver'] = 'pdo_' . $params['pdo']->getAttribute(\PDO::ATTR_DRIVER_NAME);
152
        } else {
153 129
            self::_checkParams($params);
154
        }
155
156 131
        $className = $params['driverClass'] ?? self::$_driverMap[$params['driver']];
157
158 131
        $driver = new $className();
159
160 131
        $wrapperClass = 'Doctrine\DBAL\Connection';
161 131
        if (isset($params['wrapperClass'])) {
162 22
            if (is_subclass_of($params['wrapperClass'], $wrapperClass)) {
163 21
               $wrapperClass = $params['wrapperClass'];
164
            } else {
165 1
                throw DBALException::invalidWrapperClass($params['wrapperClass']);
166
            }
167
        }
168
169 130
        return new $wrapperClass($params, $driver, $config, $eventManager);
170
    }
171
172
    /**
173
     * Returns the list of supported drivers.
174
     *
175
     * @return array
176
     */
177
    public static function getAvailableDrivers(): array
178
    {
179
        return array_keys(self::$_driverMap);
180
    }
181
182
    /**
183
     * Checks the list of parameters.
184
     *
185
     * @param array $params The list of parameters.
186
     *
187
     * @return void
188
     *
189
     * @throws \Doctrine\DBAL\DBALException
190
     */
191 129
    private static function _checkParams(array $params): void
192
    {
193
        // check existence of mandatory parameters
194
195
        // driver
196 129
        if ( ! isset($params['driver']) && ! isset($params['driverClass'])) {
197 1
            throw DBALException::driverRequired();
198
        }
199
200
        // check validity of parameters
201
202
        // driver
203 128
        if (isset($params['driver']) && ! isset(self::$_driverMap[$params['driver']])) {
204 1
            throw DBALException::unknownDriver($params['driver'], array_keys(self::$_driverMap));
205
        }
206
207 127
        if (isset($params['driverClass']) && ! in_array('Doctrine\DBAL\Driver', class_implements($params['driverClass'], true))) {
208 1
            throw DBALException::invalidDriverClass($params['driverClass']);
209
        }
210 126
    }
211
212
    /**
213
     * Normalizes the given connection URL path.
214
     *
215
     * @param string $urlPath
216
     *
217
     * @return string The normalized connection URL path
218
     */
219 25
    private static function normalizeDatabaseUrlPath(string $urlPath): string
220
    {
221
        // Trim leading slash from URL path.
222 25
        return substr($urlPath, 1);
223
    }
224
225
    /**
226
     * Extracts parts from a database URL, if present, and returns an
227
     * updated list of parameters.
228
     *
229
     * @param array $params The list of parameters.
230
     *
231
     * @return array A modified list of parameters with info from a database
232
     *               URL extracted into indidivual parameter parts.
233
     *
234
     * @throws DBALException
235
     */
236 138
    private static function parseDatabaseUrl(array $params): array
237
    {
238 138
        if (!isset($params['url'])) {
239 110
            return $params;
240
        }
241
242
        // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
243 28
        $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $params['url']);
244 28
        $url = parse_url($url);
245
246 28
        if ($url === false) {
247
            throw new DBALException('Malformed parameter "url".');
248
        }
249
250 28
        $url = array_map('rawurldecode', $url);
251
252
        // If we have a connection URL, we have to unset the default PDO instance connection parameter (if any)
253
        // as we cannot merge connection details from the URL into the PDO instance (URL takes precedence).
254 28
        unset($params['pdo']);
255
256 28
        $params = self::parseDatabaseUrlScheme($url, $params);
257
258 25
        if (isset($url['host'])) {
259 25
            $params['host'] = $url['host'];
260
        }
261 25
        if (isset($url['port'])) {
262 1
            $params['port'] = $url['port'];
263
        }
264 25
        if (isset($url['user'])) {
265 19
            $params['user'] = $url['user'];
266
        }
267 25
        if (isset($url['pass'])) {
268 19
            $params['password'] = $url['pass'];
269
        }
270
271 25
        $params = self::parseDatabaseUrlPath($url, $params);
272 25
        $params = self::parseDatabaseUrlQuery($url, $params);
273
274 25
        return $params;
275
    }
276
277
    /**
278
     * Parses the given connection URL and resolves the given connection parameters.
279
     *
280
     * Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters
281
     * via {@link parseDatabaseUrlScheme}.
282
     *
283
     * @param array $url    The URL parts to evaluate.
284
     * @param array $params The connection parameters to resolve.
285
     *
286
     * @return array The resolved connection parameters.
287
     *
288
     * @see parseDatabaseUrlScheme
289
     */
290 25
    private static function parseDatabaseUrlPath(array $url, array $params): array
291
    {
292 25
        if (! isset($url['path'])) {
293
            return $params;
294
        }
295
296 25
        $url['path'] = self::normalizeDatabaseUrlPath($url['path']);
297
298
        // If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate
299
        // and therefore treat the path as regular DBAL connection URL path.
300 25
        if (! isset($params['driver'])) {
301 1
            return self::parseRegularDatabaseUrlPath($url, $params);
302
        }
303
304 24
        if (strpos($params['driver'], 'sqlite') !== false) {
305 6
            return self::parseSqliteDatabaseUrlPath($url, $params);
306
        }
307
308 18
        return self::parseRegularDatabaseUrlPath($url, $params);
309
    }
310
311
    /**
312
     * Parses the query part of the given connection URL and resolves the given connection parameters.
313
     *
314
     * @param array $url    The connection URL parts to evaluate.
315
     * @param array $params The connection parameters to resolve.
316
     *
317
     * @return array The resolved connection parameters.
318
     */
319 25
    private static function parseDatabaseUrlQuery(array $url, array $params): array
320
    {
321 25
        if (! isset($url['query'])) {
322 24
            return $params;
323
        }
324
325 1
        $query = [];
326
327 1
        parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode
328
329 1
        return array_merge($params, $query); // parse_str wipes existing array elements
330
    }
331
332
    /**
333
     * Parses the given regular connection URL and resolves the given connection parameters.
334
     *
335
     * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}.
336
     *
337
     * @param array $url    The regular connection URL parts to evaluate.
338
     * @param array $params The connection parameters to resolve.
339
     *
340
     * @return array The resolved connection parameters.
341
     *
342
     * @see normalizeDatabaseUrlPath
343
     */
344 19
    private static function parseRegularDatabaseUrlPath(array $url, array $params): array
345
    {
346 19
        $params['dbname'] = $url['path'];
347
348 19
        return $params;
349
    }
350
351
    /**
352
     * Parses the given SQLite connection URL and resolves the given connection parameters.
353
     *
354
     * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}.
355
     *
356
     * @param array $url    The SQLite connection URL parts to evaluate.
357
     * @param array $params The connection parameters to resolve.
358
     *
359
     * @return array The resolved connection parameters.
360
     *
361
     * @see normalizeDatabaseUrlPath
362
     */
363 6
    private static function parseSqliteDatabaseUrlPath(array $url, array $params): array
364
    {
365 6
        if ($url['path'] === ':memory:') {
366 2
            $params['memory'] = true;
367
368 2
            return $params;
369
        }
370
371 4
        $params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key
372
373 4
        return $params;
374
    }
375
376
    /**
377
     * Parses the scheme part from given connection URL and resolves the given connection parameters.
378
     *
379
     * @param array $url    The connection URL parts to evaluate.
380
     * @param array $params The connection parameters to resolve.
381
     *
382
     * @return array The resolved connection parameters.
383
     *
384
     * @throws DBALException if parsing failed or resolution is not possible.
385
     */
386 28
    private static function parseDatabaseUrlScheme(array $url, array $params): array
387
    {
388 28
        if (isset($url['scheme'])) {
389
            // The requested driver from the URL scheme takes precedence
390
            // over the default custom driver from the connection parameters (if any).
391 21
            unset($params['driverClass']);
392
393
            // URL schemes must not contain underscores, but dashes are ok
394 21
            $driver = str_replace('-', '_', $url['scheme']);
395
396
            // The requested driver from the URL scheme takes precedence over the
397
            // default driver from the connection parameters. If the driver is
398
            // an alias (e.g. "postgres"), map it to the actual name ("pdo-pgsql").
399
            // Otherwise, let checkParams decide later if the driver exists.
400 21
            $params['driver'] = self::$driverSchemeAliases[$driver] ?? $driver;
401
402 21
            return $params;
403
        }
404
405
        // If a schemeless connection URL is given, we require a default driver or default custom driver
406
        // as connection parameter.
407 7
        if (! isset($params['driverClass']) && ! isset($params['driver'])) {
408 3
            throw DBALException::driverRequired($params['url']);
409
        }
410
411 4
        return $params;
412
    }
413
}
414