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

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