DriverManager.php 14.7 KB
Newer Older
romanb's avatar
romanb committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
Benjamin Eberlei's avatar
Benjamin Eberlei committed
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
romanb's avatar
romanb committed
18 19
 */

20 21 22
namespace Doctrine\DBAL;

use Doctrine\Common\EventManager;
romanb's avatar
romanb committed
23 24

/**
25
 * Factory for creating Doctrine\DBAL\Connection instances.
romanb's avatar
romanb committed
26 27 28 29
 *
 * @author Roman Borschel <roman@code-factory.org>
 * @since 2.0
 */
30
final class DriverManager
romanb's avatar
romanb committed
31 32
{
    /**
33
     * List of supported drivers and their mappings to the driver classes.
romanb's avatar
romanb committed
34
     *
35 36 37
     * To add your own driver use the 'driverClass' parameter to
     * {@link DriverManager::getConnection()}.
     *
romanb's avatar
romanb committed
38 39
     * @var array
     */
40
     private static $_driverMap = array(
Steve Müller's avatar
Steve Müller committed
41 42 43 44 45 46 47 48
         'pdo_mysql'          => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
         'pdo_sqlite'         => 'Doctrine\DBAL\Driver\PDOSqlite\Driver',
         'pdo_pgsql'          => 'Doctrine\DBAL\Driver\PDOPgSql\Driver',
         'pdo_oci'            => 'Doctrine\DBAL\Driver\PDOOracle\Driver',
         'oci8'               => 'Doctrine\DBAL\Driver\OCI8\Driver',
         'ibm_db2'            => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver',
         'pdo_sqlsrv'         => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver',
         'mysqli'             => 'Doctrine\DBAL\Driver\Mysqli\Driver',
49
         'drizzle_pdo_mysql'  => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver',
Steve Müller's avatar
Steve Müller committed
50 51
         'sqlanywhere'        => 'Doctrine\DBAL\Driver\SQLAnywhere\Driver',
         'sqlsrv'             => 'Doctrine\DBAL\Driver\SQLSrv\Driver',
52
    );
53

54 55 56
    /**
     * List of URL schemes from a database URL and their mappings to driver.
     */
57
    private static $driverSchemeAliases = array(
58 59 60 61 62 63 64 65 66 67 68
        'db2'        => 'ibm_db2',
        'mssql'      => 'pdo_sqlsrv',
        'mysql'      => 'pdo_mysql',
        'mysql2'     => 'pdo_mysql', // Amazon RDS, for some weird reason
        'postgres'   => 'pdo_pgsql',
        'postgresql' => 'pdo_pgsql',
        'pgsql'      => 'pdo_pgsql',
        'sqlite'     => 'pdo_sqlite',
        'sqlite3'    => 'pdo_sqlite',
    );

Benjamin Morel's avatar
Benjamin Morel committed
69 70 71 72 73 74
    /**
     * Private constructor. This class cannot be instantiated.
     */
    private function __construct()
    {
    }
75

romanb's avatar
romanb committed
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.
romanb's avatar
romanb committed
80
     *
81
     * $params must contain at least one of the following.
Benjamin Eberlei's avatar
Benjamin Eberlei committed
82
     *
83
     * Either 'driver' with one of the following values:
84
     *
85 86 87
     *     pdo_mysql
     *     pdo_sqlite
     *     pdo_pgsql
88 89
     *     pdo_oci (unstable)
     *     pdo_sqlsrv
90
     *     pdo_sqlsrv
91
     *     mysqli
92
     *     sqlanywhere
93 94 95
     *     sqlsrv
     *     ibm_db2 (unstable)
     *     drizzle_pdo_mysql
Benjamin Eberlei's avatar
Benjamin Eberlei committed
96
     *
97 98
     * OR 'driverClass' that contains the full class name (with namespace) of the
     * driver class to instantiate.
Benjamin Eberlei's avatar
Benjamin Eberlei committed
99
     *
100
     * Other (optional) parameters:
Benjamin Eberlei's avatar
Benjamin Eberlei committed
101
     *
102
     * <b>user (string)</b>:
Benjamin Eberlei's avatar
Benjamin Eberlei committed
103 104
     * The username to use when connecting.
     *
105 106
     * <b>password (string)</b>:
     * The password to use when connecting.
Benjamin Eberlei's avatar
Benjamin Eberlei committed
107
     *
108 109 110
     * <b>driverOptions (array)</b>:
     * Any additional driver-specific options for the driver. These are just passed
     * through to the driver.
Benjamin Eberlei's avatar
Benjamin Eberlei committed
111
     *
112 113
     * <b>pdo</b>:
     * You can pass an existing PDO instance through this parameter. The PDO
114
     * instance will be wrapped in a Doctrine\DBAL\Connection.
Benjamin Eberlei's avatar
Benjamin Eberlei committed
115
     *
116 117
     * <b>wrapperClass</b>:
     * You may specify a custom wrapper class through the 'wrapperClass'
118
     * parameter but this class MUST inherit from Doctrine\DBAL\Connection.
Benjamin Eberlei's avatar
Benjamin Eberlei committed
119
     *
120 121 122
     * <b>driverClass</b>:
     * The driver class to use.
     *
Benjamin Morel's avatar
Benjamin Morel committed
123 124 125 126
     * @param array                              $params       The parameters.
     * @param \Doctrine\DBAL\Configuration|null  $config       The configuration to use.
     * @param \Doctrine\Common\EventManager|null $eventManager The event manager to use.
     *
127
     * @return \Doctrine\DBAL\Connection
Benjamin Morel's avatar
Benjamin Morel committed
128 129
     *
     * @throws \Doctrine\DBAL\DBALException
romanb's avatar
romanb committed
130
     */
131 132
    public static function getConnection(
            array $params,
133 134
            Configuration $config = null,
            EventManager $eventManager = null)
romanb's avatar
romanb committed
135 136 137
    {
        // create default config and event manager, if not set
        if ( ! $config) {
138
            $config = new Configuration();
romanb's avatar
romanb committed
139 140
        }
        if ( ! $eventManager) {
141
            $eventManager = new EventManager();
romanb's avatar
romanb committed
142
        }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
143

David Zuelke's avatar
David Zuelke committed
144
        $params = self::parseDatabaseUrl($params);
145

romanb's avatar
romanb committed
146
        // check for existing pdo object
147
        if (isset($params['pdo']) && ! $params['pdo'] instanceof \PDO) {
148
            throw DBALException::invalidPdoInstance();
Steve Müller's avatar
Steve Müller committed
149
        } elseif (isset($params['pdo'])) {
150
            $params['pdo']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
jwage's avatar
jwage committed
151
            $params['driver'] = 'pdo_' . $params['pdo']->getAttribute(\PDO::ATTR_DRIVER_NAME);
romanb's avatar
romanb committed
152
        } else {
153
            self::_checkParams($params);
romanb's avatar
romanb committed
154 155 156 157
        }
        if (isset($params['driverClass'])) {
            $className = $params['driverClass'];
        } else {
158
            $className = self::$_driverMap[$params['driver']];
romanb's avatar
romanb committed
159
        }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
160

romanb's avatar
romanb committed
161
        $driver = new $className();
Benjamin Eberlei's avatar
Benjamin Eberlei committed
162

163
        $wrapperClass = 'Doctrine\DBAL\Connection';
164 165 166 167 168 169
        if (isset($params['wrapperClass'])) {
            if (is_subclass_of($params['wrapperClass'], $wrapperClass)) {
               $wrapperClass = $params['wrapperClass'];
            } else {
                throw DBALException::invalidWrapperClass($params['wrapperClass']);
            }
romanb's avatar
romanb committed
170
        }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
171

romanb's avatar
romanb committed
172 173
        return new $wrapperClass($params, $driver, $config, $eventManager);
    }
174

175
    /**
176
     * Returns the list of supported drivers.
177 178 179
     *
     * @return array
     */
180
    public static function getAvailableDrivers()
181
    {
182
        return array_keys(self::$_driverMap);
183 184
    }

romanb's avatar
romanb committed
185 186 187
    /**
     * Checks the list of parameters.
     *
Benjamin Morel's avatar
Benjamin Morel committed
188 189 190 191 192
     * @param array $params The list of parameters.
     *
     * @return void
     *
     * @throws \Doctrine\DBAL\DBALException
romanb's avatar
romanb committed
193
     */
194
    private static function _checkParams(array $params)
Benjamin Eberlei's avatar
Benjamin Eberlei committed
195
    {
Pascal Borreli's avatar
Pascal Borreli committed
196
        // check existence of mandatory parameters
Benjamin Eberlei's avatar
Benjamin Eberlei committed
197

romanb's avatar
romanb committed
198 199
        // driver
        if ( ! isset($params['driver']) && ! isset($params['driverClass'])) {
200
            throw DBALException::driverRequired();
romanb's avatar
romanb committed
201
        }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
202

romanb's avatar
romanb committed
203
        // check validity of parameters
Benjamin Eberlei's avatar
Benjamin Eberlei committed
204

romanb's avatar
romanb committed
205
        // driver
206
        if (isset($params['driver']) && ! isset(self::$_driverMap[$params['driver']])) {
207
            throw DBALException::unknownDriver($params['driver'], array_keys(self::$_driverMap));
romanb's avatar
romanb committed
208
        }
209 210 211 212

        if (isset($params['driverClass']) && ! in_array('Doctrine\DBAL\Driver', class_implements($params['driverClass'], true))) {
            throw DBALException::invalidDriverClass($params['driverClass']);
        }
romanb's avatar
romanb committed
213
    }
214

215 216 217 218 219 220 221 222 223 224 225 226 227
    /**
     * Normalizes the given connection URL path.
     *
     * @param string $urlPath
     *
     * @return string The normalized connection URL path
     */
    private static function normalizeDatabaseUrlPath($urlPath)
    {
        // Trim leading slash from URL path.
        return substr($urlPath, 1);
    }

228 229 230 231 232 233
    /**
     * Extracts parts from a database URL, if present, and returns an
     * updated list of parameters.
     *
     * @param array $params The list of parameters.
     *
234 235
     * @return array A modified list of parameters with info from a database
     *               URL extracted into indidivual parameter parts.
236
     *
237
     * @throws DBALException
238
     */
239
    private static function parseDatabaseUrl(array $params)
240 241
    {
        if (!isset($params['url'])) {
David Zuelke's avatar
David Zuelke committed
242
            return $params;
243
        }
244

245
        // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid
David Zuelke's avatar
David Zuelke committed
246
        $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $params['url']);
247

248 249 250 251 252 253 254 255 256
        // PHP < 5.4.8 doesn't parse schemeless urls properly.
        // See: https://php.net/parse-url#refsect1-function.parse-url-changelog
        if (PHP_VERSION_ID < 50408 && strpos($url, '//') === 0) {
            $url = parse_url('fake:' . $url);

            unset($url['scheme']);
        } else {
            $url = parse_url($url);
        }
257

David Zuelke's avatar
David Zuelke committed
258
        if ($url === false) {
259 260
            throw new DBALException('Malformed parameter "url".');
        }
261

262 263
        $url = array_map('rawurldecode', $url);

264 265 266 267 268
        // If we have a connection URL, we have to unset the default PDO instance connection parameter (if any)
        // as we cannot merge connection details from the URL into the PDO instance (URL takes precedence).
        unset($params['pdo']);

        $params = self::parseDatabaseUrlScheme($url, $params);
269

270 271 272 273 274 275 276 277 278 279 280 281
        if (isset($url['host'])) {
            $params['host'] = $url['host'];
        }
        if (isset($url['port'])) {
            $params['port'] = $url['port'];
        }
        if (isset($url['user'])) {
            $params['user'] = $url['user'];
        }
        if (isset($url['pass'])) {
            $params['password'] = $url['pass'];
        }
282

283 284
        $params = self::parseDatabaseUrlPath($url, $params);
        $params = self::parseDatabaseUrlQuery($url, $params);
285 286 287 288 289

        return $params;
    }

    /**
290 291 292 293
     * Parses the given connection URL and resolves the given connection parameters.
     *
     * Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters
     * via {@link parseDatabaseUrlScheme}.
294 295 296 297 298
     *
     * @param array $url    The URL parts to evaluate.
     * @param array $params The connection parameters to resolve.
     *
     * @return array The resolved connection parameters.
299 300
     *
     * @see parseDatabaseUrlScheme
301 302 303
     */
    private static function parseDatabaseUrlPath(array $url, array $params)
    {
304
        if (! isset($url['path'])) {
305 306 307
            return $params;
        }

308
        $url['path'] = self::normalizeDatabaseUrlPath($url['path']);
309

310 311 312 313 314 315 316
        // If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate
        // and therefore treat the path as regular DBAL connection URL path.
        if (! isset($params['driver'])) {
            return self::parseRegularDatabaseUrlPath($url, $params);
        }

        if (strpos($params['driver'], 'sqlite') !== false) {
317 318 319
            return self::parseSqliteDatabaseUrlPath($url, $params);
        }

320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
        return self::parseRegularDatabaseUrlPath($url, $params);
    }

    /**
     * Parses the query part of the given connection URL and resolves the given connection parameters.
     *
     * @param array $url    The connection URL parts to evaluate.
     * @param array $params The connection parameters to resolve.
     *
     * @return array The resolved connection parameters.
     */
    private static function parseDatabaseUrlQuery(array $url, array $params)
    {
        if (! isset($url['query'])) {
            return $params;
        }

        $query = array();

        parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode

        return array_merge($params, $query); // parse_str wipes existing array elements
    }

    /**
     * Parses the given regular connection URL and resolves the given connection parameters.
     *
     * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}.
     *
     * @param array $url    The regular connection URL parts to evaluate.
     * @param array $params The connection parameters to resolve.
     *
     * @return array The resolved connection parameters.
     *
     * @see normalizeDatabaseUrlPath
     */
    private static function parseRegularDatabaseUrlPath(array $url, array $params)
    {
358 359 360 361 362 363
        $params['dbname'] = $url['path'];

        return $params;
    }

    /**
364
     * Parses the given SQLite connection URL and resolves the given connection parameters.
365
     *
366 367 368
     * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}.
     *
     * @param array $url    The SQLite connection URL parts to evaluate.
369 370 371
     * @param array $params The connection parameters to resolve.
     *
     * @return array The resolved connection parameters.
372 373
     *
     * @see normalizeDatabaseUrlPath
374 375 376 377 378 379 380 381 382 383 384
     */
    private static function parseSqliteDatabaseUrlPath(array $url, array $params)
    {
        if ($url['path'] === ':memory:') {
            $params['memory'] = true;

            return $params;
        }

        $params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key

David Zuelke's avatar
David Zuelke committed
385
        return $params;
386
    }
