vendor\doctrine\dbal\lib\Doctrine\DBAL\DriverManager.php line 280

Open in your IDE?
  1. <?php
  2. namespace Doctrine\DBAL;
  3. use Doctrine\Common\EventManager;
  4. use Doctrine\DBAL\Driver\DrizzlePDOMySql;
  5. use Doctrine\DBAL\Driver\IBMDB2;
  6. use Doctrine\DBAL\Driver\Mysqli;
  7. use Doctrine\DBAL\Driver\OCI8;
  8. use Doctrine\DBAL\Driver\PDO;
  9. use Doctrine\DBAL\Driver\SQLAnywhere;
  10. use Doctrine\DBAL\Driver\SQLSrv;
  11. use function array_keys;
  12. use function array_merge;
  13. use function class_implements;
  14. use function in_array;
  15. use function is_string;
  16. use function is_subclass_of;
  17. use function parse_str;
  18. use function parse_url;
  19. use function preg_replace;
  20. use function rawurldecode;
  21. use function str_replace;
  22. use function strpos;
  23. use function substr;
  24. /**
  25.  * Factory for creating {@link Connection} instances.
  26.  *
  27.  * @psalm-type OverrideParams = array{
  28.  *     charset?: string,
  29.  *     dbname?: string,
  30.  *     default_dbname?: string,
  31.  *     driver?: key-of<self::DRIVER_MAP>,
  32.  *     driverClass?: class-string<Driver>,
  33.  *     driverOptions?: array<mixed>,
  34.  *     host?: string,
  35.  *     password?: string,
  36.  *     path?: string,
  37.  *     pdo?: \PDO,
  38.  *     platform?: Platforms\AbstractPlatform,
  39.  *     port?: int,
  40.  *     user?: string,
  41.  * }
  42.  * @psalm-type Params = array{
  43.  *     charset?: string,
  44.  *     dbname?: string,
  45.  *     default_dbname?: string,
  46.  *     driver?: key-of<self::DRIVER_MAP>,
  47.  *     driverClass?: class-string<Driver>,
  48.  *     driverOptions?: array<mixed>,
  49.  *     host?: string,
  50.  *     keepSlave?: bool,
  51.  *     keepReplica?: bool,
  52.  *     master?: OverrideParams,
  53.  *     memory?: bool,
  54.  *     password?: string,
  55.  *     path?: string,
  56.  *     pdo?: \PDO,
  57.  *     platform?: Platforms\AbstractPlatform,
  58.  *     port?: int,
  59.  *     primary?: OverrideParams,
  60.  *     replica?: array<OverrideParams>,
  61.  *     sharding?: array<string,mixed>,
  62.  *     slaves?: array<OverrideParams>,
  63.  *     user?: string,
  64.  *     wrapperClass?: class-string<Connection>,
  65.  * }
  66.  */
  67. final class DriverManager
  68. {
  69.     /**
  70.      * List of supported drivers and their mappings to the driver classes.
  71.      *
  72.      * To add your own driver use the 'driverClass' parameter to {@link DriverManager::getConnection()}.
  73.      */
  74.     private const DRIVER_MAP = [
  75.         'pdo_mysql'          => PDO\MySQL\Driver::class,
  76.         'pdo_sqlite'         => PDO\SQLite\Driver::class,
  77.         'pdo_pgsql'          => PDO\PgSQL\Driver::class,
  78.         'pdo_oci'            => PDO\OCI\Driver::class,
  79.         'oci8'               => OCI8\Driver::class,
  80.         'ibm_db2'            => IBMDB2\Driver::class,
  81.         'pdo_sqlsrv'         => PDO\SQLSrv\Driver::class,
  82.         'mysqli'             => Mysqli\Driver::class,
  83.         'drizzle_pdo_mysql'  => DrizzlePDOMySql\Driver::class,
  84.         'sqlanywhere'        => SQLAnywhere\Driver::class,
  85.         'sqlsrv'             => SQLSrv\Driver::class,
  86.     ];
  87.     /**
  88.      * List of URL schemes from a database URL and their mappings to driver.
  89.      *
  90.      * @var string[]
  91.      */
  92.     private static $driverSchemeAliases = [
  93.         'db2'        => 'ibm_db2',
  94.         'mssql'      => 'pdo_sqlsrv',
  95.         'mysql'      => 'pdo_mysql',
  96.         'mysql2'     => 'pdo_mysql'// Amazon RDS, for some weird reason
  97.         'postgres'   => 'pdo_pgsql',
  98.         'postgresql' => 'pdo_pgsql',
  99.         'pgsql'      => 'pdo_pgsql',
  100.         'sqlite'     => 'pdo_sqlite',
  101.         'sqlite3'    => 'pdo_sqlite',
  102.     ];
  103.     /**
  104.      * Private constructor. This class cannot be instantiated.
  105.      *
  106.      * @codeCoverageIgnore
  107.      */
  108.     private function __construct()
  109.     {
  110.     }
  111.     /**
  112.      * Creates a connection object based on the specified parameters.
  113.      * This method returns a Doctrine\DBAL\Connection which wraps the underlying
  114.      * driver connection.
  115.      *
  116.      * $params must contain at least one of the following.
  117.      *
  118.      * Either 'driver' with one of the array keys of {@link DRIVER_MAP},
  119.      * OR 'driverClass' that contains the full class name (with namespace) of the
  120.      * driver class to instantiate.
  121.      *
  122.      * Other (optional) parameters:
  123.      *
  124.      * <b>user (string)</b>:
  125.      * The username to use when connecting.
  126.      *
  127.      * <b>password (string)</b>:
  128.      * The password to use when connecting.
  129.      *
  130.      * <b>driverOptions (array)</b>:
  131.      * Any additional driver-specific options for the driver. These are just passed
  132.      * through to the driver.
  133.      *
  134.      * <b>pdo</b>:
  135.      * You can pass an existing PDO instance through this parameter. The PDO
  136.      * instance will be wrapped in a Doctrine\DBAL\Connection.
  137.      * This feature is deprecated and no longer supported in 3.0.x version.
  138.      *
  139.      * <b>wrapperClass</b>:
  140.      * You may specify a custom wrapper class through the 'wrapperClass'
  141.      * parameter but this class MUST inherit from Doctrine\DBAL\Connection.
  142.      *
  143.      * <b>driverClass</b>:
  144.      * The driver class to use.
  145.      *
  146.      * @param array<string,mixed> $params
  147.      * @param Configuration|null  $config       The configuration to use.
  148.      * @param EventManager|null   $eventManager The event manager to use.
  149.      * @psalm-param array{
  150.      *     charset?: string,
  151.      *     dbname?: string,
  152.      *     default_dbname?: string,
  153.      *     driver?: key-of<self::DRIVER_MAP>,
  154.      *     driverClass?: class-string<Driver>,
  155.      *     driverOptions?: array<mixed>,
  156.      *     host?: string,
  157.      *     keepSlave?: bool,
  158.      *     keepReplica?: bool,
  159.      *     master?: OverrideParams,
  160.      *     memory?: bool,
  161.      *     password?: string,
  162.      *     path?: string,
  163.      *     pdo?: \PDO,
  164.      *     platform?: Platforms\AbstractPlatform,
  165.      *     port?: int,
  166.      *     primary?: OverrideParams,
  167.      *     replica?: array<OverrideParams>,
  168.      *     sharding?: array<string,mixed>,
  169.      *     slaves?: array<OverrideParams>,
  170.      *     user?: string,
  171.      *     wrapperClass?: class-string<T>,
  172.      * } $params
  173.      * @phpstan-param array<string,mixed> $params
  174.      *
  175.      * @psalm-return ($params is array{wrapperClass:mixed} ? T : Connection)
  176.      *
  177.      * @throws Exception
  178.      *
  179.      * @template T of Connection
  180.      */
  181.     public static function getConnection(
  182.         array $params,
  183.         ?Configuration $config null,
  184.         ?EventManager $eventManager null
  185.     ): Connection {
  186.         // create default config and event manager, if not set
  187.         if (! $config) {
  188.             $config = new Configuration();
  189.         }
  190.         if (! $eventManager) {
  191.             $eventManager = new EventManager();
  192.         }
  193.         $params self::parseDatabaseUrl($params);
  194.         // @todo: deprecated, notice thrown by connection constructor
  195.         if (isset($params['master'])) {
  196.             $params['master'] = self::parseDatabaseUrl($params['master']);
  197.         }
  198.         // @todo: deprecated, notice thrown by connection constructor
  199.         if (isset($params['slaves'])) {
  200.             foreach ($params['slaves'] as $key => $slaveParams) {
  201.                 $params['slaves'][$key] = self::parseDatabaseUrl($slaveParams);
  202.             }
  203.         }
  204.         // URL support for PrimaryReplicaConnection
  205.         if (isset($params['primary'])) {
  206.             $params['primary'] = self::parseDatabaseUrl($params['primary']);
  207.         }
  208.         if (isset($params['replica'])) {
  209.             foreach ($params['replica'] as $key => $replicaParams) {
  210.                 $params['replica'][$key] = self::parseDatabaseUrl($replicaParams);
  211.             }
  212.         }
  213.         // URL support for PoolingShardConnection
  214.         if (isset($params['global'])) {
  215.             $params['global'] = self::parseDatabaseUrl($params['global']);
  216.         }
  217.         if (isset($params['shards'])) {
  218.             foreach ($params['shards'] as $key => $shardParams) {
  219.                 $params['shards'][$key] = self::parseDatabaseUrl($shardParams);
  220.             }
  221.         }
  222.         // check for existing pdo object
  223.         if (isset($params['pdo']) && ! $params['pdo'] instanceof \PDO) {
  224.             throw Exception::invalidPdoInstance();
  225.         }
  226.         if (isset($params['pdo'])) {
  227.             $params['pdo']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
  228.             $params['driver'] = 'pdo_' $params['pdo']->getAttribute(\PDO::ATTR_DRIVER_NAME);
  229.         }
  230.         $driver self::createDriver($params);
  231.         $wrapperClass Connection::class;
  232.         if (isset($params['wrapperClass'])) {
  233.             if (! is_subclass_of($params['wrapperClass'], $wrapperClass)) {
  234.                 throw Exception::invalidWrapperClass($params['wrapperClass']);
  235.             }
  236.             /** @var class-string<Connection> $wrapperClass */
  237.             $wrapperClass $params['wrapperClass'];
  238.         }
  239.         return new $wrapperClass($params$driver$config$eventManager);
  240.     }
  241.     /**
  242.      * Returns the list of supported drivers.
  243.      *
  244.      * @return string[]
  245.      */
  246.     public static function getAvailableDrivers(): array
  247.     {
  248.         return array_keys(self::DRIVER_MAP);
  249.     }
  250.     /**
  251.      * @param array<string,mixed> $params
  252.      * @psalm-param Params $params
  253.      * @phpstan-param array<string,mixed> $params
  254.      *
  255.      * @throws Exception
  256.      */
  257.     private static function createDriver(array $params): Driver
  258.     {
  259.         if (isset($params['driverClass'])) {
  260.             $interfaces class_implements($params['driverClass'], true);
  261.             if ($interfaces === false || ! in_array(Driver::class, $interfaces)) {
  262.                 throw Exception::invalidDriverClass($params['driverClass']);
  263.             }
  264.             return new $params['driverClass']();
  265.         }
  266.         if (isset($params['driver'])) {
  267.             if (! isset(self::DRIVER_MAP[$params['driver']])) {
  268.                 throw Exception::unknownDriver($params['driver'], array_keys(self::DRIVER_MAP));
  269.             }
  270.             $class self::DRIVER_MAP[$params['driver']];
  271.             return new $class();
  272.         }
  273.         throw Exception::driverRequired();
  274.     }
  275.     /**
  276.      * Normalizes the given connection URL path.
  277.      *
  278.      * @return string The normalized connection URL path
  279.      */
  280.     private static function normalizeDatabaseUrlPath(string $urlPath): string
  281.     {
  282.         // Trim leading slash from URL path.
  283.         return substr($urlPath1);
  284.     }
  285.     /**
  286.      * Extracts parts from a database URL, if present, and returns an
  287.      * updated list of parameters.
  288.      *
  289.      * @param mixed[] $params The list of parameters.
  290.      * @psalm-param Params $params
  291.      * @phpstan-param array<string,mixed> $params
  292.      *
  293.      * @return mixed[] A modified list of parameters with info from a database
  294.      *                 URL extracted into indidivual parameter parts.
  295.      * @psalm-return Params
  296.      * @phpstan-return array<string,mixed>
  297.      *
  298.      * @throws Exception
  299.      */
  300.     private static function parseDatabaseUrl(array $params): array
  301.     {
  302.         if (! isset($params['url'])) {
  303.             return $params;
  304.         }
  305.         // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid
  306.         $url preg_replace('#^((?:pdo_)?sqlite3?):///#''$1://localhost/'$params['url']);
  307.         $url parse_url($url);
  308.         if ($url === false) {
  309.             throw new Exception('Malformed parameter "url".');
  310.         }
  311.         foreach ($url as $param => $value) {
  312.             if (! is_string($value)) {
  313.                 continue;
  314.             }
  315.             $url[$param] = rawurldecode($value);
  316.         }
  317.         // If we have a connection URL, we have to unset the default PDO instance connection parameter (if any)
  318.         // as we cannot merge connection details from the URL into the PDO instance (URL takes precedence).
  319.         unset($params['pdo']);
  320.         $params self::parseDatabaseUrlScheme($url['scheme'] ?? null$params);
  321.         if (isset($url['host'])) {
  322.             $params['host'] = $url['host'];
  323.         }
  324.         if (isset($url['port'])) {
  325.             $params['port'] = $url['port'];
  326.         }
  327.         if (isset($url['user'])) {
  328.             $params['user'] = $url['user'];
  329.         }
  330.         if (isset($url['pass'])) {
  331.             $params['password'] = $url['pass'];
  332.         }
  333.         $params self::parseDatabaseUrlPath($url$params);
  334.         $params self::parseDatabaseUrlQuery($url$params);
  335.         return $params;
  336.     }
  337.     /**
  338.      * Parses the given connection URL and resolves the given connection parameters.
  339.      *
  340.      * Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters
  341.      * via {@link parseDatabaseUrlScheme}.
  342.      *
  343.      * @see parseDatabaseUrlScheme
  344.      *
  345.      * @param mixed[] $url    The URL parts to evaluate.
  346.      * @param mixed[] $params The connection parameters to resolve.
  347.      *
  348.      * @return mixed[] The resolved connection parameters.
  349.      */
  350.     private static function parseDatabaseUrlPath(array $url, array $params): array
  351.     {
  352.         if (! isset($url['path'])) {
  353.             return $params;
  354.         }
  355.         $url['path'] = self::normalizeDatabaseUrlPath($url['path']);
  356.         // If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate
  357.         // and therefore treat the path as regular DBAL connection URL path.
  358.         if (! isset($params['driver'])) {
  359.             return self::parseRegularDatabaseUrlPath($url$params);
  360.         }
  361.         if (strpos($params['driver'], 'sqlite') !== false) {
  362.             return self::parseSqliteDatabaseUrlPath($url$params);
  363.         }
  364.         return self::parseRegularDatabaseUrlPath($url$params);
  365.     }
  366.     /**
  367.      * Parses the query part of the given connection URL and resolves the given connection parameters.
  368.      *
  369.      * @param mixed[] $url    The connection URL parts to evaluate.
  370.      * @param mixed[] $params The connection parameters to resolve.
  371.      *
  372.      * @return mixed[] The resolved connection parameters.
  373.      */
  374.     private static function parseDatabaseUrlQuery(array $url, array $params): array
  375.     {
  376.         if (! isset($url['query'])) {
  377.             return $params;
  378.         }
  379.         $query = [];
  380.         parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode
  381.         return array_merge($params$query); // parse_str wipes existing array elements
  382.     }
  383.     /**
  384.      * Parses the given regular connection URL and resolves the given connection parameters.
  385.      *
  386.      * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}.
  387.      *
  388.      * @see normalizeDatabaseUrlPath
  389.      *
  390.      * @param mixed[] $url    The regular connection URL parts to evaluate.
  391.      * @param mixed[] $params The connection parameters to resolve.
  392.      *
  393.      * @return mixed[] The resolved connection parameters.
  394.      */
  395.     private static function parseRegularDatabaseUrlPath(array $url, array $params): array
  396.     {
  397.         $params['dbname'] = $url['path'];
  398.         return $params;
  399.     }
  400.     /**
  401.      * Parses the given SQLite connection URL and resolves the given connection parameters.
  402.      *
  403.      * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}.
  404.      *
  405.      * @see normalizeDatabaseUrlPath
  406.      *
  407.      * @param mixed[] $url    The SQLite connection URL parts to evaluate.
  408.      * @param mixed[] $params The connection parameters to resolve.
  409.      *
  410.      * @return mixed[] The resolved connection parameters.
  411.      */
  412.     private static function parseSqliteDatabaseUrlPath(array $url, array $params): array
  413.     {
  414.         if ($url['path'] === ':memory:') {
  415.             $params['memory'] = true;
  416.             return $params;
  417.         }
  418.         $params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key
  419.         return $params;
  420.     }
  421.     /**
  422.      * Parses the scheme part from given connection URL and resolves the given connection parameters.
  423.      *
  424.      * @param string|null $scheme The connection URL scheme, if available
  425.      * @param mixed[]     $params The connection parameters to resolve.
  426.      *
  427.      * @return mixed[] The resolved connection parameters.
  428.      *
  429.      * @throws Exception If parsing failed or resolution is not possible.
  430.      */
  431.     private static function parseDatabaseUrlScheme($scheme, array $params): array
  432.     {
  433.         if ($scheme !== null) {
  434.             // The requested driver from the URL scheme takes precedence
  435.             // over the default custom driver from the connection parameters (if any).
  436.             unset($params['driverClass']);
  437.             // URL schemes must not contain underscores, but dashes are ok
  438.             $driver str_replace('-''_'$scheme);
  439.             // The requested driver from the URL scheme takes precedence over the
  440.             // default driver from the connection parameters. If the driver is
  441.             // an alias (e.g. "postgres"), map it to the actual name ("pdo-pgsql").
  442.             // Otherwise, let checkParams decide later if the driver exists.
  443.             $params['driver'] = self::$driverSchemeAliases[$driver] ?? $driver;
  444.             return $params;
  445.         }
  446.         // If a schemeless connection URL is given, we require a default driver or default custom driver
  447.         // as connection parameter.
  448.         if (! isset($params['driverClass']) && ! isset($params['driver'])) {
  449.             throw Exception::driverRequired($params['url']);
  450.         }
  451.         return $params;
  452.     }
  453. }