diff --git a/docs/hooks/hook_civicrm_buildProfile.md b/docs/hooks/hook_civicrm_buildProfile.md
index 1899d8e10835961f4054da9806fb9f61ef2bc0c4..90a5dab5c4b50d5c1f6532ca8458b30b8e0cd0b0 100644
--- a/docs/hooks/hook_civicrm_buildProfile.md
+++ b/docs/hooks/hook_civicrm_buildProfile.md
@@ -6,11 +6,11 @@ This hook is called while preparing a profile form.
 
 ## Definition
 
-    buildProfile($name)
+    buildProfile($profileName)
 
 ## Parameters
 
--   $name - the (machine readable) name of the profile.
+-   $profileName - the (machine readable) name of the profile.
 
 ## Returns
 
@@ -21,9 +21,9 @@ immediately obvious how I can use this. I could do something like this:
 
 
 
-    function myext_civicrm_buildProfile($name) {
+    function myext_civicrm_buildProfile($profileName) {
 
-      if ($name === 'MyTargetedProfile) {
+      if ($profileName === 'MyTargetedProfile) {
 
         CRM_Core_Resources::singleton()->addScriptFile('org.example.myext', 'some/fancy.js', 100);
 
diff --git a/docs/hooks/hook_civicrm_contactListQuery.md b/docs/hooks/hook_civicrm_contactListQuery.md
index 50dc83159aaecc07aa9e5b10d6dad0a84e4667bf..d2e9663fe334201cffb1dd6324c1035325e8f801 100644
--- a/docs/hooks/hook_civicrm_contactListQuery.md
+++ b/docs/hooks/hook_civicrm_contactListQuery.md
@@ -18,7 +18,7 @@ the list of contacts to display.
 
 ## Definition
 
-    hook_civicrm_contactListQuery( &$query, $name, $context, $id )
+    hook_civicrm_contactListQuery( &$query, $queryText, $context, $id )
 
 ## Parameters
 
@@ -29,7 +29,7 @@ the list of contacts to display.
     -   the contact 'data' to display in the autocomplete dropdown
         (usually contact.sort_name - aliased as 'data')
     -   the contact IDs
--   $name - the name string to execute the query against (this is the
+-   $queryText - the name string to execute the query against (this is the
     value being typed in by the user)
 -   $context - the context in which this ajax call is being made (for
     example: 'customfield', 'caseview')
@@ -56,7 +56,7 @@ This example limits contacts in my contact reference field lookup
             $query = "
     SELECT c.sort_name as data, c.id
     FROM civicrm_contact c, civicrm_group_contact cg
-    WHERE c.sort_name LIKE '$name%'
+    WHERE c.sort_name LIKE '$queryText%'
     AND   cg.group_id IN ( 5 )
     AND   cg.contact_id = c.id
     AND   cg.status = 'Added'
diff --git a/docs/hooks/hook_civicrm_optionValues.md b/docs/hooks/hook_civicrm_optionValues.md
index 8078fc804110c014a0a07f01db757135755be484..fcc0f5dc6234f291cac768dd140c6c6f696319c9 100644
--- a/docs/hooks/hook_civicrm_optionValues.md
+++ b/docs/hooks/hook_civicrm_optionValues.md
@@ -12,13 +12,13 @@ hook to add/remove options from the option group.
 ## Definition
 
 ```php
-hook_civicrm_optionValues(&$options, $name)
+hook_civicrm_optionValues(&$options, $groupName)
 ```
 
 ## Parameters
 
 -   array `$options` - the current set of options
--   string `$name` - the name of the option group
+-   string `$groupName` - the name of the option group
 
 ## Returns
 
diff --git a/docs/hooks/hook_civicrm_processProfile.md b/docs/hooks/hook_civicrm_processProfile.md
index 05888e9afa48981d6ea899b250a25760e872f8f6..3b6401549423edf295b0644e48e743d33b18b845 100644
--- a/docs/hooks/hook_civicrm_processProfile.md
+++ b/docs/hooks/hook_civicrm_processProfile.md
@@ -6,11 +6,11 @@ This hook is called processing a valid profile form submission (e.g. for "civicr
 
 ## Definition
 
-    processProfile($name)
+    processProfile($profileName)
 
 ## Parameters
 
--   $name - the (machine readable) name of the profile.
+-   $profileName - the (machine readable) name of the profile.
 
 !!! Tip
     In SQL, this corresponds to the "name" column of table "civicrm_uf_group"
diff --git a/docs/hooks/hook_civicrm_searchProfile.md b/docs/hooks/hook_civicrm_searchProfile.md
index d8216db6f22014845f56c88b2178c74e525594a8..f79739384439cdd4c6ada631f4ef8a4a8da8b342 100644
--- a/docs/hooks/hook_civicrm_searchProfile.md
+++ b/docs/hooks/hook_civicrm_searchProfile.md
@@ -7,11 +7,11 @@ profile).
 
 ## Definition
 
-    searchProfile($name)
+    searchProfile($profileName)
 
 ## Parameters
 
--   $name - the (machine readable) name of the profile.
+-   $profileName - the (machine readable) name of the profile.
 
 ## Returns
 
diff --git a/docs/hooks/hook_civicrm_validateProfile.md b/docs/hooks/hook_civicrm_validateProfile.md
index b961c48a575d7b156b34a59097f78f7ec36099b4..fbba815fa5899051467fd73f91096d2142a7b60f 100644
--- a/docs/hooks/hook_civicrm_validateProfile.md
+++ b/docs/hooks/hook_civicrm_validateProfile.md
@@ -6,11 +6,11 @@ This hook is called while validating a profile form submission.
 
 ## Definition
 
-    validateProfile($name)
+    validateProfile($profileName)
 
 ## Parameters
 
--   $name - the (machine readable) name of the profile.
+-   $profileName - the (machine readable) name of the profile.
 
 ## Returns
 
diff --git a/docs/hooks/hook_civicrm_viewProfile.md b/docs/hooks/hook_civicrm_viewProfile.md
index 3e3636f7b1eb0d5ff76405fd58634e59d5687a10..40bfda32d846376436d4191bcc7afaa0a28cbbb8 100644
--- a/docs/hooks/hook_civicrm_viewProfile.md
+++ b/docs/hooks/hook_civicrm_viewProfile.md
@@ -6,11 +6,11 @@ This hook is called while preparing a read-only profile screen.
 
 ## Definition
 
-    viewProfile($name)
+    viewProfile($profileName)
 
 ## Parameters
 
--   $name - the (machine readable) name of the profile.
+-   $profileName - the (machine readable) name of the profile.
 
 ## Returns
 
diff --git a/docs/hooks/setup/symfony.md b/docs/hooks/setup/symfony.md
new file mode 100644
index 0000000000000000000000000000000000000000..1345298fbabefa64e3f764d4738c50d2a57118d1
--- /dev/null
+++ b/docs/hooks/setup/symfony.md
@@ -0,0 +1,133 @@
+## Overview
+
+The [__Symfony EventDispatcher__](http://symfony.com/components/EventDispatcher) is an
+event library used by several PHP applications and frameworks.  For example,
+Symfony SE, Drupal 8, Magento, Laravel, CiviCRM, and many others support
+`EventDispatcher`.  It provides a common mechanism for dispatching and listening
+to events.
+
+In CiviCRM v4.7.19+, you can use Symfony `EventDispatcher` with hooks.
+
+```php
+Civi::dispatcher()->addListener('hook_civicrm_EXAMPLE', $callback, $priority);
+```
+
+Using `EventDispatcher` is useful if you need more advanced features, such as:
+
+ * Setting the priority of an event-listener
+ * Registering an object-oriented event-listener
+ * Registering a dynamic, on-the-fly event-listener
+ * Registering multiple listeners for the same event
+ * Registering for internal/unpublished events
+
+For a general introduction or background on `EventDispatcher`, consult the [Symfony documentation](http://symfony.com/doc/2.7/components/event_dispatcher.html).
+
+## Example: `Civi::dispatcher()`
+
+In this case, we have a CiviCRM extension or Drupal module named `example`.
+During the system initialization, we lookup the `EventDispatcher`, call
+`addListener()`, and listen for `hook_civicrm_alterContent`.
+
+```php
+function example_civicrm_config(&$config) {
+  if (isset(Civi::$statics[__FUNCTION__])) { return; }
+  Civi::$statics[__FUNCTION__] = 1;
+
+  Civi::dispatcher()->addListener('hook_civicrm_alterContent', '_example_say_hello');
+}
+
+function _example_say_hello($event) {
+  $event->content = 'hello ' . $event->content;
+}
+```
+
+!!! tip "Using the `$event` object"
+    Hook parameters are passed as an object, `$event`.
+    For example, [`hook_civicrm_alterContent`](/hooks/hook_civicrm_alterContent/)
+    has the parameters `(&$content, $context, $tplName, &$object)`.
+    You can access the data as `$event->content`, `$event->context`, `$event->tplName`, and `$event->object`.
+
+!!! tip "Using `hook_civicrm_config`"
+    In some environments, `hook_civicrm_config` runs multiple times. The flag
+    `Civi::$statics[__FUNCTION__]` prevents duplicate listeners.
+
+## Example: `Container::findDefinition()`
+
+In this case, we have a CiviCRM extension or Drupal module named `example`.
+We lookup the defintion of the `dispatcher` service and amend it.
+
+```php
+function example_civicrm_container($container) {
+  $container->findDefinition('dispatcher')
+    ->addMethodCall('addListener', array('hook_civicrm_alterContent', '_example_say_hello'));
+}
+
+function _example_say_hello($event) {
+  $event->content = 'hello ' . $event->content;
+}
+```
+
+<!--
+  TODO: an example using a container-service and tag.  See "Registering Event Listeners
+  in the Service Container" from http://symfony.com/doc/2.7/components/event_dispatcher.html
+-->
+
+## Events
+
+CiviCRM broadcasts many different events through the `EventDispatcher`. These
+events fall into two categories:
+
+ * __External Events/Hooks__ (v4.7.19+): These have a prefix `hook_civicrm_*`. They extend
+   the class [`GenericHookEvent`](https://github.com/civicrm/civicrm-core/blob/master/Civi/Core/Event/GenericHookEvent.php)
+   (which, in turn, extends  [`Event`](http://api.symfony.com/2.7/Symfony/Component/EventDispatcher/Event.html)).
+   Hooks are simulcast across `EventDispatcher` as well as CMS-specific event systems.
+ * __Internal Events__ (v4.5.0+): These have a prefix `civi.*`. They extend
+   the class  [`Event`](http://api.symfony.com/2.7/Symfony/Component/EventDispatcher/Event.html).
+   They are *only* broadcast via `EventDispatcher` (**not** CMS-specific event systems).
+
+You can recognize these events by their naming convention. Compare:
+
+```php
+// Listen to a hook. Note the prefix, "hook_civicrm_*".
+Civi::dispatcher()->addListener('hook_civicrm_alterContent', $callback, $priority);
+
+// Listen to an internal event. Note the prefix, "civi.*".
+Civi::dispatcher()->addListener('civi.api.resolve', $callback, $priority);
+```
+
+## Methods
+
+The `EventDispatcher` has several different methods for registering a
+listener.  Our examples have focused on the simplest one, `addListener()`,
+but the Symfony documentation describes other methods (`addSubscriber()`,
+`addListenerService()`, and `addSubscriberService()`).  See also:
+
+ * [Symfony EventDispatcher](http://symfony.com/doc/2.7/components/event_dispatcher.html)
+ * [Symfony ContainerAwareEventDispatcher](http://symfony.com/doc/2.7/components/event_dispatcher/container_aware_dispatcher.html)
+
+!!! tip "Using `addListener()`"
+    When calling `addListener()`, you _can_ pass any [PHP callable](http://php.net/manual/en/language.types.callable.php).
+    However, _in practice_, the safest bet is to pass a string (function-name) or array
+    (class-name, function-name). Other formats may not work with the
+    [container-cache](http://symfony.com/doc/2.7/components/dependency_injection/compilation.html).
+
+## History
+
+ * _CiviCRM v4.5.0_: Introduced Symfony EventDispatcher for internal use (within `civicrm-core`). For example,
+   APIv3 dispatches the events `civi.api.resolve` and `civi.api.authorize` while executing an API call.
+ * _CiviCRM v4.7.0_: Introduced `hook_civicrm_container`.
+ * _CiviCRM v4.7.0_: Integrated the Symfony `Container` and `EventDispatcher`.
+ * _CiviCRM v4.7.19_: Integrated `hook_civicrm_*` with the Symfony `EventDispatcher`.
+ * _CiviCRM v4.7.19_: Added the `Civi::dispatcher()` function.
+
+## Limitations
+
+ * _Boot-critical hooks_: `hook_civicrm_config`, `hook_civicrm_container`, and `hook_civicrm_entityTypes`
+   are fired during the bootstrap process -- before the Symfony subsystems are fully online. Consequently,
+   you may not be able to listen for these hooks.
+ * _Opaque CMS listeners_: Most hooks are dispatched through `EventDispatcher` as well as the traditional
+   hook systems for Drupal modules, Joomla plugins, WordPress plugins, and/or CiviCRM extensions.
+   This is accomplished by _daisy-chaining_: first, the event is dispatched with `EventDispatcher`; then, the
+   listener `CiviEventDispatcher::delegateToUF()` passes the event down to the other systems.
+   If you inspect `EventDispatcher`, there will be one listener (`delegateToUF()`)
+   which represents _all_ CMS-based listeners.
diff --git a/mkdocs.yml b/mkdocs.yml
index e75e479c6810952bc78f56859bd796ab38276668..1574a42ddf16d2b760095b0735b89e4086b1ce40 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -86,6 +86,7 @@ pages:
 - Hooks:
   - Using hooks: hooks.md                        # page-tree = NEED_PAGE_MOVE to /hooks/usage.md
   - Setup:
+    - Hooks with Symfony: hooks/setup/symfony.md
     - Hooks with Joomla: hooks/setup/joomla.md
     - Hooks with Drupal: hooks/setup/drupal.md
     - Hooks with Wordpress: hooks/setup/wordpress.md