vendor/doctrine/dbal/lib/Doctrine/DBAL/DriverManager.php line 190

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