Completed
Push — master ( c7757e...39cb21 )
by Luís
16s
created

lib/Doctrine/DBAL/DriverManager.php (1 issue)

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