comparison core/lib/Drupal/Core/Database/Database.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 129ea1e6d783
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 namespace Drupal\Core\Database;
4
5 /**
6 * Primary front-controller for the database system.
7 *
8 * This class is uninstantiatable and un-extendable. It acts to encapsulate
9 * all control and shepherding of database connections into a single location
10 * without the use of globals.
11 */
12 abstract class Database {
13
14 /**
15 * Flag to indicate a query call should simply return NULL.
16 *
17 * This is used for queries that have no reasonable return value anyway, such
18 * as INSERT statements to a table without a serial primary key.
19 */
20 const RETURN_NULL = 0;
21
22 /**
23 * Flag to indicate a query call should return the prepared statement.
24 */
25 const RETURN_STATEMENT = 1;
26
27 /**
28 * Flag to indicate a query call should return the number of affected rows.
29 */
30 const RETURN_AFFECTED = 2;
31
32 /**
33 * Flag to indicate a query call should return the "last insert id".
34 */
35 const RETURN_INSERT_ID = 3;
36
37 /**
38 * An nested array of all active connections. It is keyed by database name
39 * and target.
40 *
41 * @var array
42 */
43 static protected $connections = [];
44
45 /**
46 * A processed copy of the database connection information from settings.php.
47 *
48 * @var array
49 */
50 static protected $databaseInfo = [];
51
52 /**
53 * A list of key/target credentials to simply ignore.
54 *
55 * @var array
56 */
57 static protected $ignoreTargets = [];
58
59 /**
60 * The key of the currently active database connection.
61 *
62 * @var string
63 */
64 static protected $activeKey = 'default';
65
66 /**
67 * An array of active query log objects.
68 *
69 * Every connection has one and only one logger object for all targets and
70 * logging keys.
71 *
72 * array(
73 * '$db_key' => DatabaseLog object.
74 * );
75 *
76 * @var array
77 */
78 static protected $logs = [];
79
80 /**
81 * Starts logging a given logging key on the specified connection.
82 *
83 * @param string $logging_key
84 * The logging key to log.
85 * @param string $key
86 * The database connection key for which we want to log.
87 *
88 * @return \Drupal\Core\Database\Log
89 * The query log object. Note that the log object does support richer
90 * methods than the few exposed through the Database class, so in some
91 * cases it may be desirable to access it directly.
92 *
93 * @see \Drupal\Core\Database\Log
94 */
95 final public static function startLog($logging_key, $key = 'default') {
96 if (empty(self::$logs[$key])) {
97 self::$logs[$key] = new Log($key);
98
99 // Every target already active for this connection key needs to have the
100 // logging object associated with it.
101 if (!empty(self::$connections[$key])) {
102 foreach (self::$connections[$key] as $connection) {
103 $connection->setLogger(self::$logs[$key]);
104 }
105 }
106 }
107
108 self::$logs[$key]->start($logging_key);
109 return self::$logs[$key];
110 }
111
112 /**
113 * Retrieves the queries logged on for given logging key.
114 *
115 * This method also ends logging for the specified key. To get the query log
116 * to date without ending the logger request the logging object by starting
117 * it again (which does nothing to an open log key) and call methods on it as
118 * desired.
119 *
120 * @param string $logging_key
121 * The logging key to log.
122 * @param string $key
123 * The database connection key for which we want to log.
124 *
125 * @return array
126 * The query log for the specified logging key and connection.
127 *
128 * @see \Drupal\Core\Database\Log
129 */
130 final public static function getLog($logging_key, $key = 'default') {
131 if (empty(self::$logs[$key])) {
132 return [];
133 }
134 $queries = self::$logs[$key]->get($logging_key);
135 self::$logs[$key]->end($logging_key);
136 return $queries;
137 }
138
139 /**
140 * Gets the connection object for the specified database key and target.
141 *
142 * @param string $target
143 * The database target name.
144 * @param string $key
145 * The database connection key. Defaults to NULL which means the active key.
146 *
147 * @return \Drupal\Core\Database\Connection
148 * The corresponding connection object.
149 */
150 final public static function getConnection($target = 'default', $key = NULL) {
151 if (!isset($key)) {
152 // By default, we want the active connection, set in setActiveConnection.
153 $key = self::$activeKey;
154 }
155 // If the requested target does not exist, or if it is ignored, we fall back
156 // to the default target. The target is typically either "default" or
157 // "replica", indicating to use a replica SQL server if one is available. If
158 // it's not available, then the default/primary server is the correct server
159 // to use.
160 if (!empty(self::$ignoreTargets[$key][$target]) || !isset(self::$databaseInfo[$key][$target])) {
161 $target = 'default';
162 }
163
164 if (!isset(self::$connections[$key][$target])) {
165 // If necessary, a new connection is opened.
166 self::$connections[$key][$target] = self::openConnection($key, $target);
167 }
168 return self::$connections[$key][$target];
169 }
170
171 /**
172 * Determines if there is an active connection.
173 *
174 * Note that this method will return FALSE if no connection has been
175 * established yet, even if one could be.
176 *
177 * @return bool
178 * TRUE if there is at least one database connection established, FALSE
179 * otherwise.
180 */
181 final public static function isActiveConnection() {
182 return !empty(self::$activeKey) && !empty(self::$connections) && !empty(self::$connections[self::$activeKey]);
183 }
184
185 /**
186 * Sets the active connection to the specified key.
187 *
188 * @return string|null
189 * The previous database connection key.
190 */
191 final public static function setActiveConnection($key = 'default') {
192 if (!empty(self::$databaseInfo[$key])) {
193 $old_key = self::$activeKey;
194 self::$activeKey = $key;
195 return $old_key;
196 }
197 }
198
199 /**
200 * Process the configuration file for database information.
201 *
202 * @param array $info
203 * The database connection information, as defined in settings.php. The
204 * structure of this array depends on the database driver it is connecting
205 * to.
206 */
207 final public static function parseConnectionInfo(array $info) {
208 // If there is no "driver" property, then we assume it's an array of
209 // possible connections for this target. Pick one at random. That allows
210 // us to have, for example, multiple replica servers.
211 if (empty($info['driver'])) {
212 $info = $info[mt_rand(0, count($info) - 1)];
213 }
214 // Parse the prefix information.
215 if (!isset($info['prefix'])) {
216 // Default to an empty prefix.
217 $info['prefix'] = [
218 'default' => '',
219 ];
220 }
221 elseif (!is_array($info['prefix'])) {
222 // Transform the flat form into an array form.
223 $info['prefix'] = [
224 'default' => $info['prefix'],
225 ];
226 }
227 return $info;
228 }
229
230 /**
231 * Adds database connection information for a given key/target.
232 *
233 * This method allows to add new connections at runtime.
234 *
235 * Under normal circumstances the preferred way to specify database
236 * credentials is via settings.php. However, this method allows them to be
237 * added at arbitrary times, such as during unit tests, when connecting to
238 * admin-defined third party databases, etc.
239 *
240 * If the given key/target pair already exists, this method will be ignored.
241 *
242 * @param string $key
243 * The database key.
244 * @param string $target
245 * The database target name.
246 * @param array $info
247 * The database connection information, as defined in settings.php. The
248 * structure of this array depends on the database driver it is connecting
249 * to.
250 */
251 final public static function addConnectionInfo($key, $target, array $info) {
252 if (empty(self::$databaseInfo[$key][$target])) {
253 self::$databaseInfo[$key][$target] = self::parseConnectionInfo($info);
254 }
255 }
256
257 /**
258 * Gets information on the specified database connection.
259 *
260 * @param string $key
261 * (optional) The connection key for which to return information.
262 *
263 * @return array|null
264 */
265 final public static function getConnectionInfo($key = 'default') {
266 if (!empty(self::$databaseInfo[$key])) {
267 return self::$databaseInfo[$key];
268 }
269 }
270
271 /**
272 * Gets connection information for all available databases.
273 *
274 * @return array
275 */
276 final public static function getAllConnectionInfo() {
277 return self::$databaseInfo;
278 }
279
280 /**
281 * Sets connection information for multiple databases.
282 *
283 * @param array $databases
284 * A multi-dimensional array specifying database connection parameters, as
285 * defined in settings.php.
286 */
287 final public static function setMultipleConnectionInfo(array $databases) {
288 foreach ($databases as $key => $targets) {
289 foreach ($targets as $target => $info) {
290 self::addConnectionInfo($key, $target, $info);
291 }
292 }
293 }
294
295 /**
296 * Rename a connection and its corresponding connection information.
297 *
298 * @param string $old_key
299 * The old connection key.
300 * @param string $new_key
301 * The new connection key.
302 *
303 * @return bool
304 * TRUE in case of success, FALSE otherwise.
305 */
306 final public static function renameConnection($old_key, $new_key) {
307 if (!empty(self::$databaseInfo[$old_key]) && empty(self::$databaseInfo[$new_key])) {
308 // Migrate the database connection information.
309 self::$databaseInfo[$new_key] = self::$databaseInfo[$old_key];
310 unset(self::$databaseInfo[$old_key]);
311
312 // Migrate over the DatabaseConnection object if it exists.
313 if (isset(self::$connections[$old_key])) {
314 self::$connections[$new_key] = self::$connections[$old_key];
315 unset(self::$connections[$old_key]);
316 }
317
318 return TRUE;
319 }
320 else {
321 return FALSE;
322 }
323 }
324
325 /**
326 * Remove a connection and its corresponding connection information.
327 *
328 * @param string $key
329 * The connection key.
330 *
331 * @return bool
332 * TRUE in case of success, FALSE otherwise.
333 */
334 final public static function removeConnection($key) {
335 if (isset(self::$databaseInfo[$key])) {
336 self::closeConnection(NULL, $key);
337 unset(self::$databaseInfo[$key]);
338 return TRUE;
339 }
340 else {
341 return FALSE;
342 }
343 }
344
345 /**
346 * Opens a connection to the server specified by the given key and target.
347 *
348 * @param string $key
349 * The database connection key, as specified in settings.php. The default is
350 * "default".
351 * @param string $target
352 * The database target to open.
353 *
354 * @throws \Drupal\Core\Database\ConnectionNotDefinedException
355 * @throws \Drupal\Core\Database\DriverNotSpecifiedException
356 */
357 final protected static function openConnection($key, $target) {
358 // If the requested database does not exist then it is an unrecoverable
359 // error.
360 if (!isset(self::$databaseInfo[$key])) {
361 throw new ConnectionNotDefinedException('The specified database connection is not defined: ' . $key);
362 }
363
364 if (!$driver = self::$databaseInfo[$key][$target]['driver']) {
365 throw new DriverNotSpecifiedException('Driver not specified for this database connection: ' . $key);
366 }
367
368 if (!empty(self::$databaseInfo[$key][$target]['namespace'])) {
369 $driver_class = self::$databaseInfo[$key][$target]['namespace'] . '\\Connection';
370 }
371 else {
372 // Fallback for Drupal 7 settings.php.
373 $driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
374 }
375
376 $pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]);
377 $new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]);
378 $new_connection->setTarget($target);
379 $new_connection->setKey($key);
380
381 // If we have any active logging objects for this connection key, we need
382 // to associate them with the connection we just opened.
383 if (!empty(self::$logs[$key])) {
384 $new_connection->setLogger(self::$logs[$key]);
385 }
386
387 return $new_connection;
388 }
389
390 /**
391 * Closes a connection to the server specified by the given key and target.
392 *
393 * @param string $target
394 * The database target name. Defaults to NULL meaning that all target
395 * connections will be closed.
396 * @param string $key
397 * The database connection key. Defaults to NULL which means the active key.
398 */
399 public static function closeConnection($target = NULL, $key = NULL) {
400 // Gets the active connection by default.
401 if (!isset($key)) {
402 $key = self::$activeKey;
403 }
404 // To close a connection, it needs to be set to NULL and removed from the
405 // static variable. In all cases, closeConnection() might be called for a
406 // connection that was not opened yet, in which case the key is not defined
407 // yet and we just ensure that the connection key is undefined.
408 if (isset($target)) {
409 if (isset(self::$connections[$key][$target])) {
410 self::$connections[$key][$target]->destroy();
411 self::$connections[$key][$target] = NULL;
412 }
413 unset(self::$connections[$key][$target]);
414 }
415 else {
416 if (isset(self::$connections[$key])) {
417 foreach (self::$connections[$key] as $target => $connection) {
418 self::$connections[$key][$target]->destroy();
419 self::$connections[$key][$target] = NULL;
420 }
421 }
422 unset(self::$connections[$key]);
423 }
424 }
425
426 /**
427 * Instructs the system to temporarily ignore a given key/target.
428 *
429 * At times we need to temporarily disable replica queries. To do so, call this
430 * method with the database key and the target to disable. That database key
431 * will then always fall back to 'default' for that key, even if it's defined.
432 *
433 * @param string $key
434 * The database connection key.
435 * @param string $target
436 * The target of the specified key to ignore.
437 */
438 public static function ignoreTarget($key, $target) {
439 self::$ignoreTargets[$key][$target] = TRUE;
440 }
441
442 /**
443 * Converts a URL to a database connection info array.
444 *
445 * @param string $url
446 * The URL.
447 * @param string $root
448 * The root directory of the Drupal installation.
449 *
450 * @return array
451 * The database connection info.
452 *
453 * @throws \InvalidArgumentException
454 * Exception thrown when the provided URL does not meet the minimum
455 * requirements.
456 */
457 public static function convertDbUrlToConnectionInfo($url, $root) {
458 $info = parse_url($url);
459 if (!isset($info['scheme'], $info['host'], $info['path'])) {
460 throw new \InvalidArgumentException('Minimum requirement: driver://host/database');
461 }
462 $info += [
463 'user' => '',
464 'pass' => '',
465 'fragment' => '',
466 ];
467
468 // A SQLite database path with two leading slashes indicates a system path.
469 // Otherwise the path is relative to the Drupal root.
470 if ($info['path'][0] === '/') {
471 $info['path'] = substr($info['path'], 1);
472 }
473 if ($info['scheme'] === 'sqlite' && $info['path'][0] !== '/') {
474 $info['path'] = $root . '/' . $info['path'];
475 }
476
477 $database = [
478 'driver' => $info['scheme'],
479 'username' => $info['user'],
480 'password' => $info['pass'],
481 'host' => $info['host'],
482 'database' => $info['path'],
483 ];
484 if (isset($info['port'])) {
485 $database['port'] = $info['port'];
486 }
487 return $database;
488 }
489
490 /**
491 * Gets database connection info as a URL.
492 *
493 * @param string $key
494 * (Optional) The database connection key.
495 *
496 * @return string
497 * The connection info as a URL.
498 */
499 public static function getConnectionInfoAsUrl($key = 'default') {
500 $db_info = static::getConnectionInfo($key);
501 if ($db_info['default']['driver'] == 'sqlite') {
502 $db_url = 'sqlite://localhost/' . $db_info['default']['database'];
503 }
504 else {
505 $user = '';
506 if ($db_info['default']['username']) {
507 $user = $db_info['default']['username'];
508 if ($db_info['default']['password']) {
509 $user .= ':' . $db_info['default']['password'];
510 }
511 $user .= '@';
512 }
513
514 $db_url = $db_info['default']['driver'] . '://' . $user . $db_info['default']['host'];
515 if (isset($db_info['default']['port'])) {
516 $db_url .= ':' . $db_info['default']['port'];
517 }
518 $db_url .= '/' . $db_info['default']['database'];
519 }
520 if ($db_info['default']['prefix']['default']) {
521 $db_url .= '#' . $db_info['default']['prefix']['default'];
522 }
523 return $db_url;
524 }
525
526 }