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

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