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

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)
  104.                     ? [new StaticServerVersionProvider($params['serverVersion'] ?? $params['primary']['serverVersion'] ?? '')]
  105.                     : []
  106.                 ),
  107.             );
  108.             if (! isset($params['charset'])) {
  109.                 if ($platform instanceof AbstractMySQLPlatform) {
  110.                     $params['charset'] = 'utf8mb4';
  111.                     if (isset($params['defaultTableOptions']['collate'])) {
  112.                         Deprecation::trigger(
  113.                             'doctrine/doctrine-bundle',
  114.                             'https://github.com/doctrine/dbal/issues/5214',
  115.                             'The "collate" default table option is deprecated in favor of "collation" and will be removed in doctrine/doctrine-bundle 3.0. ',
  116.                         );
  117.                         $params['defaultTableOptions']['collation'] = $params['defaultTableOptions']['collate'];
  118.                         unset($params['defaultTableOptions']['collate']);
  119.                     }
  120.                     if (! isset($params['defaultTableOptions']['collation'])) {
  121.                         $params['defaultTableOptions']['collation'] = 'utf8mb4_unicode_ci';
  122.                     }
  123.                 } else {
  124.                     $params['charset'] = 'utf8';
  125.                 }
  126.             }
  127.             if ($wrapperClass !== null) {
  128.                 $params['wrapperClass'] = $wrapperClass;
  129.             } else {
  130.                 $wrapperClass Connection::class;
  131.             }
  132.             $connection = new $wrapperClass($params$driver$config$eventManager);
  133.         } else {
  134.             $connection DriverManager::getConnection(...array_merge([$params$config], $eventManager ? [$eventManager] : []));
  135.         }
  136.         if (! empty($mappingTypes)) {
  137.             $platform $this->getDatabasePlatform($connection);
  138.             foreach ($mappingTypes as $dbType => $doctrineType) {
  139.                 $platform->registerDoctrineTypeMapping($dbType$doctrineType);
  140.             }
  141.         }
  142.         return $connection;
  143.     }
  144.     /**
  145.      * Try to get the database platform.
  146.      *
  147.      * This could fail if types should be registered to an predefined/unused connection
  148.      * and the platform version is unknown.
  149.      *
  150.      * @link https://github.com/doctrine/DoctrineBundle/issues/673
  151.      *
  152.      * @throws DBALException
  153.      */
  154.     private function getDatabasePlatform(Connection $connection): AbstractPlatform
  155.     {
  156.         try {
  157.             return $connection->getDatabasePlatform();
  158.         } catch (DriverException $driverException) {
  159.             $class class_exists(DBALException::class) ? DBALException::class : ConnectionException::class;
  160.             throw new $class(
  161.                 'An exception occurred while establishing a connection to figure out your platform version.' PHP_EOL .
  162.                 "You can circumvent this by setting a 'server_version' configuration value" PHP_EOL PHP_EOL .
  163.                 'For further information have a look at:' PHP_EOL .
  164.                 'https://github.com/doctrine/DoctrineBundle/issues/673',
  165.                 0,
  166.                 $driverException,
  167.             );
  168.         }
  169.     }
  170.     /**
  171.      * initialize the types
  172.      */
  173.     private function initializeTypes(): void
  174.     {
  175.         foreach ($this->typesConfig as $typeName => $typeConfig) {
  176.             if (Type::hasType($typeName)) {
  177.                 Type::overrideType($typeName$typeConfig['class']);
  178.             } else {
  179.                 Type::addType($typeName$typeConfig['class']);
  180.             }
  181.         }
  182.         $this->initialized true;
  183.     }
  184.     /**
  185.      * @param array<string, mixed> $params
  186.      *
  187.      * @return array<string, mixed>
  188.      */
  189.     private function addDatabaseSuffix(array $params): array
  190.     {
  191.         if (isset($params['dbname']) && isset($params['dbname_suffix'])) {
  192.             $params['dbname'] .= $params['dbname_suffix'];
  193.         }
  194.         foreach ($params['replica'] ?? [] as $key => $replicaParams) {
  195.             if (! isset($replicaParams['dbname'], $replicaParams['dbname_suffix'])) {
  196.                 continue;
  197.             }
  198.             $params['replica'][$key]['dbname'] .= $replicaParams['dbname_suffix'];
  199.         }
  200.         if (isset($params['primary']['dbname'], $params['primary']['dbname_suffix'])) {
  201.             $params['primary']['dbname'] .= $params['primary']['dbname_suffix'];
  202.         }
  203.         return $params;
  204.     }
  205.     /**
  206.      * Extracts parts from a database URL, if present, and returns an
  207.      * updated list of parameters.
  208.      *
  209.      * @param mixed[] $params The list of parameters.
  210.      * @psalm-param Params $params
  211.      *
  212.      * @return mixed[] A modified list of parameters with info from a database
  213.      *                 URL extracted into individual parameter parts.
  214.      * @psalm-return Params
  215.      *
  216.      * @throws DBALException
  217.      */
  218.     private function parseDatabaseUrl(array $params): array
  219.     {
  220.         /** @psalm-suppress InvalidArrayOffset Need to be compatible with DBAL < 4, which still has `$params['url']` */
  221.         if (! isset($params['url'])) {
  222.             return $params;
  223.         }
  224.         try {
  225.             $parsedParams $this->dsnParser->parse($params['url']);
  226.         } catch (MalformedDsnException $e) {
  227.             throw new MalformedDsnException('Malformed parameter "url".'0$e);
  228.         }
  229.         if (isset($parsedParams['driver'])) {
  230.             // The requested driver from the URL scheme takes precedence
  231.             // over the default custom driver from the connection parameters (if any).
  232.             unset($params['driverClass']);
  233.         }
  234.         $params array_merge($params$parsedParams);
  235.         // If a schemeless connection URL is given, we require a default driver or default custom driver
  236.         // as connection parameter.
  237.         if (! isset($params['driverClass']) && ! isset($params['driver'])) {
  238.             if (class_exists(DriverRequired::class)) {
  239.                 throw DriverRequired::new($params['url']);
  240.             }
  241.             throw DBALException::driverRequired($params['url']);
  242.         }
  243.         unset($params['url']);
  244.         return $params;
  245.     }
  246. }