Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / Database / Database.php
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     $namespace = static::getDatabaseDriverNamespace(self::$databaseInfo[$key][$target]);
369     $driver_class = $namespace . '\\Connection';
370
371     $pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]);
372     $new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]);
373     $new_connection->setTarget($target);
374     $new_connection->setKey($key);
375
376     // If we have any active logging objects for this connection key, we need
377     // to associate them with the connection we just opened.
378     if (!empty(self::$logs[$key])) {
379       $new_connection->setLogger(self::$logs[$key]);
380     }
381
382     return $new_connection;
383   }
384
385   /**
386    * Closes a connection to the server specified by the given key and target.
387    *
388    * @param string $target
389    *   The database target name.  Defaults to NULL meaning that all target
390    *   connections will be closed.
391    * @param string $key
392    *   The database connection key. Defaults to NULL which means the active key.
393    */
394   public static function closeConnection($target = NULL, $key = NULL) {
395     // Gets the active connection by default.
396     if (!isset($key)) {
397       $key = self::$activeKey;
398     }
399     // To close a connection, it needs to be set to NULL and removed from the
400     // static variable. In all cases, closeConnection() might be called for a
401     // connection that was not opened yet, in which case the key is not defined
402     // yet and we just ensure that the connection key is undefined.
403     if (isset($target)) {
404       if (isset(self::$connections[$key][$target])) {
405         self::$connections[$key][$target]->destroy();
406         self::$connections[$key][$target] = NULL;
407       }
408       unset(self::$connections[$key][$target]);
409     }
410     else {
411       if (isset(self::$connections[$key])) {
412         foreach (self::$connections[$key] as $target => $connection) {
413           self::$connections[$key][$target]->destroy();
414           self::$connections[$key][$target] = NULL;
415         }
416       }
417       unset(self::$connections[$key]);
418     }
419   }
420
421   /**
422    * Instructs the system to temporarily ignore a given key/target.
423    *
424    * At times we need to temporarily disable replica queries. To do so, call this
425    * method with the database key and the target to disable. That database key
426    * will then always fall back to 'default' for that key, even if it's defined.
427    *
428    * @param string $key
429    *   The database connection key.
430    * @param string $target
431    *   The target of the specified key to ignore.
432    */
433   public static function ignoreTarget($key, $target) {
434     self::$ignoreTargets[$key][$target] = TRUE;
435   }
436
437   /**
438    * Converts a URL to a database connection info array.
439    *
440    * @param string $url
441    *   The URL.
442    * @param string $root
443    *   The root directory of the Drupal installation.
444    *
445    * @return array
446    *   The database connection info.
447    *
448    * @throws \InvalidArgumentException
449    *   Exception thrown when the provided URL does not meet the minimum
450    *   requirements.
451    */
452   public static function convertDbUrlToConnectionInfo($url, $root) {
453     // Check that the URL is well formed, starting with 'scheme://', where
454     // 'scheme' is a database driver name.
455     if (preg_match('/^(.*):\/\//', $url, $matches) !== 1) {
456       throw new \InvalidArgumentException("Missing scheme in URL '$url'");
457     }
458     $driver = $matches[1];
459
460     // Discover if the URL has a valid driver scheme. Try with core drivers
461     // first.
462     $connection_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
463     if (!class_exists($connection_class)) {
464       // If the URL is not relative to a core driver, try with custom ones.
465       $connection_class = "Drupal\\Driver\\Database\\{$driver}\\Connection";
466       if (!class_exists($connection_class)) {
467         throw new \InvalidArgumentException("Can not convert '$url' to a database connection, class '$connection_class' does not exist");
468       }
469     }
470
471     return $connection_class::createConnectionOptionsFromUrl($url, $root);
472   }
473
474   /**
475    * Gets database connection info as a URL.
476    *
477    * @param string $key
478    *   (Optional) The database connection key.
479    *
480    * @return string
481    *   The connection info as a URL.
482    *
483    * @throws \RuntimeException
484    *   When the database connection is not defined.
485    */
486   public static function getConnectionInfoAsUrl($key = 'default') {
487     $db_info = static::getConnectionInfo($key);
488     if (empty($db_info) || empty($db_info['default'])) {
489       throw new \RuntimeException("Database connection $key not defined or missing the 'default' settings");
490     }
491     $connection_class = static::getDatabaseDriverNamespace($db_info['default']) . '\\Connection';
492     return $connection_class::createUrlFromConnectionOptions($db_info['default']);
493   }
494
495   /**
496    * Gets the PHP namespace of a database driver from the connection info.
497    *
498    * @param array $connection_info
499    *   The database connection information, as defined in settings.php. The
500    *   structure of this array depends on the database driver it is connecting
501    *   to.
502    *
503    * @return string
504    *   The PHP namespace of the driver's database.
505    */
506   protected static function getDatabaseDriverNamespace(array $connection_info) {
507     if (isset($connection_info['namespace'])) {
508       return $connection_info['namespace'];
509     }
510     // Fallback for Drupal 7 settings.php.
511     return 'Drupal\\Core\\Database\\Driver\\' . $connection_info['driver'];
512   }
513
514 }