[BUGFIX] Move doctrine initalization to connect() method
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Database / ConnectionPool.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Core\Database;
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
18 use Doctrine\DBAL\DriverManager;
19 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
20
21 /**
22 * Manager that handles opening/retrieving database connections.
23 *
24 * It's a facade to the actual Doctrine DBAL DriverManager that implements TYPO3
25 * specific functionality like mapping individual tables to different database
26 * connections.
27 *
28 * getConnectionForTable() is the only supported way to get a connection that
29 * honors the table mapping configuration.
30 */
31 class ConnectionPool
32 {
33 /**
34 * @var string
35 */
36 const DEFAULT_CONNECTION_NAME = 'Default';
37
38 /**
39 * @var Connection[]
40 */
41 protected static $connections = [];
42
43 /**
44 * Creates a connection object based on the specified table name.
45 *
46 * This is the official entry point to get a database connection to ensure
47 * that the mapping of table names to database connections is honored.
48 *
49 * @param string $tableName
50 * @return Connection
51 */
52 public function getConnectionForTable(string $tableName): Connection
53 {
54 if (empty($tableName)) {
55 throw new \UnexpectedValueException(
56 'ConnectionPool->getConnectionForTable() requires a table name to be provided.',
57 1459421719
58 );
59 }
60
61 $connectionName = self::DEFAULT_CONNECTION_NAME;
62 if (!empty($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName])) {
63 $connectionName = (string)$GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'][$tableName];
64 }
65
66 return $this->getConnectionByName($connectionName);
67 }
68
69 /**
70 * Creates a connection object based on the specified identifier.
71 *
72 * This method should only be used in edge cases. Use getConnectionForTable() so
73 * that the tablename<>databaseConnection mapping will be taken into account.
74 *
75 * @param string $connectionName
76 * @return Connection
77 * @throws \Doctrine\DBAL\DBALException
78 * @internal
79 */
80 public function getConnectionByName(string $connectionName): Connection
81 {
82 if (empty($connectionName)) {
83 throw new \UnexpectedValueException(
84 'ConnectionPool->getConnectionByName() requires a connection name to be provided.',
85 1459422125
86 );
87 }
88
89 if (isset(static::$connections[$connectionName])) {
90 return static::$connections[$connectionName];
91 }
92
93 if (empty($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][$connectionName])
94 || !is_array($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][$connectionName])
95 ) {
96 throw new \RuntimeException(
97 'The requested database connection named "' . $connectionName . '" has not been configured.',
98 1459422492
99 );
100 }
101
102 $connectionParams = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections'][$connectionName];
103 if (empty($connectionParams['wrapperClass'])) {
104 $connectionParams['wrapperClass'] = Connection::class;
105 }
106
107 if (!is_a($connectionParams['wrapperClass'], Connection::class, true)) {
108 throw new \UnexpectedValueException(
109 'The "wrapperClass" for the connection name "' . $connectionName .
110 '" needs to be a subclass of "' . Connection::class . '".',
111 1459422968
112 );
113 }
114
115 static::$connections[$connectionName] = $this->getDatabaseConnection($connectionParams);
116
117 return static::$connections[$connectionName];
118 }
119
120 /**
121 * Creates a connection object based on the specified parameters
122 *
123 * @param array $connectionParams
124 * @return Connection
125 */
126 protected function getDatabaseConnection(array $connectionParams): Connection
127 {
128 // Default to UTF-8 connection charset
129 if (empty($connectionParams['charset'])) {
130 $connectionParams['charset'] = 'utf-8';
131 }
132
133 // Force consistent handling of binary objects across datbase platforms
134 // MySQL returns strings by default, PostgreSQL streams.
135 if (strpos($connectionParams['driver'], 'pdo_') === 0) {
136 $connectionParams['driverOptions'][\PDO::ATTR_STRINGIFY_FETCHES] = true;
137 }
138
139 /** @var Connection $conn */
140 $conn = DriverManager::getConnection($connectionParams);
141 $conn->setFetchMode(\PDO::FETCH_ASSOC);
142 $conn->prepareConnection($connectionParams['initCommands'] ?? '');
143
144 return $conn;
145 }
146
147 /**
148 * Returns the connection specific query builder object that can be used to build
149 * complex SQL queries using and object oriented approach.
150 *
151 * @param string $tableName
152 * @return QueryBuilder
153 */
154 public function getQueryBuilderForTable(string $tableName): QueryBuilder
155 {
156 if (empty($tableName)) {
157 throw new \UnexpectedValueException(
158 'ConnectionPool->getQueryBuilderForTable() requires a connection name to be provided.',
159 1459423448
160 );
161 }
162
163 return $this->getConnectionForTable($tableName)->createQueryBuilder();
164 }
165
166 /**
167 * Returns an array containing the names of all currently configured connections.
168 *
169 * This method should only be used in edge cases. Use getConnectionForTable() so
170 * that the tablename<>databaseConnection mapping will be taken into account.
171 *
172 * @internal
173 * @return array
174 */
175 public function getConnectionNames(): array
176 {
177 return array_keys($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']);
178 }
179 }