vendor/doctrine/doctrine-bundle/ConnectionFactory.php line 70

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