vendor/doctrine/doctrine-bundle/src/ConnectionFactory.php line 200

Open in your IDE?
  1. <?php
  2. namespace Doctrine\Bundle\DoctrineBundle;
  3. use Doctrine\Common\EventManager;
  4. use Doctrine\DBAL\Configuration;
  5. use Doctrine\DBAL\Connection;
  6. use Doctrine\DBAL\Connection\StaticServerVersionProvider;
  7. use Doctrine\DBAL\ConnectionException;
  8. use Doctrine\DBAL\DriverManager;
  9. use Doctrine\DBAL\Exception as DBALException;
  10. use Doctrine\DBAL\Exception\DriverException;
  11. use Doctrine\DBAL\Exception\DriverRequired;
  12. use Doctrine\DBAL\Exception\InvalidWrapperClass;
  13. use Doctrine\DBAL\Exception\MalformedDsnException;
  14. use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
  15. use Doctrine\DBAL\Platforms\AbstractPlatform;
  16. use Doctrine\DBAL\Tools\DsnParser;
  17. use Doctrine\DBAL\Types\Type;
  18. use Doctrine\Deprecations\Deprecation;
  19. use InvalidArgumentException;
  20. use function array_merge;
  21. use function class_exists;
  22. use function is_subclass_of;
  23. use function method_exists;
  24. use function trigger_deprecation;
  25. use const PHP_EOL;
  26. /** @psalm-import-type Params from DriverManager */
  27. class ConnectionFactory
  28. {
  29.     /** @internal */
  30.     public const DEFAULT_SCHEME_MAP = [
  31.         'db2'        => 'ibm_db2',
  32.         'mssql'      => 'pdo_sqlsrv',
  33.         'mysql'      => 'pdo_mysql',
  34.         'mysql2'     => 'pdo_mysql'// Amazon RDS, for some weird reason
  35.         'postgres'   => 'pdo_pgsql',
  36.         'postgresql' => 'pdo_pgsql',
  37.         'pgsql'      => 'pdo_pgsql',
  38.         'sqlite'     => 'pdo_sqlite',
  39.         'sqlite3'    => 'pdo_sqlite',
  40.     ];
  41.     /** @var mixed[][] */
  42.     private array $typesConfig = [];
  43.     private DsnParser $dsnParser;
  44.     private bool $initialized false;
  45.     /** @param mixed[][] $typesConfig */
  46.     public function __construct(array $typesConfig, ?DsnParser $dsnParser null)
  47.     {
  48.         $this->typesConfig $typesConfig;
  49.         $this->dsnParser   $dsnParser ?? new DsnParser(self::DEFAULT_SCHEME_MAP);
  50.     }
  51.     /**
  52.      * Create a connection by name.
  53.      *
  54.      * @param mixed[]               $params
  55.      * @param array<string, string> $mappingTypes
  56.      * @psalm-param Params $params
  57.      *
  58.      * @return Connection
  59.      */
  60.     public function createConnection(array $params, ?Configuration $config null, ?EventManager $eventManager null, array $mappingTypes = [])
  61.     {
  62.         if (! method_exists(Connection::class, 'getEventManager') && $eventManager !== null) {
  63.             throw new InvalidArgumentException('Passing an EventManager instance is not supported with DBAL > 3');
  64.         }
  65.         if (! $this->initialized) {
  66.             $this->initializeTypes();
  67.         }
  68.         $overriddenOptions = [];
  69.         /** @psalm-suppress InvalidArrayOffset We should adjust when https://github.com/vimeo/psalm/issues/8984 is fixed */
  70.         if (isset($params['connection_override_options'])) {
  71.             trigger_deprecation('doctrine/doctrine-bundle''2.4''The "connection_override_options" connection parameter is deprecated');
  72.             $overriddenOptions $params['connection_override_options'];
  73.             unset($params['connection_override_options']);
  74.         }
  75.         $params $this->parseDatabaseUrl($params);
  76.         // URL support for PrimaryReplicaConnection
  77.         if (isset($params['primary'])) {
  78.             $params['primary'] = $this->parseDatabaseUrl($params['primary']);
  79.         }
  80.         if (isset($params['replica'])) {
  81.             foreach ($params['replica'] as $key => $replicaParams) {
  82.                 $params['replica'][$key] = $this->parseDatabaseUrl($replicaParams);
  83.             }
  84.         }
  85.         /** @psalm-suppress InvalidArrayOffset We should adjust when https://github.com/vimeo/psalm/issues/8984 is fixed */
  86.         if (! isset($params['pdo']) && (! isset($params['charset']) || $overriddenOptions || isset($params['dbname_suffix']))) {
  87.             $wrapperClass null;
  88.             if (isset($params['wrapperClass'])) {
  89.                 if (! is_subclass_of($params['wrapperClass'], Connection::class)) {
  90.                     if (class_exists(InvalidWrapperClass::class)) {
  91.                         throw InvalidWrapperClass::new($params['wrapperClass']);
  92.                     }
  93.                     throw DBALException::invalidWrapperClass($params['wrapperClass']);
  94.                 }
  95.                 $wrapperClass           $params['wrapperClass'];
  96.                 $params['wrapperClass'] = null;
  97.             }
  98.             $connection DriverManager::getConnection(...array_merge([$params$config], $eventManager ? [$eventManager] : []));
  99.             $params     $this->addDatabaseSuffix(array_merge($connection->getParams(), $overriddenOptions));
  100.             $driver     $connection->getDriver();
  101.             /** @psalm-suppress InvalidScalarArgument Bogus error, StaticServerVersionProvider implements Doctrine\DBAL\ServerVersionProvider  */
  102.             $platform $driver->getDatabasePlatform(
  103.                 ...(class_exists(StaticServerVersionProvider::class) ? [new StaticServerVersionProvider($params['serverVersion'] ?? '')] : []),
  104.             );
  105.             if (! isset($params['charset'])) {
  106.                 if ($platform instanceof AbstractMySQLPlatform) {
  107.                     $params['charset'] = 'utf8mb4';
  108.                     if (isset($params['defaultTableOptions']['collate'])) {
  109.                         Deprecation::trigger(
  110.                             'doctrine/doctrine-bundle',
  111.                             'https://github.com/doctrine/dbal/issues/5214',
  112.                             'The "collate" default table option is deprecated in favor of "collation" and will be removed in doctrine/doctrine-bundle 3.0. ',
  113.                         );
  114.                         $params['defaultTableOptions']['collation'] = $params['defaultTableOptions']['collate'];
  115.                         unset($params['defaultTableOptions']['collate']);
  116.                     }
  117.                     if (! isset($params['defaultTableOptions']['collation'])) {
  118.                         $params['defaultTableOptions']['collation'] = 'utf8mb4_unicode_ci';
  119.                     }
  120.                 } else {
  121.                     $params['charset'] = 'utf8';
  122.                 }
  123.             }
  124.             if ($wrapperClass !== null) {
  125.                 $params['wrapperClass'] = $wrapperClass;
  126.             } else {
  127.                 $wrapperClass Connection::class;
  128.             }
  129.             $connection = new $wrapperClass($params$driver$config$eventManager);
  130.         } else {
  131.             $connection DriverManager::getConnection(...array_merge([$params$config], $eventManager ? [$eventManager] : []));
  132.         }
  133.         if (! empty($mappingTypes)) {
  134.             $platform $this->getDatabasePlatform($connection);
  135.             foreach ($mappingTypes as $dbType => $doctrineType) {
  136.                 $platform->registerDoctrineTypeMapping($dbType$doctrineType);
  137.             }
  138.         }
  139.         return $connection;
  140.     }
  141.     /**
  142.      * Try to get the database platform.
  143.      *
  144.      * This could fail if types should be registered to an predefined/unused connection
  145.      * and the platform version is unknown.
  146.      *
  147.      * @link https://github.com/doctrine/DoctrineBundle/issues/673
  148.      *
  149.      * @throws DBALException
  150.      */
  151.     private function getDatabasePlatform(Connection $connection): AbstractPlatform
  152.     {
  153.         try {
  154.             return $connection->getDatabasePlatform();
  155.         } catch (DriverException $driverException) {
  156.             $class class_exists(DBALException::class) ? DBALException::class : ConnectionException::class;
  157.             throw new $class(
  158.                 'An exception occurred while establishing a connection to figure out your platform version.' PHP_EOL .
  159.                 "You can circumvent this by setting a 'server_version' configuration value" PHP_EOL PHP_EOL .
  160.                 'For further information have a look at:' PHP_EOL .
  161.                 'https://github.com/doctrine/DoctrineBundle/issues/673',
  162.                 0,
  163.                 $driverException,
  164.             );
  165.         }
  166.     }
  167.     /**
  168.      * initialize the types
  169.      */
  170.     private function initializeTypes(): void
  171.     {
  172.         foreach ($this->typesConfig as $typeName => $typeConfig) {
  173.             if (Type::hasType($typeName)) {
  174.                 Type::overrideType($typeName$typeConfig['class']);
  175.             } else {
  176.                 Type::addType($typeName$typeConfig['class']);
  177.             }
  178.         }
  179.         $this->initialized true;
  180.     }
  181.     /**
  182.      * @param array<string, mixed> $params
  183.      *
  184.      * @return array<string, mixed>
  185.      */
  186.     private function addDatabaseSuffix(array $params): array
  187.     {
  188.         if (isset($params['dbname']) && isset($params['dbname_suffix'])) {
  189.             $params['dbname'] .= $params['dbname_suffix'];
  190.         }
  191.         foreach ($params['replica'] ?? [] as $key => $replicaParams) {
  192.             if (! isset($replicaParams['dbname'], $replicaParams['dbname_suffix'])) {
  193.                 continue;
  194.             }
  195.             $params['replica'][$key]['dbname'] .= $replicaParams['dbname_suffix'];
  196.         }
  197.         if (isset($params['primary']['dbname'], $params['primary']['dbname_suffix'])) {
  198.             $params['primary']['dbname'] .= $params['primary']['dbname_suffix'];
  199.         }
  200.         return $params;
  201.     }
  202.     /**
  203.      * Extracts parts from a database URL, if present, and returns an
  204.      * updated list of parameters.
  205.      *
  206.      * @param mixed[] $params The list of parameters.
  207.      * @psalm-param Params $params
  208.      *
  209.      * @return mixed[] A modified list of parameters with info from a database
  210.      *                 URL extracted into individual parameter parts.
  211.      * @psalm-return Params
  212.      *
  213.      * @throws DBALException
  214.      */
  215.     private function parseDatabaseUrl(array $params): array
  216.     {
  217.         /** @psalm-suppress InvalidArrayOffset Need to be compatible with DBAL < 4, which still has `$params['url']` */
  218.         if (! isset($params['url'])) {
  219.             return $params;
  220.         }
  221.         try {
  222.             $parsedParams $this->dsnParser->parse($params['url']);
  223.         } catch (MalformedDsnException $e) {
  224.             throw new MalformedDsnException('Malformed parameter "url".'0$e);
  225.         }
  226.         if (isset($parsedParams['driver'])) {
  227.             // The requested driver from the URL scheme takes precedence
  228.             // over the default custom driver from the connection parameters (if any).
  229.             unset($params['driverClass']);
  230.         }
  231.         $params array_merge($params$parsedParams);
  232.         // If a schemeless connection URL is given, we require a default driver or default custom driver
  233.         // as connection parameter.
  234.         if (! isset($params['driverClass']) && ! isset($params['driver'])) {
  235.             if (class_exists(DriverRequired::class)) {
  236.                 throw DriverRequired::new($params['url']);
  237.             }
  238.             throw DBALException::driverRequired($params['url']);
  239.         }
  240.         unset($params['url']);
  241.         return $params;
  242.     }
  243. }