annotate core/lib/Drupal/Core/Extension/ModuleHandler.php @ 19:fa3358dc1485 tip

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