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

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