commit 9e3390795350a84775341995baafcb65b7e8d20c
Author: Bart Feenstra <bart@mynameisbart.com>
Date:   Tue Dec 17 11:03:46 2013 +0100

    foo

diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 3cbbd82..bd0134c 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -422,7 +422,8 @@ function install_begin_request(&$install_state) {
 
     // Register a module handler for managing enabled modules.
     $container
-      ->register('module_handler', 'Drupal\Core\Extension\ModuleHandler');
+      ->register('module_handler', 'Drupal\Core\Extension\ModuleHandler')
+      ->addArgument(new Reference('event_dispatcher'));
 
     // Register the Guzzle HTTP client for fetching translation files from a
     // remote translation server such as localization.drupal.org.
diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php
index 8a525c9..16d732d 100644
--- a/core/lib/Drupal/Core/CoreServiceProvider.php
+++ b/core/lib/Drupal/Core/CoreServiceProvider.php
@@ -91,10 +91,12 @@ protected function registerModuleHandler(ContainerBuilder $container) {
     if ($container->getParameter('kernel.environment') == 'install') {
       // During installation we use the non-cached version.
       $container->register('module_handler', 'Drupal\Core\Extension\ModuleHandler')
+        ->addArgument(new Reference('event_dispatcher'))
         ->addArgument('%container.modules%');
     }
     else {
       $container->register('module_handler', 'Drupal\Core\Extension\CachedModuleHandler')
+        ->addArgument(new Reference('event_dispatcher'))
         ->addArgument('%container.modules%')
         ->addArgument(new Reference('state'))
         ->addArgument(new Reference('cache.bootstrap'));
diff --git a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
index ee637ea..5092456 100644
--- a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
+++ b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php
@@ -34,6 +34,7 @@ public function register(ContainerBuilder $container) {
       ->register('config.storage', 'Drupal\Core\Config\FileStorage')
       ->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
     $container->register('module_handler', 'Drupal\Core\Extension\UpdateModuleHandler')
+      ->addArgument(new Reference('event_dispatcher'))
       ->addArgument('%container.modules%');
     $container
       ->register("cache_factory", 'Drupal\Core\Cache\MemoryBackendFactory');
diff --git a/core/lib/Drupal/Core/Extension/CachedModuleHandler.php b/core/lib/Drupal/Core/Extension/CachedModuleHandler.php
index 0def7f3..7f10155 100644
--- a/core/lib/Drupal/Core/Extension/CachedModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/CachedModuleHandler.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\KeyValueStore\StateInterface;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
  * Class that manages enabled modules in a Drupal installation.
@@ -39,8 +40,8 @@ class CachedModuleHandler extends ModuleHandler implements CachedModuleHandlerIn
   /**
    * Constructs a new CachedModuleHandler object.
    */
-  public function __construct(array $module_list = array(), StateInterface $state, CacheBackendInterface $bootstrap_cache) {
-    parent::__construct($module_list);
+  public function __construct(array $module_list = array(), StateInterface $state, CacheBackendInterface $bootstrap_cache, EventDispatcherInterface $event_dispatcher) {
+    parent::__construct($event_dispatcher, $module_list);
     $this->state = $state;
     $this->bootstrapCache = $bootstrap_cache;
   }
diff --git a/core/lib/Drupal/Core/Extension/HookEvent.php b/core/lib/Drupal/Core/Extension/HookEvent.php
new file mode 100644
index 0000000..3ec934e
--- /dev/null
+++ b/core/lib/Drupal/Core/Extension/HookEvent.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\Core\Extension;
+
+use Symfony\Component\EventDispatcher\GenericEvent;
+
+/**
+ * Generic event for hooks.
+ *
+ * Every hook that fires will also fire an event named hook.$hookname, using
+ * this class. Modules may listen to this event rather than the hook in order
+ * to have their code be injectable and automatically lazy-load as a class.
+ */
+class HookEvent extends GenericEvent {
+
+  /**
+   * An array of collected values from listeners.
+   *
+   * @var mixed[]
+   */
+  protected $returnValues = array();
+
+  /**
+   * Adds a value to the collected return values from all listeners.
+   *
+   * Adding a value via this method is equivalent to returning a value
+   * from a procedural hook implementation. All values added here will be merged
+   * into a single array when returned to the calling code.
+   *
+   * @param mixed $value
+   *   The value to 'return' from a listener.
+   *
+   * @see \Drupal\Core\Extension\ModuleHandler::invokeAll().
+   */
+  public function setReturnValue($value) {
+    $this->returnValues[] = $value;
+  }
+
+  /**
+   * Returns the collected return values from all listeners to this hook event.
+   *
+   * @return mixed[]
+   */
+  public function getReturnValues() {
+    return $this->returnValues;
+  }
+
+}
+
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 92cf2a4..64afc93 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Extension;
 
 use Drupal\Component\Graph\Graph;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\Yaml\Parser;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\CacheBackendInterface;
@@ -65,8 +66,17 @@ class ModuleHandler implements ModuleHandlerInterface {
   protected $alterFunctions;
 
   /**
+   * Event dispatcher to use for hook events.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
+   */
+  protected $dispatcher;
+
+  /**
    * Constructs a ModuleHandler object.
    *
+   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
+   *   The event dispatcher that should be used for firing hook events.
    * @param array $module_list
    *   An associative array whose keys are the names of installed modules and
    *   whose values are the module filenames. This is normally the
@@ -75,7 +85,8 @@ class ModuleHandler implements ModuleHandlerInterface {
    * @see \Drupal\Core\DrupalKernel
    * @see \Drupal\Core\CoreServiceProvider
    */
-  public function __construct(array $module_list = array()) {
+  public function __construct(EventDispatcherInterface $dispatcher, array $module_list = array()) {
+    $this->dispatcher = $dispatcher;
     $this->moduleList = $module_list;
   }
 
@@ -277,6 +288,8 @@ public function invoke($module, $hook, $args = array()) {
    */
   public function invokeAll($hook, $args = array()) {
     $return = array();
+
+    // Invoke procedural hook implementations.
     $implementations = $this->getImplementations($hook);
     foreach ($implementations as $module) {
       $function = $module . '_' . $hook;
@@ -291,6 +304,18 @@ public function invokeAll($hook, $args = array()) {
       }
     }
 
+    // Dispatch the event.
+    $event = new HookEvent($hook, $args);
+    $this->dispatcher->dispatch("hook.{$hook}", $event);
+    foreach ($event->getReturnValues() as $result) {
+      if (is_array($result)) {
+        $return = NestedArray::mergeDeep($return, $result);
+      }
+      else {
+        $return[] = $result;
+      }
+    }
+
     return $return;
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Extension/HookEventUnitTest.php b/core/tests/Drupal/Tests/Core/Extension/HookEventUnitTest.php
new file mode 100644
index 0000000..8ff1a6d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Extension/HookEventUnitTest.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Extension\HookEventUnitTest.
+ */
+
+namespace Drupal\Tests\Core\Extension;
+
+use Drupal\Core\Extension\HookEvent;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests \Drupal\Core\Extension\HookEvent.
+ *
+ * @group System
+ */
+class HookEventUnitTest extends UnitTestCase {
+
+  /**
+   * Contains the hook event under test.
+   *
+   * @var \Drupal\Core\Extension\HookEvent
+   */
+  protected $hookEvent;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'description' => '',
+      'group' => 'System',
+      'name' => '\Drupal\Core\Extension\HookEvent',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function setUp() {
+    $this->hookEvent = new HookEvent();
+  }
+
+  /**
+   * Tests getReturnValues().
+   */
+  public function testGetReturnValues() {
+    $this->assertSame(array(), $this->hookEvent->getReturnValues());
+  }
+
+  /**
+   * Tests setReturnValue().
+   *
+   * @depends testGetReturnValues
+   */
+  public function testSetReturnValue() {
+    $value = $this->randomName();
+
+    $this->hookEvent->setReturnValue($value);
+    $this->assertSame(array($value), $this->hookEvent->getReturnValues());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerUnitTest.php b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerUnitTest.php
index 49ab1f8..637f033 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerUnitTest.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Contains \Drupal\Core\Extension\ModuleHanderUnitTest.
+ * Contains \Drupal\Core\Extension\ModuleHandlerUnitTest.
  */
 
 namespace Drupal\Tests\Core\Extension;
@@ -13,7 +13,6 @@
 
 use Drupal\Core\Extension\ModuleHandler;
 use Drupal\Tests\UnitTestCase;
-use PHPUnit_Framework_Error_Notice;
 
 /**
  * Tests the ModuleHandler class.
@@ -22,6 +21,23 @@
  */
 class ModuleHandlerUnitTest extends UnitTestCase {
 
+  /**
+   * Contains the event dispatcher used for testing.
+   *
+   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $eventDispatcher;
+
+  /**
+   * Contains the module handler under test.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandler
+   */
+  protected $moduleHandler;
+
+  /**
+   * {@inheritdoc}
+   */
   public static function getInfo() {
     return array(
       'name' => 'ModuleHandler functionality',
@@ -30,9 +46,13 @@ public static function getInfo() {
     );
   }
 
+  /**
+   * {@inheritdoc}
+   */
   function setUp() {
-    parent::setUp();
-    $this->moduleHandler = new ModuleHandler;
+    $this->eventDispatcher = $this->getMock('\Symfony\Component\EventDispatcher\EventDispatcherInterface');
+
+    $this->moduleHandler = new ModuleHandler($this->eventDispatcher);
   }
 
   /**
@@ -43,4 +63,17 @@ public function testLoadInclude() {
     $this->assertFalse($this->moduleHandler->loadInclude('foo', 'inc'));
   }
 
+  /**
+   * Tests invokeAll().
+   */
+  public function testInvokeAll() {
+    $hook = $this->randomName();
+
+    $this->eventDispatcher->expects($this->once())
+      ->method('dispatch')
+      ->with('hook.' . $hook);
+
+    $this->moduleHandler->invokeAll($hook);
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskIntegrationTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskIntegrationTest.php
index 61ee601..7b49467 100644
--- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskIntegrationTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskIntegrationTest.php
@@ -73,7 +73,9 @@ protected function getLocalTaskManager($modules, $route_name, $route_params) {
     $property->setAccessible(TRUE);
     $property->setValue($manager, $accessManager);
 
-    $module_handler = new ModuleHandler($modules);
+    $event_dispatcher = $this->getMock('\Symfony\Component\EventDispatcher\EventDispatcherInterface');
+
+    $module_handler = new ModuleHandler($event_dispatcher, $modules);
     $pluginDiscovery = new YamlDiscovery('local_tasks', $module_handler->getModuleDirectories());
     $pluginDiscovery = new ContainerDerivativeDiscoveryDecorator($pluginDiscovery);
     $property = new \ReflectionProperty('Drupal\Core\Menu\LocalTaskManager', 'discovery');
diff --git a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
index 3e86b66..ace779d 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
@@ -80,7 +80,9 @@ public function testDefaultPluginManager() {
    * Tests the plugin manager with no cache and altering.
    */
   public function testDefaultPluginManagerWithAlter() {
-    $module_handler = $this->getMock('Drupal\Core\Extension\ModuleHandler');
+    $module_handler = $this->getMockBuilder('Drupal\Core\Extension\ModuleHandler')
+      ->disableOriginalConstructor()
+      ->getMock();
 
     // Configure the stub.
     $alter_hook_name = $this->randomName();
