Database.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. <?php
  2. namespace Drupal\Core\Database;
  3. use Composer\Autoload\ClassLoader;
  4. use Drupal\Core\Extension\ExtensionDiscovery;
  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. * Flag to indicate a query call should simply return NULL.
  15. *
  16. * This is used for queries that have no reasonable return value anyway, such
  17. * as INSERT statements to a table without a serial primary key.
  18. */
  19. const RETURN_NULL = 0;
  20. /**
  21. * Flag to indicate a query call should return the prepared statement.
  22. */
  23. const RETURN_STATEMENT = 1;
  24. /**
  25. * Flag to indicate a query call should return the number of affected rows.
  26. */
  27. const RETURN_AFFECTED = 2;
  28. /**
  29. * Flag to indicate a query call should return the "last insert id".
  30. */
  31. const RETURN_INSERT_ID = 3;
  32. /**
  33. * An nested array of all active connections. It is keyed by database name
  34. * and target.
  35. *
  36. * @var array
  37. */
  38. protected static $connections = [];
  39. /**
  40. * A processed copy of the database connection information from settings.php.
  41. *
  42. * @var array
  43. */
  44. protected static $databaseInfo = [];
  45. /**
  46. * A list of key/target credentials to simply ignore.
  47. *
  48. * @var array
  49. */
  50. protected static $ignoreTargets = [];
  51. /**
  52. * The key of the currently active database connection.
  53. *
  54. * @var string
  55. */
  56. protected static $activeKey = 'default';
  57. /**
  58. * An array of active query log objects.
  59. *
  60. * Every connection has one and only one logger object for all targets and
  61. * logging keys.
  62. *
  63. * array(
  64. * '$db_key' => DatabaseLog object.
  65. * );
  66. *
  67. * @var array
  68. */
  69. protected static $logs = [];
  70. /**
  71. * Starts logging a given logging key on the specified connection.
  72. *
  73. * @param string $logging_key
  74. * The logging key to log.
  75. * @param string $key
  76. * The database connection key for which we want to log.
  77. *
  78. * @return \Drupal\Core\Database\Log
  79. * The query log object. Note that the log object does support richer
  80. * methods than the few exposed through the Database class, so in some
  81. * cases it may be desirable to access it directly.
  82. *
  83. * @see \Drupal\Core\Database\Log
  84. */
  85. final public static function startLog($logging_key, $key = 'default') {
  86. if (empty(self::$logs[$key])) {
  87. self::$logs[$key] = new Log($key);
  88. // Every target already active for this connection key needs to have the
  89. // logging object associated with it.
  90. if (!empty(self::$connections[$key])) {
  91. foreach (self::$connections[$key] as $connection) {
  92. $connection->setLogger(self::$logs[$key]);
  93. }
  94. }
  95. }
  96. self::$logs[$key]->start($logging_key);
  97. return self::$logs[$key];
  98. }
  99. /**
  100. * Retrieves the queries logged on for given logging key.
  101. *
  102. * This method also ends logging for the specified key. To get the query log
  103. * to date without ending the logger request the logging object by starting
  104. * it again (which does nothing to an open log key) and call methods on it as
  105. * desired.
  106. *
  107. * @param string $logging_key
  108. * The logging key to log.
  109. * @param string $key
  110. * The database connection key for which we want to log.
  111. *
  112. * @return array
  113. * The query log for the specified logging key and connection.
  114. *
  115. * @see \Drupal\Core\Database\Log
  116. */
  117. final public static function getLog($logging_key, $key = 'default') {
  118. if (empty(self::$logs[$key])) {
  119. return [];
  120. }
  121. $queries = self::$logs[$key]->get($logging_key);
  122. self::$logs[$key]->end($logging_key);
  123. return $queries;
  124. }
  125. /**
  126. * Gets the connection object for the specified database key and target.
  127. *
  128. * @param string $target
  129. * The database target name.
  130. * @param string $key
  131. * The database connection key. Defaults to NULL which means the active key.
  132. *
  133. * @return \Drupal\Core\Database\Connection
  134. * The corresponding connection object.
  135. */
  136. final public static function getConnection($target = 'default', $key = NULL) {
  137. if (!isset($key)) {
  138. // By default, we want the active connection, set in setActiveConnection.
  139. $key = self::$activeKey;
  140. }
  141. // If the requested target does not exist, or if it is ignored, we fall back
  142. // to the default target. The target is typically either "default" or
  143. // "replica", indicating to use a replica SQL server if one is available. If
  144. // it's not available, then the default/primary server is the correct server
  145. // to use.
  146. if (!empty(self::$ignoreTargets[$key][$target]) || !isset(self::$databaseInfo[$key][$target])) {
  147. $target = 'default';
  148. }
  149. if (!isset(self::$connections[$key][$target])) {
  150. // If necessary, a new connection is opened.
  151. self::$connections[$key][$target] = self::openConnection($key, $target);
  152. }
  153. return self::$connections[$key][$target];
  154. }
  155. /**
  156. * Determines if there is an active connection.
  157. *
  158. * Note that this method will return FALSE if no connection has been
  159. * established yet, even if one could be.
  160. *
  161. * @return bool
  162. * TRUE if there is at least one database connection established, FALSE
  163. * otherwise.
  164. */
  165. final public static function isActiveConnection() {
  166. return !empty(self::$activeKey) && !empty(self::$connections) && !empty(self::$connections[self::$activeKey]);
  167. }
  168. /**
  169. * Sets the active connection to the specified key.
  170. *
  171. * @return string|null
  172. * The previous database connection key.
  173. */
  174. final public static function setActiveConnection($key = 'default') {
  175. if (!empty(self::$databaseInfo[$key])) {
  176. $old_key = self::$activeKey;
  177. self::$activeKey = $key;
  178. return $old_key;
  179. }
  180. }
  181. /**
  182. * Process the configuration file for database information.
  183. *
  184. * @param array $info
  185. * The database connection information, as defined in settings.php. The
  186. * structure of this array depends on the database driver it is connecting
  187. * to.
  188. */
  189. final public static function parseConnectionInfo(array $info) {
  190. // If there is no "driver" property, then we assume it's an array of
  191. // possible connections for this target. Pick one at random. That allows
  192. // us to have, for example, multiple replica servers.
  193. if (empty($info['driver'])) {
  194. $info = $info[mt_rand(0, count($info) - 1)];
  195. }
  196. // Parse the prefix information.
  197. if (!isset($info['prefix'])) {
  198. // Default to an empty prefix.
  199. $info['prefix'] = [
  200. 'default' => '',
  201. ];
  202. }
  203. elseif (!is_array($info['prefix'])) {
  204. // Transform the flat form into an array form.
  205. $info['prefix'] = [
  206. 'default' => $info['prefix'],
  207. ];
  208. }
  209. return $info;
  210. }
  211. /**
  212. * Adds database connection information for a given key/target.
  213. *
  214. * This method allows to add new connections at runtime.
  215. *
  216. * Under normal circumstances the preferred way to specify database
  217. * credentials is via settings.php. However, this method allows them to be
  218. * added at arbitrary times, such as during unit tests, when connecting to
  219. * admin-defined third party databases, etc.
  220. *
  221. * If the given key/target pair already exists, this method will be ignored.
  222. *
  223. * @param string $key
  224. * The database key.
  225. * @param string $target
  226. * The database target name.
  227. * @param array $info
  228. * The database connection information, as defined in settings.php. The
  229. * structure of this array depends on the database driver it is connecting
  230. * to.
  231. */
  232. final public static function addConnectionInfo($key, $target, array $info) {
  233. if (empty(self::$databaseInfo[$key][$target])) {
  234. self::$databaseInfo[$key][$target] = self::parseConnectionInfo($info);
  235. }
  236. }
  237. /**
  238. * Gets information on the specified database connection.
  239. *
  240. * @param string $key
  241. * (optional) The connection key for which to return information.
  242. *
  243. * @return array|null
  244. */
  245. final public static function getConnectionInfo($key = 'default') {
  246. if (!empty(self::$databaseInfo[$key])) {
  247. return self::$databaseInfo[$key];
  248. }
  249. }
  250. /**
  251. * Gets connection information for all available databases.
  252. *
  253. * @return array
  254. */
  255. final public static function getAllConnectionInfo() {
  256. return self::$databaseInfo;
  257. }
  258. /**
  259. * Sets connection information for multiple databases.
  260. *
  261. * @param array $databases
  262. * A multi-dimensional array specifying database connection parameters, as
  263. * defined in settings.php.
  264. */
  265. final public static function setMultipleConnectionInfo(array $databases) {
  266. foreach ($databases as $key => $targets) {
  267. foreach ($targets as $target => $info) {
  268. self::addConnectionInfo($key, $target, $info);
  269. }
  270. }
  271. }
  272. /**
  273. * Rename a connection and its corresponding connection information.
  274. *
  275. * @param string $old_key
  276. * The old connection key.
  277. * @param string $new_key
  278. * The new connection key.
  279. *
  280. * @return bool
  281. * TRUE in case of success, FALSE otherwise.
  282. */
  283. final public static function renameConnection($old_key, $new_key) {
  284. if (!empty(self::$databaseInfo[$old_key]) && empty(self::$databaseInfo[$new_key])) {
  285. // Migrate the database connection information.
  286. self::$databaseInfo[$new_key] = self::$databaseInfo[$old_key];
  287. unset(self::$databaseInfo[$old_key]);
  288. // Migrate over the DatabaseConnection object if it exists.
  289. if (isset(self::$connections[$old_key])) {
  290. self::$connections[$new_key] = self::$connections[$old_key];
  291. unset(self::$connections[$old_key]);
  292. }
  293. return TRUE;
  294. }
  295. else {
  296. return FALSE;
  297. }
  298. }
  299. /**
  300. * Remove a connection and its corresponding connection information.
  301. *
  302. * @param string $key
  303. * The connection key.
  304. *
  305. * @return bool
  306. * TRUE in case of success, FALSE otherwise.
  307. */
  308. final public static function removeConnection($key) {
  309. if (isset(self::$databaseInfo[$key])) {
  310. self::closeConnection(NULL, $key);
  311. unset(self::$databaseInfo[$key]);
  312. return TRUE;
  313. }
  314. else {
  315. return FALSE;
  316. }
  317. }
  318. /**
  319. * Opens a connection to the server specified by the given key and target.
  320. *
  321. * @param string $key
  322. * The database connection key, as specified in settings.php. The default is
  323. * "default".
  324. * @param string $target
  325. * The database target to open.
  326. *
  327. * @throws \Drupal\Core\Database\ConnectionNotDefinedException
  328. * @throws \Drupal\Core\Database\DriverNotSpecifiedException
  329. */
  330. final protected static function openConnection($key, $target) {
  331. // If the requested database does not exist then it is an unrecoverable
  332. // error.
  333. if (!isset(self::$databaseInfo[$key])) {
  334. throw new ConnectionNotDefinedException('The specified database connection is not defined: ' . $key);
  335. }
  336. if (!$driver = self::$databaseInfo[$key][$target]['driver']) {
  337. throw new DriverNotSpecifiedException('Driver not specified for this database connection: ' . $key);
  338. }
  339. $namespace = static::getDatabaseDriverNamespace(self::$databaseInfo[$key][$target]);
  340. $driver_class = $namespace . '\\Connection';
  341. $pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]);
  342. $new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]);
  343. $new_connection->setTarget($target);
  344. $new_connection->setKey($key);
  345. // If we have any active logging objects for this connection key, we need
  346. // to associate them with the connection we just opened.
  347. if (!empty(self::$logs[$key])) {
  348. $new_connection->setLogger(self::$logs[$key]);
  349. }
  350. return $new_connection;
  351. }
  352. /**
  353. * Closes a connection to the server specified by the given key and target.
  354. *
  355. * @param string $target
  356. * The database target name. Defaults to NULL meaning that all target
  357. * connections will be closed.
  358. * @param string $key
  359. * The database connection key. Defaults to NULL which means the active key.
  360. */
  361. public static function closeConnection($target = NULL, $key = NULL) {
  362. // Gets the active connection by default.
  363. if (!isset($key)) {
  364. $key = self::$activeKey;
  365. }
  366. // To close a connection, it needs to be set to NULL and removed from the
  367. // static variable. In all cases, closeConnection() might be called for a
  368. // connection that was not opened yet, in which case the key is not defined
  369. // yet and we just ensure that the connection key is undefined.
  370. if (isset($target)) {
  371. if (isset(self::$connections[$key][$target])) {
  372. self::$connections[$key][$target]->destroy();
  373. self::$connections[$key][$target] = NULL;
  374. }
  375. unset(self::$connections[$key][$target]);
  376. }
  377. else {
  378. if (isset(self::$connections[$key])) {
  379. foreach (self::$connections[$key] as $target => $connection) {
  380. self::$connections[$key][$target]->destroy();
  381. self::$connections[$key][$target] = NULL;
  382. }
  383. }
  384. unset(self::$connections[$key]);
  385. }
  386. }
  387. /**
  388. * Instructs the system to temporarily ignore a given key/target.
  389. *
  390. * At times we need to temporarily disable replica queries. To do so, call this
  391. * method with the database key and the target to disable. That database key
  392. * will then always fall back to 'default' for that key, even if it's defined.
  393. *
  394. * @param string $key
  395. * The database connection key.
  396. * @param string $target
  397. * The target of the specified key to ignore.
  398. */
  399. public static function ignoreTarget($key, $target) {
  400. self::$ignoreTargets[$key][$target] = TRUE;
  401. }
  402. /**
  403. * Converts a URL to a database connection info array.
  404. *
  405. * @param string $url
  406. * The URL.
  407. * @param string $root
  408. * The root directory of the Drupal installation.
  409. *
  410. * @return array
  411. * The database connection info.
  412. *
  413. * @throws \InvalidArgumentException
  414. * Exception thrown when the provided URL does not meet the minimum
  415. * requirements.
  416. * @throws \RuntimeException
  417. * Exception thrown when a module provided database driver does not exist.
  418. */
  419. public static function convertDbUrlToConnectionInfo($url, $root) {
  420. // Check that the URL is well formed, starting with 'scheme://', where
  421. // 'scheme' is a database driver name.
  422. if (preg_match('/^(.*):\/\//', $url, $matches) !== 1) {
  423. throw new \InvalidArgumentException("Missing scheme in URL '$url'");
  424. }
  425. $driver = $matches[1];
  426. // Determine if the database driver is provided by a module.
  427. $module = NULL;
  428. $connection_class = NULL;
  429. $url_components = parse_url($url);
  430. if (isset($url_components['query'])) {
  431. parse_str($url_components['query'], $query);
  432. if (isset($query['module']) && $query['module']) {
  433. $module = $query['module'];
  434. // Set up an additional autoloader. We don't use the main autoloader as
  435. // this method can be called before Drupal is installed and is never
  436. // called during regular runtime.
  437. $namespace = "Drupal\\$module\\Driver\\Database\\$driver";
  438. $psr4_base_directory = Database::findDriverAutoloadDirectory($namespace, $root, TRUE);
  439. $additional_class_loader = new ClassLoader();
  440. $additional_class_loader->addPsr4($namespace . '\\', $psr4_base_directory);
  441. $additional_class_loader->register(TRUE);
  442. $connection_class = $custom_connection_class = $namespace . '\\Connection';
  443. }
  444. }
  445. if (!$module) {
  446. // Determine the connection class to use. Discover if the URL has a valid
  447. // driver scheme. Try with Drupal 8 style custom drivers first, since
  448. // those can override/extend the core ones.
  449. $connection_class = $custom_connection_class = "Drupal\\Driver\\Database\\{$driver}\\Connection";
  450. if (!class_exists($connection_class)) {
  451. // If the URL is not relative to a custom driver, try with core ones.
  452. $connection_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
  453. }
  454. }
  455. if (!class_exists($connection_class)) {
  456. throw new \InvalidArgumentException("Can not convert '$url' to a database connection, class '$custom_connection_class' does not exist");
  457. }
  458. $options = $connection_class::createConnectionOptionsFromUrl($url, $root);
  459. // If the driver is provided by a module add the necessary information to
  460. // autoload the code.
  461. // @see \Drupal\Core\Site\Settings::initialize()
  462. if (isset($psr4_base_directory)) {
  463. $options['autoload'] = $psr4_base_directory;
  464. }
  465. return $options;
  466. }
  467. /**
  468. * Finds the directory to add to the autoloader for the driver's namespace.
  469. *
  470. * For Drupal sites that manage their codebase with Composer, the package
  471. * that provides the database driver should add the driver's namespace to
  472. * Composer's autoloader. However, to support sites that add Drupal modules
  473. * without Composer, and because the database connection must be established
  474. * before Drupal adds the module's entire namespace to the autoloader, the
  475. * database connection info array can include an "autoload" key containing
  476. * the autoload directory for the driver's namespace. For requests that
  477. * connect to the database via a connection info array, the value of the
  478. * "autoload" key is automatically added to the autoloader.
  479. *
  480. * This method can be called to find the default value of that key when the
  481. * database connection info array isn't available. This includes:
  482. * - Console commands and test runners that connect to a database specified
  483. * by a database URL rather than a connection info array.
  484. * - During installation, prior to the connection info array being written to
  485. * settings.php.
  486. *
  487. * This method returns the directory that must be added to the autoloader for
  488. * the given namespace.
  489. * - If the namespace is a sub-namespace of a Drupal module, then this method
  490. * returns the autoload directory for that namespace, allowing Drupal
  491. * modules containing database drivers to be added to a Drupal website
  492. * without Composer.
  493. * - If the namespace is a sub-namespace of Drupal\Core or Drupal\Driver,
  494. * then this method returns FALSE, because Drupal core's autoloader already
  495. * includes these namespaces, so no additional autoload directory is
  496. * required for any code within them.
  497. * - If the namespace is anything else, then this method returns FALSE,
  498. * because neither drupal_get_database_types() nor
  499. * static::convertDbUrlToConnectionInfo() support that anyway. One can
  500. * manually edit the connection info array in settings.php to reference
  501. * any arbitrary namespace, but requests using that would use the
  502. * corresponding 'autoload' key in that connection info rather than calling
  503. * this method.
  504. *
  505. * @param string $namespace
  506. * The database driver's namespace.
  507. * @param string $root
  508. * The root directory of the Drupal installation.
  509. *
  510. * @return string|false
  511. * The PSR-4 directory to add to the autoloader for the namespace if the
  512. * namespace is a sub-namespace of a Drupal module. FALSE otherwise, as
  513. * explained above.
  514. *
  515. * @throws \RuntimeException
  516. * Exception thrown when a module provided database driver does not exist.
  517. */
  518. public static function findDriverAutoloadDirectory($namespace, $root) {
  519. // As explained by this method's documentation, return FALSE if the
  520. // namespace is not a sub-namespace of a Drupal module.
  521. if (!static::isWithinModuleNamespace($namespace)) {
  522. return FALSE;
  523. }
  524. // Extract the module information from the namespace.
  525. list(, $module, $module_relative_namespace) = explode('\\', $namespace, 3);
  526. // The namespace is within a Drupal module. Find the directory where the
  527. // module is located.
  528. $extension_discovery = new ExtensionDiscovery($root, FALSE, []);
  529. $modules = $extension_discovery->scan('module');
  530. if (!isset($modules[$module])) {
  531. throw new \RuntimeException(sprintf("Cannot find the module '%s' for the database driver namespace '%s'", $module, $namespace));
  532. }
  533. $module_directory = $modules[$module]->getPath();
  534. // All code within the Drupal\MODULE namespace is expected to follow a
  535. // PSR-4 layout within the module's "src" directory.
  536. $driver_directory = $module_directory . '/src/' . str_replace('\\', '/', $module_relative_namespace) . '/';
  537. if (!is_dir($root . '/' . $driver_directory)) {
  538. throw new \RuntimeException(sprintf("Cannot find the database driver namespace '%s' in module '%s'", $namespace, $module));
  539. }
  540. return $driver_directory;
  541. }
  542. /**
  543. * Gets database connection info as a URL.
  544. *
  545. * @param string $key
  546. * (Optional) The database connection key.
  547. *
  548. * @return string
  549. * The connection info as a URL.
  550. *
  551. * @throws \RuntimeException
  552. * When the database connection is not defined.
  553. */
  554. public static function getConnectionInfoAsUrl($key = 'default') {
  555. $db_info = static::getConnectionInfo($key);
  556. if (empty($db_info) || empty($db_info['default'])) {
  557. throw new \RuntimeException("Database connection $key not defined or missing the 'default' settings");
  558. }
  559. $namespace = static::getDatabaseDriverNamespace($db_info['default']);
  560. // If the driver namespace is within a Drupal module, add the module name
  561. // to the connection options to make it easy for the connection class's
  562. // createUrlFromConnectionOptions() method to add it to the URL.
  563. if (static::isWithinModuleNamespace($namespace)) {
  564. $db_info['default']['module'] = explode('\\', $namespace)[1];
  565. }
  566. $connection_class = $namespace . '\\Connection';
  567. return $connection_class::createUrlFromConnectionOptions($db_info['default']);
  568. }
  569. /**
  570. * Gets the PHP namespace of a database driver from the connection info.
  571. *
  572. * @param array $connection_info
  573. * The database connection information, as defined in settings.php. The
  574. * structure of this array depends on the database driver it is connecting
  575. * to.
  576. *
  577. * @return string
  578. * The PHP namespace of the driver's database.
  579. */
  580. protected static function getDatabaseDriverNamespace(array $connection_info) {
  581. if (isset($connection_info['namespace'])) {
  582. return $connection_info['namespace'];
  583. }
  584. // Fallback for Drupal 7 settings.php.
  585. return 'Drupal\\Core\\Database\\Driver\\' . $connection_info['driver'];
  586. }
  587. /**
  588. * Checks whether a namespace is within the namespace of a Drupal module.
  589. *
  590. * This can be used to determine if a database driver's namespace is provided
  591. * by a Drupal module.
  592. *
  593. * @param string $namespace
  594. * The namespace (for example, of a database driver) to check.
  595. *
  596. * @return bool
  597. * TRUE if the passed in namespace is a sub-namespace of a Drupal module's
  598. * namespace.
  599. *
  600. * @todo https://www.drupal.org/project/drupal/issues/3125476 Remove if we
  601. * add this to the extension API or if
  602. * \Drupal\Core\Database\Database::getConnectionInfoAsUrl() is removed.
  603. */
  604. private static function isWithinModuleNamespace(string $namespace) {
  605. list($first, $second) = explode('\\', $namespace, 3);
  606. // The namespace for Drupal modules is Drupal\MODULE_NAME, and the module
  607. // name must be all lowercase. Second-level namespaces containing uppercase
  608. // letters (e.g., "Core", "Component", "Driver") are not modules.
  609. // @see \Drupal\Core\DrupalKernel::getModuleNamespacesPsr4()
  610. // @see https://www.drupal.org/docs/8/creating-custom-modules/naming-and-placing-your-drupal-8-module#s-name-your-module
  611. return ($first === 'Drupal' && strtolower($second) === $second);
  612. }
  613. }