387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426

    /**
     * Parses the scheme part from given connection URL and resolves the given connection parameters.
     *
     * @param array $url    The connection URL parts to evaluate.
     * @param array $params The connection parameters to resolve.
     *
     * @return array The resolved connection parameters.
     *
     * @throws DBALException if parsing failed or resolution is not possible.
     */
    private static function parseDatabaseUrlScheme(array $url, array $params)
    {
        if (isset($url['scheme'])) {
            // The requested driver from the URL scheme takes precedence
            // over the default custom driver from the connection parameters (if any).
            unset($params['driverClass']);

            // URL schemes must not contain underscores, but dashes are ok
            $driver = str_replace('-', '_', $url['scheme']);

            // The requested driver from the URL scheme takes precedence
            // over the default driver from the connection parameters (if any).
            $params['driver'] = isset(self::$driverSchemeAliases[$driver])
                // use alias like "postgres", else we just let checkParams decide later
                // if the driver exists (for literal "pdo-pgsql" etc)
                ? self::$driverSchemeAliases[$driver]
                : $driver;

            return $params;
        }

        // If a schemeless connection URL is given, we require a default driver or default custom driver
        // as connection parameter.
        if (! isset($params['driverClass']) && ! isset($params['driver'])) {
            throw DBALException::driverRequired($params['url']);
        }

        return $params;
    }
Benjamin Eberlei's avatar
Benjamin Eberlei committed
427
}