Completed
Push — master ( 5e9cdb...0c7452 )
by Marco
48s
created

DriverManager::parseDatabaseUrlPath()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 8
cts 9
cp 0.8889
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 2
crap 4.0218
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 131
        if (isset($params['driverClass'])) {
156 3
            $className = $params['driverClass'];
157
        } else {
158 128
            $className = self::$_driverMap[$params['driver']];
159
        }
160
161 131
        $driver = new $className();
162
163 131
        $wrapperClass = 'Doctrine\DBAL\Connection';
164 131
        if (isset($params['wrapperClass'])) {
165 22
            if (is_subclass_of($params['wrapperClass'], $wrapperClass)) {
166 21
               $wrapperClass = $params['wrapperClass'];
167
            } else {
168 1
                throw DBALException::invalidWrapperClass($params['wrapperClass']);
169
            }
170
        }
171
172 130
        return new $wrapperClass($params, $driver, $config, $eventManager);
173
    }
174
175
    /**
176
     * Returns the list of supported drivers.
177
     *
178
     * @return array
179
     */
180
    public static function getAvailableDrivers(): array
181
    {
182
        return array_keys(self::$_driverMap);
183
    }
184
185
    /**
186
     * Checks the list of parameters.
187
     *
188
     * @param array $params The list of parameters.
189
     *
190
     * @return void
191
     *
192
     * @throws \Doctrine\DBAL\DBALException
193
     */
194 129
    private static function _checkParams(array $params): void
195
    {
196
        // check existence of mandatory parameters
197
198
        // driver
199 129
        if ( ! isset($params['driver']) && ! isset($params['driverClass'])) {
200 1
            throw DBALException::driverRequired();
201
        }
202
203
        // check validity of parameters
204
205
        // driver
206 128
        if (isset($params['driver']) && ! isset(self::$_driverMap[$params['driver']])) {
207 1
            throw DBALException::unknownDriver($params['driver'], array_keys(self::$_driverMap));
208
        }
209
210 127
        if (isset($params['driverClass']) && ! in_array('Doctrine\DBAL\Driver', class_implements($params['driverClass'], true))) {
211 1
            throw DBALException::invalidDriverClass($params['driverClass']);
212
        }
213 126
    }
214
215
    /**
216
     * Normalizes the given connection URL path.
217
     *
218
     * @param string $urlPath
219
     *
220
     * @return string The normalized connection URL path
221
     */
222 25
    private static function normalizeDatabaseUrlPath(string $urlPath): string
223
    {
224
        // Trim leading slash from URL path.
225 25
        return substr($urlPath, 1);
226
    }
227
228
    /**
229
     * Extracts parts from a database URL, if present, and returns an
230
     * updated list of parameters.
231
     *
232
     * @param array $params The list of parameters.
233
     *
234
     * @return array A modified list of parameters with info from a database
235
     *               URL extracted into indidivual parameter parts.
236
     *
237
     * @throws DBALException
238
     */
239 138
    private static function parseDatabaseUrl(array $params): array
240
    {
241 138
        if (!isset($params['url'])) {
242 110
            return $params;
243
        }
244
245
        // (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...
246 28
        $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $params['url']);
247 28
        $url = parse_url($url);
248
249 28
        if ($url === false) {
250
            throw new DBALException('Malformed parameter "url".');
251
        }
252
253 28
        $url = array_map('rawurldecode', $url);
254
255
        // If we have a connection URL, we have to unset the default PDO instance connection parameter (if any)
256
        // as we cannot merge connection details from the URL into the PDO instance (URL takes precedence).
257 28
        unset($params['pdo']);
258
259 28
        $params = self::parseDatabaseUrlScheme($url, $params);
260
261 25
        if (isset($url['host'])) {
262 25
            $params['host'] = $url['host'];
263
        }
264 25
        if (isset($url['port'])) {
265 1
            $params['port'] = $url['port'];
266
        }
267 25
        if (isset($url['user'])) {
268 19
            $params['user'] = $url['user'];
269
        }
270 25
        if (isset($url['pass'])) {
271 19
            $params['password'] = $url['pass'];
272
        }
273
274 25
        $params = self::parseDatabaseUrlPath($url, $params);
275 25
        $params = self::parseDatabaseUrlQuery($url, $params);
276
277 25
        return $params;
278
    }
