ModuleHandler.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. <?php
  2. namespace Drupal\Core\Extension;
  3. use Drupal\Component\Graph\Graph;
  4. use Drupal\Component\Utility\NestedArray;
  5. use Drupal\Core\Cache\CacheBackendInterface;
  6. use Drupal\Core\Extension\Exception\UnknownExtensionException;
  7. /**
  8. * Class that manages modules in a Drupal installation.
  9. */
  10. class ModuleHandler implements ModuleHandlerInterface {
  11. /**
  12. * List of loaded files.
  13. *
  14. * @var array
  15. * An associative array whose keys are file paths of loaded files, relative
  16. * to the application's root directory.
  17. */
  18. protected $loadedFiles;
  19. /**
  20. * List of installed modules.
  21. *
  22. * @var \Drupal\Core\Extension\Extension[]
  23. */
  24. protected $moduleList;
  25. /**
  26. * Boolean indicating whether modules have been loaded.
  27. *
  28. * @var bool
  29. */
  30. protected $loaded = FALSE;
  31. /**
  32. * List of hook implementations keyed by hook name.
  33. *
  34. * @var array
  35. */
  36. protected $implementations;
  37. /**
  38. * List of hooks where the implementations have been "verified".
  39. *
  40. * @var true[]
  41. * Associative array where keys are hook names.
  42. */
  43. protected $verified;
  44. /**
  45. * Information returned by hook_hook_info() implementations.
  46. *
  47. * @var array
  48. */
  49. protected $hookInfo;
  50. /**
  51. * Cache backend for storing module hook implementation information.
  52. *
  53. * @var \Drupal\Core\Cache\CacheBackendInterface
  54. */
  55. protected $cacheBackend;
  56. /**
  57. * Whether the cache needs to be written.
  58. *
  59. * @var bool
  60. */
  61. protected $cacheNeedsWriting = FALSE;
  62. /**
  63. * List of alter hook implementations keyed by hook name(s).
  64. *
  65. * @var array
  66. */
  67. protected $alterFunctions;
  68. /**
  69. * The app root.
  70. *
  71. * @var string
  72. */
  73. protected $root;
  74. /**
  75. * A list of module include file keys.
  76. *
  77. * @var array
  78. */
  79. protected $includeFileKeys = [];
  80. /**
  81. * Constructs a ModuleHandler object.
  82. *
  83. * @param string $root
  84. * The app root.
  85. * @param array $module_list
  86. * An associative array whose keys are the names of installed modules and
  87. * whose values are Extension class parameters. This is normally the
  88. * %container.modules% parameter being set up by DrupalKernel.
  89. * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
  90. * Cache backend for storing module hook implementation information.
  91. *
  92. * @see \Drupal\Core\DrupalKernel
  93. * @see \Drupal\Core\CoreServiceProvider
  94. */
  95. public function __construct($root, array $module_list, CacheBackendInterface $cache_backend) {
  96. $this->root = $root;
  97. $this->moduleList = [];
  98. foreach ($module_list as $name => $module) {
  99. $this->moduleList[$name] = new Extension($this->root, $module['type'], $module['pathname'], $module['filename']);
  100. }
  101. $this->cacheBackend = $cache_backend;
  102. }
  103. /**
  104. * {@inheritdoc}
  105. */
  106. public function load($name) {
  107. if (isset($this->loadedFiles[$name])) {
  108. return TRUE;
  109. }
  110. if (isset($this->moduleList[$name])) {
  111. $this->moduleList[$name]->load();
  112. $this->loadedFiles[$name] = TRUE;
  113. return TRUE;
  114. }
  115. return FALSE;
  116. }
  117. /**
  118. * {@inheritdoc}
  119. */
  120. public function loadAll() {
  121. if (!$this->loaded) {
  122. foreach ($this->moduleList as $name => $module) {
  123. $this->load($name);
  124. }
  125. $this->loaded = TRUE;
  126. }
  127. }
  128. /**
  129. * {@inheritdoc}
  130. */
  131. public function reload() {
  132. $this->loaded = FALSE;
  133. $this->loadAll();
  134. }
  135. /**
  136. * {@inheritdoc}
  137. */
  138. public function isLoaded() {
  139. return $this->loaded;
  140. }
  141. /**
  142. * {@inheritdoc}
  143. */
  144. public function getModuleList() {
  145. return $this->moduleList;
  146. }
  147. /**
  148. * {@inheritdoc}
  149. */
  150. public function getModule($name) {
  151. if (isset($this->moduleList[$name])) {
  152. return $this->moduleList[$name];
  153. }
  154. throw new UnknownExtensionException(sprintf('The module %s does not exist.', $name));
  155. }
  156. /**
  157. * {@inheritdoc}
  158. */
  159. public function setModuleList(array $module_list = []) {
  160. $this->moduleList = $module_list;
  161. // Reset the implementations, so a new call triggers a reloading of the
  162. // available hooks.
  163. $this->resetImplementations();
  164. }
  165. /**
  166. * {@inheritdoc}
  167. */
  168. public function addModule($name, $path) {
  169. $this->add('module', $name, $path);
  170. }
  171. /**
  172. * {@inheritdoc}
  173. */
  174. public function addProfile($name, $path) {
  175. $this->add('profile', $name, $path);
  176. }
  177. /**
  178. * Adds a module or profile to the list of currently active modules.
  179. *
  180. * @param string $type
  181. * The extension type; either 'module' or 'profile'.
  182. * @param string $name
  183. * The module name; e.g., 'node'.
  184. * @param string $path
  185. * The module path; e.g., 'core/modules/node'.
  186. */
  187. protected function add($type, $name, $path) {
  188. $pathname = "$path/$name.info.yml";
  189. $filename = file_exists($this->root . "/$path/$name.$type") ? "$name.$type" : NULL;
  190. $this->moduleList[$name] = new Extension($this->root, $type, $pathname, $filename);
  191. $this->resetImplementations();
  192. }
  193. /**
  194. * {@inheritdoc}
  195. */
  196. public function buildModuleDependencies(array $modules) {
  197. foreach ($modules as $module) {
  198. $graph[$module->getName()]['edges'] = [];
  199. if (isset($module->info['dependencies']) && is_array($module->info['dependencies'])) {
  200. foreach ($module->info['dependencies'] as $dependency) {
  201. $dependency_data = Dependency::createFromString($dependency);
  202. $graph[$module->getName()]['edges'][$dependency_data->getName()] = $dependency_data;
  203. }
  204. }
  205. }
  206. $graph_object = new Graph($graph);
  207. $graph = $graph_object->searchAndSort();
  208. foreach ($graph as $module_name => $data) {
  209. $modules[$module_name]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : [];
  210. $modules[$module_name]->requires = isset($data['paths']) ? $data['paths'] : [];
  211. $modules[$module_name]->sort = $data['weight'];
  212. }
  213. return $modules;
  214. }
  215. /**
  216. * {@inheritdoc}
  217. */
  218. public function moduleExists($module) {
  219. return isset($this->moduleList[$module]);
  220. }
  221. /**
  222. * {@inheritdoc}
  223. */
  224. public function loadAllIncludes($type, $name = NULL) {
  225. foreach ($this->moduleList as $module => $filename) {
  226. $this->loadInclude($module, $type, $name);
  227. }
  228. }
  229. /**
  230. * {@inheritdoc}
  231. */
  232. public function loadInclude($module, $type, $name = NULL) {
  233. if ($type == 'install') {
  234. // Make sure the installation API is available
  235. include_once $this->root . '/core/includes/install.inc';
  236. }
  237. $name = $name ?: $module;
  238. $key = $type . ':' . $module . ':' . $name;
  239. if (isset($this->includeFileKeys[$key])) {
  240. return $this->includeFileKeys[$key];
  241. }
  242. if (isset($this->moduleList[$module])) {
  243. $file = $this->root . '/' . $this->moduleList[$module]->getPath() . "/$name.$type";
  244. if (is_file($file)) {
  245. require_once $file;
  246. $this->includeFileKeys[$key] = $file;
  247. return $file;
  248. }
  249. else {
  250. $this->includeFileKeys[$key] = FALSE;
  251. }
  252. }
  253. return FALSE;
  254. }
  255. /**
  256. * {@inheritdoc}
  257. */
  258. public function getHookInfo() {
  259. if (!isset($this->hookInfo)) {
  260. if ($cache = $this->cacheBackend->get('hook_info')) {
  261. $this->hookInfo = $cache->data;
  262. }
  263. else {
  264. $this->buildHookInfo();
  265. $this->cacheBackend->set('hook_info', $this->hookInfo);
  266. }
  267. }
  268. return $this->hookInfo;
  269. }
  270. /**
  271. * Builds hook_hook_info() information.
  272. *
  273. * @see \Drupal\Core\Extension\ModuleHandler::getHookInfo()
  274. */
  275. protected function buildHookInfo() {
  276. $this->hookInfo = [];
  277. // Make sure that the modules are loaded before checking.
  278. $this->reload();
  279. // $this->invokeAll() would cause an infinite recursion.
  280. foreach ($this->moduleList as $module => $filename) {
  281. $function = $module . '_hook_info';
  282. if (function_exists($function)) {
  283. $result = $function();
  284. if (isset($result) && is_array($result)) {
  285. $this->hookInfo = NestedArray::mergeDeep($this->hookInfo, $result);
  286. }
  287. }
  288. }
  289. }
  290. /**
  291. * {@inheritdoc}
  292. */
  293. public function getImplementations($hook) {
  294. $implementations = $this->getImplementationInfo($hook);
  295. return array_keys($implementations);
  296. }
  297. /**
  298. * {@inheritdoc}
  299. */
  300. public function writeCache() {
  301. if ($this->cacheNeedsWriting) {
  302. $this->cacheBackend->set('module_implements', $this->implementations);
  303. $this->cacheNeedsWriting = FALSE;
  304. }
  305. }
  306. /**
  307. * {@inheritdoc}
  308. */
  309. public function resetImplementations() {
  310. $this->implementations = NULL;
  311. $this->hookInfo = NULL;
  312. $this->alterFunctions = NULL;
  313. // We maintain a persistent cache of hook implementations in addition to the
  314. // static cache to avoid looping through every module and every hook on each
  315. // request. Benchmarks show that the benefit of this caching outweighs the
  316. // additional database hit even when using the default database caching
  317. // backend and only a small number of modules are enabled. The cost of the
  318. // $this->cacheBackend->get() is more or less constant and reduced further
  319. // when non-database caching backends are used, so there will be more
  320. // significant gains when a large number of modules are installed or hooks
  321. // invoked, since this can quickly lead to
  322. // \Drupal::moduleHandler()->implementsHook() being called several thousand
  323. // times per request.
  324. $this->cacheBackend->set('module_implements', []);
  325. $this->cacheBackend->delete('hook_info');
  326. }
  327. /**
  328. * {@inheritdoc}
  329. */
  330. public function implementsHook($module, $hook) {
  331. $function = $module . '_' . $hook;
  332. if (function_exists($function)) {
  333. return TRUE;
  334. }
  335. // If the hook implementation does not exist, check whether it lives in an
  336. // optional include file registered via hook_hook_info().
  337. $hook_info = $this->getHookInfo();
  338. if (isset($hook_info[$hook]['group'])) {
  339. $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']);
  340. if (function_exists($function)) {
  341. return TRUE;
  342. }
  343. }
  344. return FALSE;
  345. }
  346. /**
  347. * {@inheritdoc}
  348. */
  349. public function invoke($module, $hook, array $args = []) {
  350. if (!$this->implementsHook($module, $hook)) {
  351. return;
  352. }
  353. $function = $module . '_' . $hook;
  354. return call_user_func_array($function, $args);
  355. }
  356. /**
  357. * {@inheritdoc}
  358. */
  359. public function invokeAll($hook, array $args = []) {
  360. $return = [];
  361. $implementations = $this->getImplementations($hook);
  362. foreach ($implementations as $module) {
  363. $function = $module . '_' . $hook;
  364. $result = call_user_func_array($function, $args);
  365. if (isset($result) && is_array($result)) {
  366. $return = NestedArray::mergeDeep($return, $result);
  367. }
  368. elseif (isset($result)) {
  369. $return[] = $result;
  370. }
  371. }
  372. return $return;
  373. }
  374. /**
  375. * {@inheritdoc}
  376. */
  377. public function invokeDeprecated($description, $module, $hook, array $args = []) {
  378. $result = $this->invoke($module, $hook, $args);
  379. $this->triggerDeprecationError($description, $hook);
  380. return $result;
  381. }
  382. /**
  383. * {@inheritdoc}
  384. */
  385. public function invokeAllDeprecated($description, $hook, array $args = []) {
  386. $result = $this->invokeAll($hook, $args);
  387. $this->triggerDeprecationError($description, $hook);
  388. return $result;
  389. }
  390. /**
  391. * Triggers an E_USER_DEPRECATED error if any module implements the hook.
  392. *
  393. * @param string $description
  394. * Helpful text describing what to do instead of implementing this hook.
  395. * @param string $hook
  396. * The name of the hook.
  397. */
  398. private function triggerDeprecationError($description, $hook) {
  399. $modules = array_keys($this->getImplementationInfo($hook));
  400. if (!empty($modules)) {
  401. $message = 'The deprecated hook hook_' . $hook . '() is implemented in these functions: ';
  402. $implementations = array_map(function ($module) use ($hook) {
  403. return $module . '_' . $hook . '()';
  404. }, $modules);
  405. @trigger_error($message . implode(', ', $implementations) . '. ' . $description, E_USER_DEPRECATED);
  406. }
  407. }
  408. /**
  409. * {@inheritdoc}
  410. */
  411. public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
  412. // Most of the time, $type is passed as a string, so for performance,
  413. // normalize it to that. When passed as an array, usually the first item in
  414. // the array is a generic type, and additional items in the array are more
  415. // specific variants of it, as in the case of array('form', 'form_FORM_ID').
  416. if (is_array($type)) {
  417. $cid = implode(',', $type);
  418. $extra_types = $type;
  419. $type = array_shift($extra_types);
  420. // Allow if statements in this function to use the faster isset() rather
  421. // than !empty() both when $type is passed as a string, or as an array
  422. // with one item.
  423. if (empty($extra_types)) {
  424. unset($extra_types);
  425. }
  426. }
  427. else {
  428. $cid = $type;
  429. }
  430. // Some alter hooks are invoked many times per page request, so store the
  431. // list of functions to call, and on subsequent calls, iterate through them
  432. // quickly.
  433. if (!isset($this->alterFunctions[$cid])) {
  434. $this->alterFunctions[$cid] = [];
  435. $hook = $type . '_alter';
  436. $modules = $this->getImplementations($hook);
  437. if (!isset($extra_types)) {
  438. // For the more common case of a single hook, we do not need to call
  439. // function_exists(), since $this->getImplementations() returns only
  440. // modules with implementations.
  441. foreach ($modules as $module) {
  442. $this->alterFunctions[$cid][] = $module . '_' . $hook;
  443. }
  444. }
  445. else {
  446. // For multiple hooks, we need $modules to contain every module that
  447. // implements at least one of them.
  448. $extra_modules = [];
  449. foreach ($extra_types as $extra_type) {
  450. $extra_modules = array_merge($extra_modules, $this->getImplementations($extra_type . '_alter'));
  451. }
  452. // If any modules implement one of the extra hooks that do not implement
  453. // the primary hook, we need to add them to the $modules array in their
  454. // appropriate order. $this->getImplementations() can only return
  455. // ordered implementations of a single hook. To get the ordered
  456. // implementations of multiple hooks, we mimic the
  457. // $this->getImplementations() logic of first ordering by
  458. // $this->getModuleList(), and then calling
  459. // $this->alter('module_implements').
  460. if (array_diff($extra_modules, $modules)) {
  461. // Merge the arrays and order by getModuleList().
  462. $modules = array_intersect(array_keys($this->moduleList), array_merge($modules, $extra_modules));
  463. // Since $this->getImplementations() already took care of loading the
  464. // necessary include files, we can safely pass FALSE for the array
  465. // values.
  466. $implementations = array_fill_keys($modules, FALSE);
  467. // Let modules adjust the order solely based on the primary hook. This
  468. // ensures the same module order regardless of whether this if block
  469. // runs. Calling $this->alter() recursively in this way does not
  470. // result in an infinite loop, because this call is for a single
  471. // $type, so we won't end up in this code block again.
  472. $this->alter('module_implements', $implementations, $hook);
  473. $modules = array_keys($implementations);
  474. }
  475. foreach ($modules as $module) {
  476. // Since $modules is a merged array, for any given module, we do not
  477. // know whether it has any particular implementation, so we need a
  478. // function_exists().
  479. $function = $module . '_' . $hook;
  480. if (function_exists($function)) {
  481. $this->alterFunctions[$cid][] = $function;
  482. }
  483. foreach ($extra_types as $extra_type) {
  484. $function = $module . '_' . $extra_type . '_alter';
  485. if (function_exists($function)) {
  486. $this->alterFunctions[$cid][] = $function;
  487. }
  488. }
  489. }
  490. }
  491. }
  492. foreach ($this->alterFunctions[$cid] as $function) {
  493. $function($data, $context1, $context2);
  494. }
  495. }
  496. /**
  497. * {@inheritdoc}
  498. */
  499. public function alterDeprecated($description, $type, &$data, &$context1 = NULL, &$context2 = NULL) {
  500. // Invoke the alter hook. This has the side effect of populating
  501. // $this->alterFunctions.
  502. $this->alter($type, $data, $context1, $context2);
  503. // The $type parameter can be an array. alter() will deal with this
  504. // internally, but we have to extract the proper $cid in order to discover
  505. // implementations.
  506. $cid = $type;
  507. if (is_array($type)) {
  508. $cid = implode(',', $type);
  509. $extra_types = $type;
  510. $type = array_shift($extra_types);
  511. }
  512. if (!empty($this->alterFunctions[$cid])) {
  513. $message = 'The deprecated alter hook hook_' . $type . '_alter() is implemented in these functions: ' . implode(', ', $this->alterFunctions[$cid]) . '.';
  514. @trigger_error($message . ' ' . $description, E_USER_DEPRECATED);
  515. }
  516. }
  517. /**
  518. * Provides information about modules' implementations of a hook.
  519. *
  520. * @param string $hook
  521. * The name of the hook (e.g. "help" or "menu").
  522. *
  523. * @return mixed[]
  524. * An array whose keys are the names of the modules which are implementing
  525. * this hook and whose values are either a string identifying a file in
  526. * which the implementation is to be found, or FALSE, if the implementation
  527. * is in the module file.
  528. */
  529. protected function getImplementationInfo($hook) {
  530. if (!isset($this->implementations)) {
  531. $this->implementations = [];
  532. $this->verified = [];
  533. if ($cache = $this->cacheBackend->get('module_implements')) {
  534. $this->implementations = $cache->data;
  535. }
  536. }
  537. if (!isset($this->implementations[$hook])) {
  538. // The hook is not cached, so ensure that whether or not it has
  539. // implementations, the cache is updated at the end of the request.
  540. $this->cacheNeedsWriting = TRUE;
  541. // Discover implementations.
  542. $this->implementations[$hook] = $this->buildImplementationInfo($hook);
  543. // Implementations are always "verified" as part of the discovery.
  544. $this->verified[$hook] = TRUE;
  545. }
  546. elseif (!isset($this->verified[$hook])) {
  547. if (!$this->verifyImplementations($this->implementations[$hook], $hook)) {
  548. // One or more of the implementations did not exist and need to be
  549. // removed in the cache.
  550. $this->cacheNeedsWriting = TRUE;
  551. }
  552. $this->verified[$hook] = TRUE;
  553. }
  554. return $this->implementations[$hook];
  555. }
  556. /**
  557. * Builds hook implementation information for a given hook name.
  558. *
  559. * @param string $hook
  560. * The name of the hook (e.g. "help" or "menu").
  561. *
  562. * @return mixed[]
  563. * An array whose keys are the names of the modules which are implementing
  564. * this hook and whose values are either a string identifying a file in
  565. * which the implementation is to be found, or FALSE, if the implementation
  566. * is in the module file.
  567. *
  568. * @throws \RuntimeException
  569. * Exception thrown when an invalid implementation is added by
  570. * hook_module_implements_alter().
  571. *
  572. * @see \Drupal\Core\Extension\ModuleHandler::getImplementationInfo()
  573. */
  574. protected function buildImplementationInfo($hook) {
  575. $implementations = [];
  576. $hook_info = $this->getHookInfo();
  577. foreach ($this->moduleList as $module => $extension) {
  578. $include_file = isset($hook_info[$hook]['group']) && $this->loadInclude($module, 'inc', $module . '.' . $hook_info[$hook]['group']);
  579. // Since $this->implementsHook() may needlessly try to load the include
  580. // file again, function_exists() is used directly here.
  581. if (function_exists($module . '_' . $hook)) {
  582. $implementations[$module] = $include_file ? $hook_info[$hook]['group'] : FALSE;
  583. }
  584. }
  585. // Allow modules to change the weight of specific implementations, but avoid
  586. // an infinite loop.
  587. if ($hook != 'module_implements_alter') {
  588. // Remember the original implementations, before they are modified with
  589. // hook_module_implements_alter().
  590. $implementations_before = $implementations;
  591. // Verify implementations that were added or modified.
  592. $this->alter('module_implements', $implementations, $hook);
  593. // Verify new or modified implementations.
  594. foreach (array_diff_assoc($implementations, $implementations_before) as $module => $group) {
  595. // If an implementation of hook_module_implements_alter() changed or
  596. // added a group, the respective file needs to be included.
  597. if ($group) {
  598. $this->loadInclude($module, 'inc', "$module.$group");
  599. }
  600. // If a new implementation was added, verify that the function exists.
  601. if (!function_exists($module . '_' . $hook)) {
  602. throw new \RuntimeException("An invalid implementation {$module}_{$hook} was added by hook_module_implements_alter()");
  603. }
  604. }
  605. }
  606. return $implementations;
  607. }
  608. /**
  609. * Verifies an array of implementations loaded from the cache, by including
  610. * the lazy-loaded $module.$group.inc, and checking function_exists().
  611. *
  612. * @param string[] $implementations
  613. * Implementation "group" by module name.
  614. * @param string $hook
  615. * The hook name.
  616. *
  617. * @return bool
  618. * TRUE, if all implementations exist.
  619. * FALSE, if one or more implementations don't exist and need to be removed
  620. * from the cache.
  621. */
  622. protected function verifyImplementations(&$implementations, $hook) {
  623. $all_valid = TRUE;
  624. foreach ($implementations as $module => $group) {
  625. // If this hook implementation is stored in a lazy-loaded file, include
  626. // that file first.
  627. if ($group) {
  628. $this->loadInclude($module, 'inc', "$module.$group");
  629. }
  630. // It is possible that a module removed a hook implementation without
  631. // the implementations cache being rebuilt yet, so we check whether the
  632. // function exists on each request to avoid undefined function errors.
  633. // Since ModuleHandler::implementsHook() may needlessly try to
  634. // load the include file again, function_exists() is used directly here.
  635. if (!function_exists($module . '_' . $hook)) {
  636. // Clear out the stale implementation from the cache and force a cache
  637. // refresh to forget about no longer existing hook implementations.
  638. unset($implementations[$module]);
  639. // One of the implementations did not exist and needs to be removed in
  640. // the cache.
  641. $all_valid = FALSE;
  642. }
  643. }
  644. return $all_valid;
  645. }
  646. /**
  647. * Parses a dependency for comparison by drupal_check_incompatibility().
  648. *
  649. * @param string $dependency
  650. * A dependency string, which specifies a module dependency, and optionally
  651. * the project it comes from and versions that are supported. Supported
  652. * formats include:
  653. * - 'module'
  654. * - 'project:module'
  655. * - 'project:module (>=version, version)'.
  656. *
  657. * @return array
  658. * An associative array with three keys:
  659. * - 'name' includes the name of the thing to depend on (e.g. 'foo').
  660. * - 'original_version' contains the original version string (which can be
  661. * used in the UI for reporting incompatibilities).
  662. * - 'versions' is a list of associative arrays, each containing the keys
  663. * 'op' and 'version'. 'op' can be one of: '=', '==', '!=', '<>', '<',
  664. * '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'.
  665. * Callers should pass this structure to drupal_check_incompatibility().
  666. *
  667. * @deprecated in drupal:8.7.0 and is removed from drupal:9.0.0.
  668. * Use \Drupal\Core\Extension\Dependency::createFromString() instead.
  669. *
  670. * @see https://www.drupal.org/node/2756875
  671. */
  672. public static function parseDependency($dependency) {
  673. @trigger_error(__METHOD__ . ' is deprecated. Use \Drupal\Core\Extension\Dependency::createFromString() instead. See https://www.drupal.org/node/2756875', E_USER_DEPRECATED);
  674. $dependency = Dependency::createFromString($dependency);
  675. $result = [
  676. 'name' => $dependency->getName(),
  677. 'project' => $dependency->getProject(),
  678. 'original_version' => $dependency['original_version'],
  679. 'versions' => $dependency['versions'],
  680. ];
  681. return array_filter($result);
  682. }
  683. /**
  684. * {@inheritdoc}
  685. */
  686. public function getModuleDirectories() {
  687. $dirs = [];
  688. foreach ($this->getModuleList() as $name => $module) {
  689. $dirs[$name] = $this->root . '/' . $module->getPath();
  690. }
  691. return $dirs;
  692. }
  693. /**
  694. * {@inheritdoc}
  695. */
  696. public function getName($module) {
  697. try {
  698. return \Drupal::service('extension.list.module')->getName($module);
  699. }
  700. catch (UnknownExtensionException $e) {
  701. @trigger_error('Calling ModuleHandler::getName() with an unknown module is deprecated in Drupal 8.7.0 and support for this will be removed in Drupal 9.0.0, check that the module exists before calling this method. See https://www.drupal.org/node/3024541.', E_USER_DEPRECATED);
  702. return $module;
  703. }
  704. }
  705. }