‪TYPO3CMS  ‪main
ConnectionPool.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Doctrine\DBAL\Configuration;
21 use Doctrine\DBAL\Driver\Middleware as DriverMiddleware;
22 use Doctrine\DBAL\DriverManager;
23 use Doctrine\DBAL\Types\Type;
24 use Doctrine\DBAL\Types\Types;
26 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
34 
46 {
50  public const ‪DEFAULT_CONNECTION_NAME = 'Default';
51 
55  protected static ‪$connections = [];
56 
60  protected array ‪$customDoctrineTypes = [
61  ‪EnumType::TYPE => EnumType::class,
62  ‪SetType::TYPE => SetType::class,
63  ];
64 
68  protected array ‪$overrideDoctrineTypes = [
69  Types::DATE_MUTABLE => DateType::class,
70  Types::DATETIME_MUTABLE => DateTimeType::class,
71  Types::DATETIME_IMMUTABLE => DateTimeType::class,
72  Types::TIME_MUTABLE => TimeType::class,
73  ];
74 
83  public function ‪getConnectionForTable(string $tableName): ‪Connection
84  {
85  if (empty($tableName)) {
86  throw new \UnexpectedValueException(
87  'ConnectionPool->getConnectionForTable() requires a table name to be provided.',
88  1459421719
89  );
90  }
91 
92  $connectionName = ‪self::DEFAULT_CONNECTION_NAME;
93  if (!empty(‪$GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName])) {
94  $connectionName = (string)‪$GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName];
95  }
96 
97  return $this->‪getConnectionByName($connectionName);
98  }
99 
109  public function ‪getConnectionByName(string $connectionName): ‪Connection
110  {
111  if (empty($connectionName)) {
112  throw new \UnexpectedValueException(
113  'ConnectionPool->getConnectionByName() requires a connection name to be provided.',
114  1459422125
115  );
116  }
117 
118  if (isset(static::$connections[$connectionName])) {
119  return static::$connections[$connectionName];
120  }
121 
122  static::$connections[$connectionName] = $this->‪getDatabaseConnection(
123  $connectionName,
124  $this->‪getConnectionParams($connectionName),
125  );
126 
127  return static::$connections[$connectionName];
128  }
129 
130  protected function ‪getConnectionParams(string $connectionName): array
131  {
132  $connectionParams = ‪$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][$connectionName] ?? [];
133  if (empty($connectionParams)) {
134  throw new \RuntimeException(
135  'The requested database connection named "' . $connectionName . '" has not been configured.',
136  1459422492
137  );
138  }
139 
140  if (empty($connectionParams['wrapperClass'])) {
141  $connectionParams['wrapperClass'] = Connection::class;
142  }
143 
144  if (!is_a($connectionParams['wrapperClass'], Connection::class, true)) {
145  throw new \UnexpectedValueException(
146  'The "wrapperClass" for the connection name "' . $connectionName .
147  '" needs to be a subclass of "' . Connection::class . '".',
148  1459422968
149  );
150  }
151 
152  // Transform TYPO3 `tableoptions` to valid `doctrine/dbal` connection param option `defaultTableOptions`
153  // @todo TYPO3 database configuration should be changed to directly write defaultTableOptions instead,
154  // with proper upgrade migration. Along with that, default table options for MySQL in
155  // testing-framework and core should be adjusted.
156  if (isset($connectionParams['tableoptions'])) {
157  $connectionParams['defaultTableOptions'] = array_replace(
158  $connectionParams['defaultTableOptions'] ?? [],
159  $connectionParams['tableoptions']
160  );
161  unset($connectionParams['tableoptions']);
162  }
163 
164  // Ensure integer value for port.
165  if (array_key_exists('port', $connectionParams)) {
166  $connectionParams['port'] = (int)($connectionParams['port'] ?? 0);
167  }
168 
169  return $connectionParams;
170  }
171 
177  protected function ‪getDriverMiddlewares(string $connectionName, array $connectionParams): array
178  {
179  ‪$driverMiddlewares = $this->getOrderedConnectionDriverMiddlewareConfiguration($connectionName, $connectionParams);
180  $middlewares = [];
181  foreach (‪$driverMiddlewares as $middlewareConfiguration) {
182  $className = $middlewareConfiguration['target'];
183  $disabled = $middlewareConfiguration['disabled'];
184  if ($disabled === true) {
185  // Middleware disabled, skip to next middleware.
186  continue;
187  }
188 
189  $middlewares[] = GeneralUtility::makeInstance($className);
190  }
191 
192  return $middlewares;
193  }
194 
200  {
201  $configurationArray = [
202  'Raw' => [
203  'GlobalDriverMiddlewares' => ‪$GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares'] ?? [],
204  'Connections' => [],
205  ],
206  'Connections' => [],
207  ];
208  foreach (array_keys(‪$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']) as $connectionName) {
209  $connectionParams = $this->‪getConnectionParams($connectionName);
210  $configurationArray['Raw']['Connections'][$connectionName] = $connectionParams;
211  $configurationArray['Connections'][$connectionName] = $this->getOrderedConnectionDriverMiddlewareConfiguration($connectionName, $connectionParams);
212  }
213  return $configurationArray;
214  }
215 
220  protected function getOrderedConnectionDriverMiddlewareConfiguration(string $connectionName, array $connectionParams): array
221  {
223  $driverMiddlewareService = GeneralUtility::makeInstance(DriverMiddlewareService::class);
226  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['DB']['globalDriverMiddlewares'] ?? [] as ‪$identifier => $middleware) {
228  ‪$driverMiddlewares[‪$identifier] = $driverMiddlewareService->ensureCompleteMiddlewareConfiguration(
229  $driverMiddlewareService->normalizeMiddlewareConfiguration(‪$identifier, $middleware)
230  );
231  ‪$driverMiddlewares[‪$identifier]['type'] = 'global';
232  }
233  foreach ($connectionParams['driverMiddlewares'] ?? [] as ‪$identifier => $middleware) {
234  ‪$identifier = (string)‪$identifier;
235  $middleware = array_replace(
237  $driverMiddlewareService->normalizeMiddlewareConfiguration(‪$identifier, $middleware)
238  );
239  $middleware = $driverMiddlewareService->ensureCompleteMiddlewareConfiguration($middleware);
240  ‪$driverMiddlewares[‪$identifier] = $middleware;
242  ? 'global-with-connection-override'
243  : 'connection';
244  }
245  ‪$driverMiddlewares = array_filter(‪$driverMiddlewares, static function (array $middleware) use ($connectionName, $connectionParams): bool {
246  $className = $middleware['target'];
247  $classImplements = class_exists($className) ? (class_implements($className) ?: []) : [];
248  if (!in_array(DriverMiddleware::class, $classImplements, true)) {
249  throw new \UnexpectedValueException(
250  sprintf(
251  'Doctrine Driver Middleware "%s" must implement \Doctrine\DBAL\Driver\Middleware',
252  $className
253  ),
254  1677958727
255  );
256  }
257  if (in_array(UsableForConnectionInterface::class, $classImplements, true)) {
258  return GeneralUtility::makeInstance($middleware['target'])->canBeUsedForConnection($connectionName, $connectionParams);
259  }
260 
261  return true;
262  });
263 
264  return $driverMiddlewareService->order(‪$driverMiddlewares);
265  }
266 
270  protected function ‪getDatabaseConnection(string $connectionName, array $connectionParams): Connection
271  {
272  $this->‪registerDoctrineTypes();
273 
274  // Default to UTF-8 connection charset
275  if (empty($connectionParams['charset'])) {
276  $connectionParams['charset'] = 'utf8';
277  }
278 
279  $middlewares = $this->‪getDriverMiddlewares($connectionName, $connectionParams);
280  $configuration = (new Configuration())
281  ->setMiddlewares($middlewares)
282  // @link https://github.com/doctrine/dbal/blob/3.7.x/UPGRADE.md#deprecated-not-setting-a-schema-manager-factory
283  ->setSchemaManagerFactory(GeneralUtility::makeInstance(CoreSchemaManagerFactory::class));
284 
286  $conn = DriverManager::getConnection($connectionParams, $configuration);
287  $conn->prepareConnection($connectionParams['initCommands'] ?? '');
288 
289  // Register all custom data types in the type mapping
290  foreach ($this->customDoctrineTypes as $type => $className) {
291  $conn->getDatabasePlatform()->registerDoctrineTypeMapping($type, $type);
292  }
293 
294  // Register all override data types in the type mapping
295  foreach ($this->overrideDoctrineTypes as $type => $className) {
296  $conn->getDatabasePlatform()->registerDoctrineTypeMapping($type, $type);
297  }
298 
299  return $conn;
300  }
301 
306  public function ‪getQueryBuilderForTable(string $tableName): QueryBuilder
307  {
308  if (empty($tableName)) {
309  throw new \UnexpectedValueException(
310  'ConnectionPool->getQueryBuilderForTable() requires a connection name to be provided.',
311  1459423448
312  );
313  }
314 
315  return $this->‪getConnectionForTable($tableName)->createQueryBuilder();
316  }
317 
326  public function ‪getConnectionNames(): array
327  {
328  return array_keys(‪$GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']);
329  }
330 
339  public function ‪registerDoctrineTypes(): void
340  {
341  // Register custom data types
342  foreach ($this->customDoctrineTypes as $type => $className) {
343  if (!Type::hasType($type)) {
344  Type::addType($type, $className);
345  }
346  }
347  // Override data types
348  foreach ($this->overrideDoctrineTypes as $type => $className) {
349  if (!Type::hasType($type)) {
350  Type::addType($type, $className);
351  continue;
352  }
353  Type::overrideType($type, $className);
354  }
355  }
356 
362  public function ‪resetConnections(): void
363  {
364  static::$connections = [];
365  }
366 }
‪TYPO3\CMS\Core\Database\Schema\Types\DateTimeType
Definition: DateTimeType.php:30
‪TYPO3\CMS\Core\Database\ConnectionPool\getConnectionParams
‪getConnectionParams(string $connectionName)
Definition: ConnectionPool.php:129
‪TYPO3\CMS\Core\Database\ConnectionPool\registerDoctrineTypes
‪registerDoctrineTypes()
Definition: ConnectionPool.php:338
‪TYPO3\CMS\Core\Database\Schema\Types\SetType
Definition: SetType.php:29
‪TYPO3\CMS\Core\Database\ConnectionPool\getConnectionNames
‪getConnectionNames()
Definition: ConnectionPool.php:325
‪TYPO3\CMS\Core\Database\Schema\Types\EnumType
Definition: EnumType.php:29
‪TYPO3\CMS\Core\Database\Schema\Types\DateType
Definition: DateType.php:30
‪TYPO3\CMS\Core\Database\ConnectionPool\getConnectionMiddlewareConfigurationArrayForLowLevelConfiguration
‪array getConnectionMiddlewareConfigurationArrayForLowLevelConfiguration()
Definition: ConnectionPool.php:198
‪TYPO3\CMS\Core\Database\ConnectionPool\getDriverMiddlewares
‪getDriverMiddlewares(string $connectionName, array $connectionParams)
Definition: ConnectionPool.php:176
‪TYPO3\CMS\Core\Database\Schema\Types\EnumType\TYPE
‪const TYPE
Definition: EnumType.php:30
‪TYPO3\CMS\Core\Database\ConnectionPool\DEFAULT_CONNECTION_NAME
‪const DEFAULT_CONNECTION_NAME
Definition: ConnectionPool.php:50
‪TYPO3\CMS\Core\Database\ConnectionPool\$customDoctrineTypes
‪array $customDoctrineTypes
Definition: ConnectionPool.php:59
‪TYPO3\CMS\Core\Database\Schema\SchemaManager\CoreSchemaManagerFactory
Definition: CoreSchemaManagerFactory.php:43
‪TYPO3\CMS\Core\Database\ConnectionPool\$connections
‪static Connection[] $connections
Definition: ConnectionPool.php:54
‪TYPO3\CMS\Core\Database\ConnectionPool\getConnectionByName
‪getConnectionByName(string $connectionName)
Definition: ConnectionPool.php:108
‪TYPO3\CMS\Core\Database\ConnectionPool\$driverMiddlewares
‪$driverMiddlewares[$identifier]['type']
Definition: ConnectionPool.php:230
‪TYPO3\CMS\Core\Database\ConnectionPool\getQueryBuilderForTable
‪getQueryBuilderForTable(string $tableName)
Definition: ConnectionPool.php:305
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Database\Schema\Types\TimeType
Definition: TimeType.php:30
‪TYPO3\CMS\Core\Database\ConnectionPool\getConnectionForTable
‪getConnectionForTable(string $tableName)
Definition: ConnectionPool.php:82
‪TYPO3\CMS\Core\Database\Middleware\UsableForConnectionInterface
Definition: UsableForConnectionInterface.php:34
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Database\ConnectionPool\getDatabaseConnection
‪getDatabaseConnection(string $connectionName, array $connectionParams)
Definition: ConnectionPool.php:269
‪TYPO3\CMS\Core\Database\Schema\Types\SetType\TYPE
‪const TYPE
Definition: SetType.php:30
‪TYPO3\CMS\Core\Database\ConnectionPool\resetConnections
‪resetConnections()
Definition: ConnectionPool.php:361
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Core\Database
Definition: Connection.php:18
‪TYPO3\CMS\Core\Database\ConnectionPool\$overrideDoctrineTypes
‪array $overrideDoctrineTypes
Definition: ConnectionPool.php:67