279
280
    /**
281
     * Parses the given connection URL and resolves the given connection parameters.
282
     *
283
     * Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters
284
     * via {@link parseDatabaseUrlScheme}.
285
     *
286
     * @param array $url    The URL parts to evaluate.
287
     * @param array $params The connection parameters to resolve.
288
     *
289
     * @return array The resolved connection parameters.
290
     *
291
     * @see parseDatabaseUrlScheme
292
     */
293 25
    private static function parseDatabaseUrlPath(array $url, array $params): array
294
    {
295 25
        if (! isset($url['path'])) {
296
            return $params;
297
        }
298
299 25
        $url['path'] = self::normalizeDatabaseUrlPath($url['path']);
300
301
        // If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate
302
        // and therefore treat the path as regular DBAL connection URL path.
303 25
        if (! isset($params['driver'])) {
304 1
            return self::parseRegularDatabaseUrlPath($url, $params);
305
        }
306
307 24
        if (strpos($params['driver'], 'sqlite') !== false) {
308 6
            return self::parseSqliteDatabaseUrlPath($url, $params);
309
        }
310
311 18
        return self::parseRegularDatabaseUrlPath($url, $params);
312
    }
313
314
    /**
315
     * Parses the query part of the given connection URL and resolves the given connection parameters.
316
     *
317
     * @param array $url    The connection URL parts to evaluate.
318
     * @param array $params The connection parameters to resolve.
319
     *
320
     * @return array The resolved connection parameters.
321
     */
322 25
    private static function parseDatabaseUrlQuery(array $url, array $params): array
323
    {
324 25
        if (! isset($url['query'])) {
325 24
            return $params;
326
        }
327
328 1
        $query = [];
329
330 1
        parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode
331
332 1
        return array_merge($params, $query); // parse_str wipes existing array elements
333
    }
334
335
    /**
336
     * Parses the given regular connection URL and resolves the given connection parameters.
337
     *
338
     * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}.
339
     *
340
     * @param array $url    The regular connection URL parts to evaluate.
341
     * @param array $params The connection parameters to resolve.
342
     *
343
     * @return array The resolved connection parameters.
344
     *
345
     * @see normalizeDatabaseUrlPath
346
     */
347 19
    private static function parseRegularDatabaseUrlPath(array $url, array $params): array
348
    {
349 19
        $params['dbname'] = $url['path'];
350
351 19
        return $params;
352
    }
353
354
    /**
355
     * Parses the given SQLite connection URL and resolves the given connection parameters.
356
     *
357
     * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}.
358
     *
359
     * @param array $url    The SQLite connection URL parts to evaluate.
360
     * @param array $params The connection parameters to resolve.
361
     *
362
     * @return array The resolved connection parameters.
363
     *
364
     * @see normalizeDatabaseUrlPath
365
     */
366 6
    private static function parseSqliteDatabaseUrlPath(array $url, array $params): array
367
    {
368 6
        if ($url['path'] === ':memory:') {
369 2
            $params['memory'] = true;
370
371 2
            return $params;
372
        }
373
374 4
        $params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key
375
376 4
        return $params;
377
    }
378
379
    /**
380
     * Parses the scheme part from given connection URL and resolves the given connection parameters.
381
     *
382
     * @param array $url    The connection URL parts to evaluate.
383
     * @param array $params The connection parameters to resolve.
384
     *
385
     * @return array The resolved connection parameters.
386
     *
387
     * @throws DBALException if parsing failed or resolution is not possible.
388
     */
389 28
    private static function parseDatabaseUrlScheme(array $url, array $params): array
390
    {
391 28
        if (isset($url['scheme'])) {
392
            // The requested driver from the URL scheme takes precedence
393
            // over the default custom driver from the connection parameters (if any).
394 21
            unset($params['driverClass']);
395
396
            // URL schemes must not contain underscores, but dashes are ok
397 21
            $driver = str_replace('-', '_', $url['scheme']);
398
399
            // The requested driver from the URL scheme takes precedence
400
            // over the default driver from the connection parameters (if any).
401 21
            $params['driver'] = isset(self::$driverSchemeAliases[$driver])
402
                // use alias like "postgres", else we just let checkParams decide later
403
                // if the driver exists (for literal "pdo-pgsql" etc)
404 19
                ? self::$driverSchemeAliases[$driver]
405 2
                : $driver;
406
407 21
            return $params;
408
        }
409
410
        // If a schemeless connection URL is given, we require a default driver or default custom driver
411
        // as connection parameter.
412 7
        if (! isset($params['driverClass']) && ! isset($params['driver'])) {
413 3
            throw DBALException::driverRequired($params['url']);
414
        }
415
416 4
        return $params;
417
    }
418
}
419