diff --git a/STANDARDS.md b/STANDARDS.md
index 6d7f184..21e37e2 100644
--- a/STANDARDS.md
+++ b/STANDARDS.md
@@ -3,9 +3,6 @@ HOWTO: Drupal Examples For Developers Coding Standards
 
 Examples uses mostly the same coding standards as Drupal core.
 
-If you see a discrepancy between the coding standards tools used by core and
-those used by Examples, please file an issue so that Examples can follow core.
-
 Examples uses the phpcs tool to allow for checking PHP coding standards. We also
 use eslint for JavaScript coding standards.
 
@@ -23,22 +20,19 @@ Contributors can also patch the `phpcs.xml.dist` file itself, in order to fix
 the codebase to pass a given rule or sniff. Patches which do this should be
 limited to a single rule or sniff, in order make the patch easier to review.
 
-Examples also uses the Coder project (`drupal/coder`), which adds additional
-Drupal-specific coding standards. We're currently locked to Coder version
-8.2.8, but this should change to reflect the state of core's coding standards.
-
 Installing phpcs
 ----------------
 
-Use Composer to add Drupal's Coder project to your root Drupal project. Coder
-contains the PHP_CodeSniffer rules we need, and also requires `phpcs`.
+Generally: Use Composer to add Drupal's Coder project to your root
+Drupal project. Coder contains the PHP_CodeSniffer rules we need, and
+also requires `phpcs`.
 
 Then tell `phpcs' where our Drupal-specific rules are.
 
 Like this:
 
     $ cd my/drupal/root/
-    $ composer require drupal/coder 8.2.8
+    $ composer require drupal/coder
     // Composer installs Coder, which requires PHP_CodeSniffer as well.
     // Configure phpcs to use the Drupal standard rules...
     $ ./vendor/bin/phpcs --config-set installed_paths /path/to/drupal/vendor/drupal/coder/coder_sniffer/
diff --git a/block_example/block_example.info.yml b/block_example/block_example.info.yml
index f0af0e9..a3e4736 100644
--- a/block_example/block_example.info.yml
+++ b/block_example/block_example.info.yml
@@ -1,6 +1,6 @@
 name: Block Example
 type: module
-description: Demonstrates how a module can define blocks.
+description: An example outlining how a module can define blocks.
 package: Example modules
 core: 8.x
 dependencies:
diff --git a/block_example/src/Tests/BlockExampleMenuTest.php b/block_example/src/Tests/BlockExampleMenuTest.php
new file mode 100644
index 0000000..ec732df
--- /dev/null
+++ b/block_example/src/Tests/BlockExampleMenuTest.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\block_example\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Test the user-facing menus in Block Example.
+ *
+ * @ingroup block_example
+ *
+ * @group block_example
+ * @group examples
+ */
+class BlockExampleMenuTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('block', 'block_example');
+
+  /**
+   * The installation profile to use with this test.
+   *
+   * This test class requires the "Tools" block.
+   *
+   * @var string
+   */
+  protected $profile = 'minimal';
+
+  /**
+   * Test for a link to the block example in the Tools menu.
+   */
+  public function testBlockExampleLink() {
+    $this->drupalGet('');
+    $this->assertLinkByHref('examples/block-example');
+  }
+
+  /**
+   * Tests block_example menus.
+   */
+  public function testBlockExampleMenu() {
+    $this->drupalGet('examples/block-example');
+    $this->assertResponse(200, 'Description page exists.');
+  }
+
+}
diff --git a/block_example/src/Tests/BlockExampleTest.php b/block_example/src/Tests/BlockExampleTest.php
new file mode 100644
index 0000000..856a404
--- /dev/null
+++ b/block_example/src/Tests/BlockExampleTest.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\block_example\Tests;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Test the configuration options and block created by Block Example module.
+ *
+ * @ingroup block_example
+ *
+ * @group block_example
+ * @group examples
+ */
+class BlockExampleTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('block', 'block_example');
+
+  /**
+   * Tests block_example functionality.
+   */
+  public function testBlockExampleBasic() {
+    // Create user.
+    $web_user = $this->drupalCreateUser(array('administer blocks'));
+    // Login the admin user.
+    $this->drupalLogin($web_user);
+
+    $theme_name = \Drupal::config('system.theme')->get('default');
+
+    // Verify the blocks are listed to be added.
+    $this->drupalGet('/admin/structure/block/library/' . $theme_name, ['query' => ['region' => 'content']]);
+    $this->assertRaw(t('Title of first block (example_configurable_text)'), 'Block configurable-string found.');
+    $this->assertRaw(t('Example: empty block'), 'Block empty-block found.');
+    $this->assertRaw(t('Example: uppercase this please'), 'Block uppercase found.');
+
+    // Define and place blocks.
+    $settings_configurable = array(
+      'label' => t('Title of first block (example_configurable_text)'),
+      'id' => 'block_example_example_configurable_text',
+      'theme' => $theme_name,
+    );
+    $this->drupalPlaceBlock('example_configurable_text', $settings_configurable);
+
+    $settings_uppercase = array(
+      'label' => t('Configurable block to be uppercased'),
+      'id' => 'block_example_example_uppercased',
+      'theme' => $theme_name,
+    );
+    $this->drupalPlaceBlock('example_uppercase', $settings_uppercase);
+
+    $settings_empty = array(
+      'label' => t('Example: empty block'),
+      'id' => 'block_example_example_empty',
+      'theme' => $theme_name,
+    );
+    $this->drupalPlaceBlock('example_empty', $settings_empty);
+
+    // Verify that blocks are there. Empty block will not be shown, because it
+    // holds an empty array.
+    $this->drupalGet('');
+    $this->assertRaw($settings_configurable['label'], 'Block configurable test not found.');
+    $this->assertNoRaw($settings_uppercase['label'], 'Block uppercase with normal label not found.');
+    $this->assertRaw(Unicode::strtoupper($settings_uppercase['label']), 'Block uppercase with uppercased label found.');
+    $this->assertNoRaw($settings_empty['label'], 'Block empty not found.');
+
+    // Change content of configurable text block.
+    $edit = array(
+      'settings[block_example_string_text]' => $this->randomMachineName(),
+    );
+    $this->drupalPostForm('/admin/structure/block/manage/' . $settings_configurable['id'], $edit, t('Save block'));
+
+    // Verify that new content is shown.
+    $this->drupalGet('');
+    $this->assertRaw($edit['settings[block_example_string_text]'], 'Content of configurable text block successfully verified.');
+  }
+
+}
diff --git a/block_example/tests/src/Functional/BlockExampleMenuTest.php b/block_example/tests/src/Functional/BlockExampleMenuTest.php
deleted file mode 100644
index 93521f6..0000000
--- a/block_example/tests/src/Functional/BlockExampleMenuTest.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-namespace Drupal\Tests\block_example\Functional;
-
-use Drupal\Tests\BrowserTestBase;
-
-
-/**
- * Test the user-facing menus in Block Example.
- *
- * @ingroup block_example
- *
- * @group block_example
- * @group examples
- */
-class BlockExampleMenuTest extends BrowserTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('block', 'block_example');
-
-  /**
-   * The installation profile to use with this test.
-   *
-   * This test class requires the "Tools" block.
-   *
-   * @var string
-   */
-  protected $profile = 'minimal';
-
-  /**
-   * Test for a link to the block example in the Tools menu.
-   */
-  public function testBlockExampleLink() {
-    $this->drupalGet('');
-    $this->assertSession()->linkByHrefExists('examples/block-example');
-  }
-
-  /**
-   * Tests block_example menus.
-   */
-  public function testBlockExampleMenu() {
-    $this->drupalGet('examples/block-example');
-    $this->assertSession()->statusCodeEquals(200);
-  }
-
-}
diff --git a/block_example/tests/src/Functional/BlockExampleTest.php b/block_example/tests/src/Functional/BlockExampleTest.php
deleted file mode 100644
index cd7287e..0000000
--- a/block_example/tests/src/Functional/BlockExampleTest.php
+++ /dev/null
@@ -1,88 +0,0 @@
-<?php
-
-namespace Drupal\Tests\block_example\Functional;
-
-use Drupal\Component\Utility\Unicode;
-use Drupal\Tests\BrowserTestBase;
-
-/**
- * Test the configuration options and block created by Block Example module.
- *
- * @ingroup block_example
- *
- * @group block_example
- * @group examples
- */
-class BlockExampleTest extends BrowserTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('block', 'block_example');
-
-  /**
-   * Tests block_example functionality.
-   */
-  public function testBlockExampleBasic() {
-    $assert = $this->assertSession();
-
-    // Create user.
-    $web_user = $this->drupalCreateUser(array('administer blocks'));
-    // Login the admin user.
-    $this->drupalLogin($web_user);
-
-    $theme_name = \Drupal::config('system.theme')->get('default');
-
-    // Verify the blocks are listed to be added.
-    $this->drupalGet('/admin/structure/block/library/' . $theme_name, ['query' => ['region' => 'content']]);
-    $assert->pageTextContains('Title of first block (example_configurable_text');
-    $assert->pageTextContains('Example: empty block');
-    $assert->pageTextContains('Example: uppercase this please');
-
-    // Define and place blocks.
-    $settings_configurable = array(
-      'label' => t('Title of first block (example_configurable_text)'),
-      'id' => 'block_example_example_configurable_text',
-      'theme' => $theme_name,
-    );
-    $this->drupalPlaceBlock('example_configurable_text', $settings_configurable);
-
-    $settings_uppercase = array(
-      'label' => t('Configurable block to be uppercased'),
-      'id' => 'block_example_example_uppercased',
-      'theme' => $theme_name,
-    );
-    $this->drupalPlaceBlock('example_uppercase', $settings_uppercase);
-
-    $settings_empty = array(
-      'label' => t('Example: empty block'),
-      'id' => 'block_example_example_empty',
-      'theme' => $theme_name,
-    );
-    $this->drupalPlaceBlock('example_empty', $settings_empty);
-
-    // Verify that blocks are there. Empty block will not be shown, because it
-    // holds an empty array.
-    $this->drupalGet('');
-    $assert->pageTextContains($settings_configurable['label']);
-    $assert->pageTextContains($settings_uppercase['label']);
-    $assert->pageTextContains(Unicode::strtoupper($settings_uppercase['label']));
-    $assert->pageTextNotContains($settings_empty['label']);
-
-    // Change content of configurable text block.
-    $edit = array(
-      'settings[block_example_string_text]' => $this->randomMachineName(),
-    );
-    $this->drupalPostForm('/admin/structure/block/manage/' . $settings_configurable['id'], $edit, t('Save block'));
-    $assert->statusCodeEquals(200);
-
-    // Verify that new content is shown.
-    $this->drupalGet('');
-    $assert->statusCodeEquals(200);
-
-    $assert->pageTextContains($edit['settings[block_example_string_text]']);
-  }
-
-}
diff --git a/cache_example/cache_example.info.yml b/cache_example/cache_example.info.yml
index 8b9b9e2..d937084 100644
--- a/cache_example/cache_example.info.yml
+++ b/cache_example/cache_example.info.yml
@@ -1,5 +1,5 @@
 name: Cache example
-description: Demonstrates how to use Cache API.
+description: 'An example outlining how to use Cache API.'
 package: Example modules
 core: 8.x
 type: module
diff --git a/cache_example/cache_example.routing.yml b/cache_example/cache_example.routing.yml
index 3817573..d5aa5db 100644
--- a/cache_example/cache_example.routing.yml
+++ b/cache_example/cache_example.routing.yml
@@ -1,6 +1,6 @@
 cache_example.description:
   path: 'examples/cache-example'
   defaults:
-    _form: '\Drupal\cache_example\Form\CacheExampleForm'
+    _form: '\Drupal\cache_example\Forms\CacheExampleForm'
   requirements:
     _permission: 'access content'
diff --git a/cache_example/src/Form/CacheExampleForm.php b/cache_example/src/Form/CacheExampleForm.php
deleted file mode 100644
index f7d1ad8..0000000
--- a/cache_example/src/Form/CacheExampleForm.php
+++ /dev/null
@@ -1,306 +0,0 @@
-<?php
-
-namespace Drupal\cache_example\Form;
-
-use Drupal\Core\Form\FormBase;
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Cache\Cache;
-use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Session\AccountProxyInterface;
-use Drupal\Core\StringTranslation\TranslationInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\RequestStack;
-
-
-/**
- * Form with examples on how to use cache.
- */
-class CacheExampleForm extends FormBase {
-
-  /**
-   * The current user.
-   *
-   * @var \Drupal\Core\Session\AccountProxyInterface
-   */
-  protected $currentUser;
-
-  /**
-   * The cache.default cache backend.
-   *
-   * @var \Drupal\Core\Cache\CacheBackendInterface
-   */
-  protected $cacheBackend;
-
-  /**
-   * Dependency injection through the constructor.
-   *
-   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
-   *   The request stack service.
-   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
-   *   The string translation service.
-   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
-   *   The current active user service.
-   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
-   *   The cache object associated with the default bin.
-   */
-  public function __construct(
-    RequestStack $request_stack,
-    TranslationInterface $translation,
-    AccountProxyInterface $current_user,
-    CacheBackendInterface $cache_backend
-  ) {
-    $this->setRequestStack($request_stack);
-    $this->setStringTranslation($translation);
-    $this->currentUser = $current_user;
-    $this->cacheBackend = $cache_backend;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    // Forms that require a Drupal service or a custom service should access
-    // the service using dependency injection.
-    // @link https://www.drupal.org/node/2203931.
-    // Those services are passed in the $container through the static create
-    // method.
-    return new static(
-      $container->get('request_stack'),
-      $container->get('string_translation'),
-      $container->get('current_user'),
-      $container->get('cache.default')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormId() {
-    return 'cron_cache';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, FormStateInterface $form_state) {
-    // Log execution time.
-    $start_time = microtime(TRUE);
-
-    // Try to load the files count from cache. This function will accept two
-    // arguments:
-    // - cache object name (cid)
-    // - cache bin, the (optional) cache bin (most often a database table) where
-    //   the object is to be saved.
-    //
-    // cache_get() returns the cached object or FALSE if object does not exist.
-    if ($cache = $this->cacheBackend->get('cache_example_files_count')) {
-      /*
-       * Get cached data. Complex data types will be unserialized automatically.
-       */
-      $files_count = $cache->data;
-    }
-    else {
-      // If there was no cached data available we have to search filesystem.
-      // Recursively get all .PHP files from Drupal's core folder.
-      $files_count = count(file_scan_directory('core', '/.php/'));
-
-      // Since we have recalculated, we now need to store the new data into
-      // cache. Complex data types will be automatically serialized before
-      // being saved into cache.
-      // Here we use the default setting and create an unexpiring cache item.
-      // See below for an example that creates an expiring cache item.
-      $this->cacheBackend->set('cache_example_files_count', $files_count, CacheBackendInterface::CACHE_PERMANENT);
-    }
-
-    $end_time = microtime(TRUE);
-    $duration = $end_time - $start_time;
-
-    // Format intro message.
-    $intro_message = '<p>' . $this->t("This example will search Drupal's core folder and display a count of the PHP files in it.") . ' ';
-    $intro_message .= $this->t('This can take a while, since there are a lot of files to be searched.') . ' ';
-    $intro_message .= $this->t('We will search filesystem just once and save output to the cache. We will use cached data for later requests.') . '</p>';
-    $intro_message .= '<p>'
-      . $this->t(
-        '<a href="@url">Reload this page</a> to see cache in action.',
-        array('@url' => $this->getRequest()->getRequestUri())
-      )
-      . ' ';
-    $intro_message .= $this->t('You can use the button below to remove cached data.') . '</p>';
-
-    $form['file_search'] = array(
-      '#type' => 'fieldset',
-      '#title' => $this->t('File search caching'),
-    );
-    $form['file_search']['introduction'] = array(
-      '#markup' => $intro_message,
-    );
-
-    $color = empty($cache) ? 'red' : 'green';
-    $retrieval = empty($cache) ? $this->t('calculated by traversing the filesystem') : $this->t('retrieved from cache');
-
-    $form['file_search']['statistics'] = array(
-      '#type' => 'item',
-      '#markup' => $this->t('%count files exist in this Drupal installation; @retrieval in @time ms. <br/>(Source: <span style="color:@color;">@source</span>)', array(
-        '%count' => $files_count,
-        '@retrieval' => $retrieval,
-        '@time' => number_format($duration * 1000, 2),
-        '@color' => $color,
-        '@source' => empty($cache) ? $this->t('actual file search') : $this->t('cached'),
-      )
-      ),
-    );
-    $form['file_search']['remove_file_count'] = array(
-      '#type' => 'submit',
-      '#submit' => array(array($this, 'expireFiles')),
-      '#value' => $this->t('Explicitly remove cached file count'),
-    );
-
-    $form['expiration_demo'] = array(
-      '#type' => 'fieldset',
-      '#title' => $this->t('Cache expiration settings'),
-    );
-    $form['expiration_demo']['explanation'] = array(
-      '#markup' => $this->t('A cache item can be set as CACHE_PERMANENT, meaning that it will only be removed when explicitly cleared, or it can have an expiration time (a Unix timestamp).'),
-    );
-
-    $item = $this->cacheBackend->get('cache_example_expiring_item', TRUE);
-    if ($item == FALSE) {
-      $item_status = $this->t('Cache item does not exist');
-    }
-    else {
-      $item_status = $item->valid ? $this->t('Cache item exists and is set to expire at %time', array('%time' => $item->data)) :
-      $this->t('Cache_item is invalid');
-    }
-
-    $form['expiration_demo']['current_status'] = array(
-      '#type' => 'item',
-      '#title' => $this->t('Current status of cache item "cache_example_expiring_item"'),
-      '#markup' => $item_status,
-    );
-    $form['expiration_demo']['expiration'] = array(
-      '#type' => 'select',
-      '#title' => $this->t('Time before cache expiration'),
-      '#options' => array(
-        'never_remove' => $this->t('CACHE_PERMANENT'),
-        -10 => $this->t('Immediate expiration'),
-        10 => $this->t('10 seconds from form submission'),
-        60 => $this->t('1 minute from form submission'),
-        300 => $this->t('5 minutes from form submission'),
-      ),
-      '#default_value' => -10,
-      '#description' => $this->t('Any cache item can be set to only expire when explicitly cleared, or to expire at a given time.'),
-    );
-    $form['expiration_demo']['create_cache_item'] = array(
-      '#type' => 'submit',
-      '#value' => $this->t('Create a cache item with this expiration'),
-      '#submit' => array(array($this, 'createExpiringItem')),
-    );
-
-    $form['cache_clearing'] = array(
-      '#type' => 'fieldset',
-      '#title' => $this->t('Expire and remove options'),
-      '#description' => $this->t("We have APIs to expire cached items and also to just remove them. Unfortunately, they're all the same API, cache_clear_all"),
-    );
-    $form['cache_clearing']['cache_clear_type'] = array(
-      '#type' => 'radios',
-      '#title' => $this->t('Type of cache clearing to do'),
-      '#options' => array(
-        'expire' => $this->t('Remove items from the "cache" bin that have expired'),
-        'remove_all' => $this->t('Remove all items from the "cache" bin regardless of expiration'),
-        'remove_tag' => $this->t('Remove all items in the "cache" bin with the tag "cache_example" set to 1'),
-      ),
-      '#default_value' => 'expire',
-    );
-    // Submit button to clear cached data.
-    $form['cache_clearing']['clear_expired'] = array(
-      '#type' => 'submit',
-      '#value' => $this->t('Clear or expire cache'),
-      '#submit' => array(array($this, 'cacheClearing')),
-      '#access' => $this->currentUser->hasPermission('administer site configuration'),
-    );
-
-    return $form;
-  }
-
-  /**
-   * Submit handler that explicitly clears cache_example_files_count from cache.
-   */
-  public function expireFiles($form, &$form_state) {
-    // Clear cached data. This function will delete cached object from cache
-    // bin.
-    //
-    // The first argument is cache id to be deleted. Since we've provided it
-    // explicitly, it will be removed whether or not it has an associated
-    // expiration time. The second argument (required here) is the cache bin.
-    // Using cache_clear_all() explicitly in this way
-    // forces removal of the cached item.
-    $this->cacheBackend->delete('cache_example_files_count');
-
-    // Display message to the user.
-    drupal_set_message($this->t('Cached data key "cache_example_files_count" was cleared.'), 'status');
-  }
-
-  /**
-   * Submit handler to create a new cache item with specified expiration.
-   */
-  public function createExpiringItem($form, &$form_state) {
-
-    $tags = array(
-      'cache_example:1',
-    );
-
-    $interval = $form_state->getValue('expiration');
-    if ($interval == 'never_remove') {
-      $expiration = CacheBackendInterface::CACHE_PERMANENT;
-      $expiration_friendly = $this->t('Never expires');
-    }
-    else {
-      $expiration = time() + $interval;
-      $expiration_friendly = format_date($expiration);
-    }
-    // Set the expiration to the actual Unix timestamp of the end of the
-    // required interval. Also add a tag to it to be able to clear caches more
-    // precise.
-    $this->cacheBackend->set('cache_example_expiring_item', $expiration_friendly, $expiration, $tags);
-    drupal_set_message($this->t('cache_example_expiring_item was set to expire at %time', array('%time' => $expiration_friendly)));
-  }
-
-  /**
-   * Submit handler to demonstrate the various uses of cache_clear_all().
-   */
-  public function cacheClearing($form, &$form_state) {
-    switch ($form_state->getValue('cache_clear_type')) {
-      case 'expire':
-        // Here we'll remove all cache keys in the 'cache' bin that have
-        // expired.
-        $this->cacheBackend->garbageCollection();
-        drupal_set_message($this->t('\Drupal::cache()->garbageCollection() was called, removing any expired cache items.'));
-        break;
-
-      case 'remove_all':
-        // This removes all keys in a bin using a super-wildcard. This
-        // has nothing to do with expiration. It's just brute-force removal.
-        $this->cacheBackend->deleteAll();
-        drupal_set_message($this->t('ALL entries in the "cache" bin were removed with \Drupal::cache()->deleteAll().'));
-        break;
-
-      case 'remove_tag':
-        // This removes cache entries with the tag "cache_example" set to 1 in
-        // the "cache".
-        $tags = array(
-          'cache_example:1',
-        );
-        Cache::invalidateTags($tags);
-        drupal_set_message($this->t('Cache entries with the tag "cache_example" set to 1 in the "cache" bin were invalidated with \Drupal\Core\Cache\Cache::invalidateTags($tags).'));
-        break;
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, FormStateInterface $form_state) {
-
-  }
-
-}
diff --git a/cache_example/src/Forms/CacheExampleForm.php b/cache_example/src/Forms/CacheExampleForm.php
new file mode 100644
index 0000000..4b14a10
--- /dev/null
+++ b/cache_example/src/Forms/CacheExampleForm.php
@@ -0,0 +1,306 @@
+<?php
+
+namespace Drupal\cache_example\Forms;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Session\AccountProxyInterface;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+
+/**
+ * Form with examples on how to use cache.
+ */
+class CacheExampleForm extends FormBase {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountProxyInterface
+   */
+  protected $currentUser;
+
+  /**
+   * The cache.default cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cacheBackend;
+
+  /**
+   * Dependency injection through the constructor.
+   *
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack service.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $translation
+   *   The string translation service.
+   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
+   *   The current active user service.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   The cache object associated with the default bin.
+   */
+  public function __construct(
+    RequestStack $request_stack,
+    TranslationInterface $translation,
+    AccountProxyInterface $current_user,
+    CacheBackendInterface $cache_backend
+  ) {
+    $this->setRequestStack($request_stack);
+    $this->setStringTranslation($translation);
+    $this->currentUser = $current_user;
+    $this->cacheBackend = $cache_backend;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    // Forms that require a Drupal service or a custom service should access
+    // the service using dependency injection.
+    // @link https://www.drupal.org/node/2203931.
+    // Those services are passed in the $container through the static create
+    // method.
+    return new static(
+      $container->get('request_stack'),
+      $container->get('string_translation'),
+      $container->get('current_user'),
+      $container->get('cache.default')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'cron_cache';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    // Log execution time.
+    $start_time = microtime(TRUE);
+
+    // Try to load the files count from cache. This function will accept two
+    // arguments:
+    // - cache object name (cid)
+    // - cache bin, the (optional) cache bin (most often a database table) where
+    //   the object is to be saved.
+    //
+    // cache_get() returns the cached object or FALSE if object does not exist.
+    if ($cache = $this->cacheBackend->get('cache_example_files_count')) {
+      /*
+       * Get cached data. Complex data types will be unserialized automatically.
+       */
+      $files_count = $cache->data;
+    }
+    else {
+      // If there was no cached data available we have to search filesystem.
+      // Recursively get all .PHP files from Drupal's core folder.
+      $files_count = count(file_scan_directory('core', '/.php/'));
+
+      // Since we have recalculated, we now need to store the new data into
+      // cache. Complex data types will be automatically serialized before
+      // being saved into cache.
+      // Here we use the default setting and create an unexpiring cache item.
+      // See below for an example that creates an expiring cache item.
+      $this->cacheBackend->set('cache_example_files_count', $files_count, CacheBackendInterface::CACHE_PERMANENT);
+    }
+
+    $end_time = microtime(TRUE);
+    $duration = $end_time - $start_time;
+
+    // Format intro message.
+    $intro_message = '<p>' . $this->t("This example will search Drupal's core folder and display a count of the PHP files in it.") . ' ';
+    $intro_message .= $this->t('This can take a while, since there are a lot of files to be searched.') . ' ';
+    $intro_message .= $this->t('We will search filesystem just once and save output to the cache. We will use cached data for later requests.') . '</p>';
+    $intro_message .= '<p>'
+      . $this->t(
+        '<a href="@url">Reload this page</a> to see cache in action.',
+        array('@url' => $this->getRequest()->getRequestUri())
+      )
+      . ' ';
+    $intro_message .= $this->t('You can use the button below to remove cached data.') . '</p>';
+
+    $form['file_search'] = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('File search caching'),
+    );
+    $form['file_search']['introduction'] = array(
+      '#markup' => $intro_message,
+    );
+
+    $color = empty($cache) ? 'red' : 'green';
+    $retrieval = empty($cache) ? $this->t('calculated by traversing the filesystem') : $this->t('retrieved from cache');
+
+    $form['file_search']['statistics'] = array(
+      '#type' => 'item',
+      '#markup' => $this->t('%count files exist in this Drupal installation; @retrieval in @time ms. <br/>(Source: <span style="color:@color;">@source</span>)', array(
+        '%count' => $files_count,
+        '@retrieval' => $retrieval,
+        '@time' => number_format($duration * 1000, 2),
+        '@color' => $color,
+        '@source' => empty($cache) ? $this->t('actual file search') : $this->t('cached'),
+      )
+      ),
+    );
+    $form['file_search']['remove_file_count'] = array(
+      '#type' => 'submit',
+      '#submit' => array(array($this, 'expireFiles')),
+      '#value' => $this->t('Explicitly remove cached file count'),
+    );
+
+    $form['expiration_demo'] = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('Cache expiration settings'),
+    );
+    $form['expiration_demo']['explanation'] = array(
+      '#markup' => $this->t('A cache item can be set as CACHE_PERMANENT, meaning that it will only be removed when explicitly cleared, or it can have an expiration time (a Unix timestamp).'),
+    );
+
+    $item = $this->cacheBackend->get('cache_example_expiring_item', TRUE);
+    if ($item == FALSE) {
+      $item_status = $this->t('Cache item does not exist');
+    }
+    else {
+      $item_status = $item->valid ? $this->t('Cache item exists and is set to expire at %time', array('%time' => $item->data)) :
+      $this->t('Cache_item is invalid');
+    }
+
+    $form['expiration_demo']['current_status'] = array(
+      '#type' => 'item',
+      '#title' => $this->t('Current status of cache item "cache_example_expiring_item"'),
+      '#markup' => $item_status,
+    );
+    $form['expiration_demo']['expiration'] = array(
+      '#type' => 'select',
+      '#title' => $this->t('Time before cache expiration'),
+      '#options' => array(
+        'never_remove' => $this->t('CACHE_PERMANENT'),
+        -10 => $this->t('Immediate expiration'),
+        10 => $this->t('10 seconds from form submission'),
+        60 => $this->t('1 minute from form submission'),
+        300 => $this->t('5 minutes from form submission'),
+      ),
+      '#default_value' => -10,
+      '#description' => $this->t('Any cache item can be set to only expire when explicitly cleared, or to expire at a given time.'),
+    );
+    $form['expiration_demo']['create_cache_item'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Create a cache item with this expiration'),
+      '#submit' => array(array($this, 'createExpiringItem')),
+    );
+
+    $form['cache_clearing'] = array(
+      '#type' => 'fieldset',
+      '#title' => $this->t('Expire and remove options'),
+      '#description' => $this->t("We have APIs to expire cached items and also to just remove them. Unfortunately, they're all the same API, cache_clear_all"),
+    );
+    $form['cache_clearing']['cache_clear_type'] = array(
+      '#type' => 'radios',
+      '#title' => $this->t('Type of cache clearing to do'),
+      '#options' => array(
+        'expire' => $this->t('Remove items from the "cache" bin that have expired'),
+        'remove_all' => $this->t('Remove all items from the "cache" bin regardless of expiration'),
+        'remove_tag' => $this->t('Remove all items in the "cache" bin with the tag "cache_example" set to 1'),
+      ),
+      '#default_value' => 'expire',
+    );
+    // Submit button to clear cached data.
+    $form['cache_clearing']['clear_expired'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Clear or expire cache'),
+      '#submit' => array(array($this, 'cacheClearing')),
+      '#access' => $this->currentUser->hasPermission('administer site configuration'),
+    );
+
+    return $form;
+  }
+
+  /**
+   * Submit handler that explicitly clears cache_example_files_count from cache.
+   */
+  public function expireFiles($form, &$form_state) {
+    // Clear cached data. This function will delete cached object from cache
+    // bin.
+    //
+    // The first argument is cache id to be deleted. Since we've provided it
+    // explicitly, it will be removed whether or not it has an associated
+    // expiration time. The second argument (required here) is the cache bin.
+    // Using cache_clear_all() explicitly in this way
+    // forces removal of the cached item.
+    $this->cacheBackend->delete('cache_example_files_count');
+
+    // Display message to the user.
+    drupal_set_message($this->t('Cached data key "cache_example_files_count" was cleared.'), 'status');
+  }
+
+  /**
+   * Submit handler to create a new cache item with specified expiration.
+   */
+  public function createExpiringItem($form, &$form_state) {
+
+    $tags = array(
+      'cache_example:1',
+    );
+
+    $interval = $form_state->getValue('expiration');
+    if ($interval == 'never_remove') {
+      $expiration = CacheBackendInterface::CACHE_PERMANENT;
+      $expiration_friendly = $this->t('Never expires');
+    }
+    else {
+      $expiration = time() + $interval;
+      $expiration_friendly = format_date($expiration);
+    }
+    // Set the expiration to the actual Unix timestamp of the end of the
+    // required interval. Also add a tag to it to be able to clear caches more
+    // precise.
+    $this->cacheBackend->set('cache_example_expiring_item', $expiration_friendly, $expiration, $tags);
+    drupal_set_message($this->t('cache_example_expiring_item was set to expire at %time', array('%time' => $expiration_friendly)));
+  }
+
+  /**
+   * Submit handler to demonstrate the various uses of cache_clear_all().
+   */
+  public function cacheClearing($form, &$form_state) {
+    switch ($form_state->getValue('cache_clear_type')) {
+      case 'expire':
+        // Here we'll remove all cache keys in the 'cache' bin that have
+        // expired.
+        $this->cacheBackend->garbageCollection();
+        drupal_set_message($this->t('\Drupal::cache()->garbageCollection() was called, removing any expired cache items.'));
+        break;
+
+      case 'remove_all':
+        // This removes all keys in a bin using a super-wildcard. This
+        // has nothing to do with expiration. It's just brute-force removal.
+        $this->cacheBackend->deleteAll();
+        drupal_set_message($this->t('ALL entries in the "cache" bin were removed with \Drupal::cache()->deleteAll().'));
+        break;
+
+      case 'remove_tag':
+        // This removes cache entries with the tag "cache_example" set to 1 in
+        // the "cache".
+        $tags = array(
+          'cache_example:1',
+        );
+        Cache::invalidateTags($tags);
+        drupal_set_message($this->t('Cache entries with the tag "cache_example" set to 1 in the "cache" bin were invalidated with \Drupal\Core\Cache\Cache::invalidateTags($tags).'));
+        break;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+
+  }
+
+}
diff --git a/config_entity_example/config_entity_example.info.yml b/config_entity_example/config_entity_example.info.yml
index eedc3c5..99399fa 100644
--- a/config_entity_example/config_entity_example.info.yml
+++ b/config_entity_example/config_entity_example.info.yml
@@ -1,6 +1,6 @@
 name: 'Config entity example'
 type: module
-description: 'Demonstrates how to create a config entity type.'
+description: 'An example module showing how to create a config entity type.'
 package: Example modules
 core: 8.x
 dependencies:
diff --git a/content_entity_example/content_entity_example.info.yml b/content_entity_example/content_entity_example.info.yml
index b24e650..e1f8b20 100644
--- a/content_entity_example/content_entity_example.info.yml
+++ b/content_entity_example/content_entity_example.info.yml
@@ -1,6 +1,6 @@
 name: Content Entity Example
 type: module
-description: Demonstrates how to create a content entity.
+description: 'Provides ContentEntityExampleContact entity.'
 package: Example modules
 core: 8.x
 # These modules are required by the tests, must be available at bootstrap time
diff --git a/dbtng_example/dbtng_example.info.yml b/dbtng_example/dbtng_example.info.yml
index 46928e5..a7fff09 100644
--- a/dbtng_example/dbtng_example.info.yml
+++ b/dbtng_example/dbtng_example.info.yml
@@ -1,6 +1,6 @@
 name: DBTNG example
 type: module
-description: 'Demonstrates how to use the database API: DBTNG.'
+description: 'An example module showing how to use the database API: DBTNG.'
 package: Example modules
 core: 8.x
 dependencies:
diff --git a/email_example/email_example.info.yml b/email_example/email_example.info.yml
index 3763427..e499ba4 100644
--- a/email_example/email_example.info.yml
+++ b/email_example/email_example.info.yml
@@ -1,6 +1,6 @@
 name: Email Example
 type: module
-description: Demonstrates how to send and alter Drupal-generated email messages.
+description: An example module that sends and alter drupal generated email messages.
 package: Example modules
 version: 8.x-1.x
 core: 8.x
diff --git a/examples.info.yml b/examples.info.yml
index a4e5064..b596bff 100644
--- a/examples.info.yml
+++ b/examples.info.yml
@@ -1,6 +1,6 @@
-name: Examples for Developers
+name: Examples For Developers
 type: module
-description: 'Provides example code for you to learn from.'
+description: 'A variety of example code for you to learn from and hack upon.'
 package: Example modules
 core: 8.x
 dependencies:
diff --git a/fapi_example/fapi_example.info.yml b/fapi_example/fapi_example.info.yml
index edf3aac..f7ff39d 100644
--- a/fapi_example/fapi_example.info.yml
+++ b/fapi_example/fapi_example.info.yml
@@ -1,6 +1,6 @@
 name: Form API Example
 type: module
-description: Demonstrates the Drupal Form API.
+description: Sample implementations that demonstrate the Drupal Forms API
 package: Example modules
 core: 8.x
 dependencies:
diff --git a/field_example/field_example.info.yml b/field_example/field_example.info.yml
index f5d5855..c79ec2e 100644
--- a/field_example/field_example.info.yml
+++ b/field_example/field_example.info.yml
@@ -1,6 +1,6 @@
 name: Field Example
 type: module
-description: Demonstrates the Field API via some field plugin implementations.
+description: An implementation of a field to show the Field API
 package: Example modules
 core: 8.x
 dependencies:
diff --git a/field_example/src/Tests/ColorPickerWidgetTest.php b/field_example/src/Tests/ColorPickerWidgetTest.php
index c9dadd7..e1b5841 100644
--- a/field_example/src/Tests/ColorPickerWidgetTest.php
+++ b/field_example/src/Tests/ColorPickerWidgetTest.php
@@ -1,5 +1,6 @@
 <?php
 
+
 namespace Drupal\field_example\Tests;
 
 use Drupal\field_example\FieldExampleWebTestBase;
diff --git a/field_permission_example/field_permission_example.info.yml b/field_permission_example/field_permission_example.info.yml
index 710e3b6..1c3e7bd 100644
--- a/field_permission_example/field_permission_example.info.yml
+++ b/field_permission_example/field_permission_example.info.yml
@@ -1,6 +1,6 @@
 name: Field Permission Example
 type: module
-description: Demonstrates how to create a field and implement access control over it.
+description: An example module that creates a field and puts access control over it.
 package: Example modules
 version: 8.x-1.x
 core: 8.x
diff --git a/file_example/file_example.info.yml b/file_example/file_example.info.yml
index 15f23a1..5e2f1d1 100644
--- a/file_example/file_example.info.yml
+++ b/file_example/file_example.info.yml
@@ -1,6 +1,6 @@
 name: File example
 type: module
-description: Demonstrates using the Drupal File API and stream wrappers.
+description: Examples of using the Drupal File API and Stream Wrappers.
 package: Example modules
 core: 8.x
 dependencies:
diff --git a/file_example/file_example.module b/file_example/file_example.module
index ab2e980..601f66d 100644
--- a/file_example/file_example.module
+++ b/file_example/file_example.module
@@ -1,5 +1,4 @@
 <?php
-
 /**
  * @file
  * Examples demonstrating the Drupal File API.
diff --git a/js_example/js_example.info.yml b/js_example/js_example.info.yml
index fbd6480..494d15a 100755
--- a/js_example/js_example.info.yml
+++ b/js_example/js_example.info.yml
@@ -1,5 +1,5 @@
-name: JavaScript Example
-description: Demonstrates JavaScript features in Drupal 8.
+name: 'JavaScript Example'
+description: 'An example module showing how to use some of the new JavaScript features in Drupal 8'
 type: module
 package: 'Example modules'
 core: 8.x
diff --git a/node_type_example/node_type_example.info.yml b/node_type_example/node_type_example.info.yml
index b51ced7..9d75bf1 100755
--- a/node_type_example/node_type_example.info.yml
+++ b/node_type_example/node_type_example.info.yml
@@ -1,5 +1,5 @@
 name: Node Type Example
-description: Demonstrates implementing a node content type in a module.
+description: The simplest example of implementing a node content type in a module.
 package: Example modules
 type: module
 core: 8.x
diff --git a/page_example/page_example.info.yml b/page_example/page_example.info.yml
index 15ff9b2..3f33579 100644
--- a/page_example/page_example.info.yml
+++ b/page_example/page_example.info.yml
@@ -1,6 +1,6 @@
 name: Page example
 type: module
-description: 'Demonstrates how to display a page at a given URL.'
+description: 'An example module showing how to define a page to be displayed at a given URL.'
 package: Example modules
 core: 8.x
 dependencies:
diff --git a/page_example/page_example.module b/page_example/page_example.module
index a1d0ab9..df92ea5 100644
--- a/page_example/page_example.module
+++ b/page_example/page_example.module
@@ -1,5 +1,4 @@
 <?php
-
 /**
  * @file
  * Module file for page_example_module.
diff --git a/page_example/src/Tests/PageExampleTest.php b/page_example/src/Tests/PageExampleTest.php
new file mode 100644
index 0000000..c56bbf6
--- /dev/null
+++ b/page_example/src/Tests/PageExampleTest.php
@@ -0,0 +1,172 @@
+<?php
+
+namespace Drupal\page_example\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Creates page and render the content based on the arguments passed in the URL.
+ *
+ * @group page_example
+ * @group examples
+ */
+class PageExampleTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('page_example');
+
+  /**
+   * The installation profile to use with this test.
+   *
+   * We need the 'minimal' profile in order to make sure the Tool block is
+   * available.
+   *
+   * @var string
+   */
+  protected $profile = 'minimal';
+
+  /**
+   * User object for our test.
+   *
+   * @var \Drupal\user\Entity\User
+   */
+  protected $webUser;
+
+  /**
+   * Generates a random string of ASCII numeric characters (values 48 to 57).
+   *
+   * @param int $length
+   *   Length of random string to generate.
+   *
+   * @return string
+   *   Randomly generated string.
+   */
+  protected static function randomNumber($length = 8) {
+    $str = '';
+    for ($i = 0; $i < $length; $i++) {
+      $str .= chr(mt_rand(48, 57));
+    }
+    return $str;
+  }
+
+  /**
+   * Verify that current user has no access to page.
+   *
+   * @param string $url
+   *   URL to verify.
+   */
+  public function pageExampleVerifyNoAccess($url) {
+    // Test that page returns 403 Access Denied.
+    $this->drupalGet($url);
+    $this->assertResponse(403);
+  }
+
+  /**
+   * Data provider for testing menu links.
+   *
+   * @return array
+   *   Array of page -> link relationships to check for, keyed by the
+   *   permissions required to access them:
+   *   - Permission machine name. Empty string means no login.
+   *     - Key is path to the page where the link should appear.
+   *     - Value is the link that should appear on the page.
+   */
+  protected function providerMenuLinks() {
+    return array(
+      '' => array(
+        '' => '/examples/page-example',
+      ),
+      'access simple page' => array(
+        '/examples/page-example' => '/examples/page-example/simple',
+      ),
+    );
+  }
+
+  /**
+   * Verify and validate that default menu links were loaded for this module.
+   */
+  public function testPageExampleLinks() {
+    $data = $this->providerMenuLinks();
+    foreach ($data as $permission => $links) {
+      if ($permission) {
+        $user = $this->drupalCreateUser(array($permission));
+        $this->drupalLogin($user);
+      }
+      foreach ($links as $page => $path) {
+        $this->drupalGet($page);
+        $this->assertLinkByHref($path);
+      }
+      if ($permission) {
+        $this->drupalLogout();
+      }
+    }
+  }
+
+  /**
+   * Main test.
+   *
+   * Login user, create an example node, and test page functionality through
+   * the admin and user interfaces.
+   */
+  public function testPageExample() {
+    // Verify that anonymous user can't access the pages created by
+    // page_example module.
+    $this->pageExampleVerifyNoAccess('examples/page-example/simple');
+    $this->pageExampleVerifyNoAccess('examples/page-example/arguments/1/2');
+
+    // Create a regular user and login.
+    $this->webUser = $this->drupalCreateUser();
+    $this->drupalLogin($this->webUser);
+
+    // Verify that regular user can't access the pages created by
+    // page_example module.
+    $this->pageExampleVerifyNoAccess('examples/page-example/simple');
+    $this->pageExampleVerifyNoAccess('examples/page-example/arguments/1/2');
+
+    // Create a user with permissions to access 'simple' page and login.
+    $this->webUser = $this->drupalCreateUser(array('access simple page'));
+    $this->drupalLogin($this->webUser);
+
+    // Verify that user can access simple content.
+    $this->drupalGet('/examples/page-example/simple');
+    $this->assertResponse(200, 'Simple content successfully accessed.');
+    $this->assertText(t('The quick brown fox jumps over the lazy dog.'), 'Simple content successfully verified.');
+
+    // Check if user can't access arguments page.
+    $this->pageExampleVerifyNoAccess('examples/page-example/arguments/1/2');
+
+    // Create a user with permissions to access 'simple' page and login.
+    $this->webUser = $this->drupalCreateUser(array('access arguments page'));
+    $this->drupalLogin($this->webUser);
+
+    // Verify that user can access arguments content.
+    $first = self::randomNumber(3);
+    $second = self::randomNumber(3);
+    $this->drupalGet('/examples/page-example/arguments/' . $first . '/' . $second);
+    $this->assertResponse(200, 'Arguments content successfully accessed.');
+    // Verify argument usage.
+    $this->assertRaw(t('First number was @number.', array('@number' => $first)), 'First argument successfully verified.');
+    $this->assertRaw(t('Second number was @number.', array('@number' => $second)), 'Second argument successfully verified.');
+    $this->assertRaw(t('The total was @number.', array('@number' => $first + $second)), 'arguments content successfully verified.');
+
+    // Verify incomplete argument call to arguments content.
+    $this->drupalGet('/examples/page-example/arguments/' . $first . '/');
+    $this->assertResponse(404, 'User got 404 on incomplete arguments request.');
+
+    // Verify 403 for invalid second argument.
+    $this->drupalGet('/examples/page-example/arguments/' . $first . '/non-numeric-argument');
+    $this->assertResponse(403, 'User got 403 for string argument in second position.');
+
+    // Verify 403 for invalid first argument.
+    $this->drupalGet('/examples/page-example/arguments/non-numeric-argument/' . $second);
+    $this->assertResponse(403, 'User got 403 for string argument in first position.');
+
+    // Check if user can't access simple page.
+    $this->pageExampleVerifyNoAccess('examples/page-example/simple');
+  }
+
+}
diff --git a/page_example/tests/src/Functional/PageExampleTest.php b/page_example/tests/src/Functional/PageExampleTest.php
deleted file mode 100644
index 429bc56..0000000
--- a/page_example/tests/src/Functional/PageExampleTest.php
+++ /dev/null
@@ -1,176 +0,0 @@
-<?php
-
-namespace Drupal\Tests\page_example\Functional;
-
-use Drupal\Tests\BrowserTestBase;
-
-/**
- * Creates page and render the content based on the arguments passed in the URL.
- *
- * @group page_example
- * @group examples
- */
-class PageExampleTest extends BrowserTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = array('page_example');
-
-  /**
-   * The installation profile to use with this test.
-   *
-   * We need the 'minimal' profile in order to make sure the Tool block is
-   * available.
-   *
-   * @var string
-   */
-  protected $profile = 'minimal';
-
-  /**
-   * User object for our test.
-   *
-   * @var \Drupal\user\Entity\User
-   */
-  protected $webUser;
-
-  /**
-   * Generates a random string of ASCII numeric characters (values 48 to 57).
-   *
-   * @param int $length
-   *   Length of random string to generate.
-   *
-   * @return string
-   *   Randomly generated string.
-   */
-  protected static function randomNumber($length = 8) {
-    $str = '';
-    for ($i = 0; $i < $length; $i++) {
-      $str .= chr(mt_rand(48, 57));
-    }
-    return $str;
-  }
-
-  /**
-   * Verify that current user has no access to page.
-   *
-   * @param string $url
-   *   URL to verify.
-   */
-  public function pageExampleVerifyNoAccess($url) {
-    // Test that page returns 403 Access Denied.
-    $this->drupalGet($url);
-    $this->assertSession()->statusCodeEquals(403);
-  }
-
-  /**
-   * Data provider for testing menu links.
-   *
-   * @return array
-   *
-   *   Array of page -> link relationships to check for, with the permissions
-   *   required to access them:
-   *   - Permission machine name. Empty string means no login.
-   *   - Array of link information:
-   *     - Key is path to the page where the link should appear.
-   *     - Value is the link that should appear on the page.
-   */
-  public function providerMenuLinks() {
-    return [
-      [
-        '',
-        ['' => '/examples/page-example'],
-      ],
-      [
-        'access simple page',
-        ['/examples/page-example' => '/examples/page-example/simple'],
-      ],
-    ];
-  }
-
-  /**
-   * Verify and validate that default menu links were loaded for this module.
-   *
-   * @dataProvider providerMenuLinks
-   */
-  public function testPageExampleLinks($permission, $links) {
-    if ($permission) {
-      $user = $this->drupalCreateUser(array($permission));
-      $this->drupalLogin($user);
-    }
-    foreach ($links as $page => $path) {
-      $this->drupalGet($page);
-      $this->assertSession()->linkByHrefExists($path);
-    }
-    if ($permission) {
-      $this->drupalLogout();
-    }
-  }
-
-  /**
-   * Main test.
-   *
-   * Login user, create an example node, and test page functionality through
-   * the admin and user interfaces.
-   */
-  public function testPageExample() {
-    $assert_session = $this->assertSession();
-    // Verify that anonymous user can't access the pages created by
-    // page_example module.
-    $this->pageExampleVerifyNoAccess('examples/page-example/simple');
-    $this->pageExampleVerifyNoAccess('examples/page-example/arguments/1/2');
-
-    // Create a regular user and login.
-    $this->webUser = $this->drupalCreateUser();
-    $this->drupalLogin($this->webUser);
-
-    // Verify that regular user can't access the pages created by
-    // page_example module.
-    $this->pageExampleVerifyNoAccess('examples/page-example/simple');
-    $this->pageExampleVerifyNoAccess('examples/page-example/arguments/1/2');
-
-    // Create a user with permissions to access 'simple' page and login.
-    $this->webUser = $this->drupalCreateUser(array('access simple page'));
-    $this->drupalLogin($this->webUser);
-
-    // Verify that user can access simple content.
-    $this->drupalGet('/examples/page-example/simple');
-    $assert_session->statusCodeEquals(200);
-    $assert_session->pageTextContains('The quick brown fox jumps over the lazy dog.');
-
-    // Check if user can't access arguments page.
-    $this->pageExampleVerifyNoAccess('examples/page-example/arguments/1/2');
-
-    // Create a user with permissions to access 'simple' page and login.
-    $this->webUser = $this->drupalCreateUser(array('access arguments page'));
-    $this->drupalLogin($this->webUser);
-
-    // Verify that user can access arguments content.
-    $first = self::randomNumber(3);
-    $second = self::randomNumber(3);
-    $this->drupalGet('/examples/page-example/arguments/' . $first . '/' . $second);
-    $assert_session->statusCodeEquals(200);
-    // Verify argument usage.
-    $assert_session->pageTextContains(t('First number was @number.', array('@number' => $first)));
-    $assert_session->pageTextContains(t('Second number was @number.', array('@number' => $second)));
-    $assert_session->pageTextContains(t('The total was @number.', array('@number' => $first + $second)));
-
-    // Verify incomplete argument call to arguments content.
-    $this->drupalGet('/examples/page-example/arguments/' . $first . '/');
-    $assert_session->statusCodeEquals(404);
-
-    // Verify 403 for invalid second argument.
-    $this->drupalGet('/examples/page-example/arguments/' . $first . '/non-numeric-argument');
-    $assert_session->statusCodeEquals(403);
-
-    // Verify 403 for invalid first argument.
-    $this->drupalGet('/examples/page-example/arguments/non-numeric-argument/' . $second);
-    $assert_session->statusCodeEquals(403);
-
-    // Check if user can't access simple page.
-    $this->pageExampleVerifyNoAccess('examples/page-example/simple');
-  }
-
-}
diff --git a/pager_example/pager_example.info.yml b/pager_example/pager_example.info.yml
index 181e234..073e47a 100644
--- a/pager_example/pager_example.info.yml
+++ b/pager_example/pager_example.info.yml
@@ -1,6 +1,6 @@
 name: Pager Example
 type: module
-description: Demonstrates how to use table and render page elements to create paginated tables.
+description: 'The example output nodes with pager.'
 package: Example modules
 core: 8.x
 dependencies:
diff --git a/phpunit_example/phpunit_example.info.yml b/phpunit_example/phpunit_example.info.yml
index 5bcc5f9..f69b87e 100644
--- a/phpunit_example/phpunit_example.info.yml
+++ b/phpunit_example/phpunit_example.info.yml
@@ -1,6 +1,6 @@
 name: PHPUnit example
 type: module
-description: Demontrates how to use PHPUnit-based tests.
+description: 'How to use PHPUnit-based tests with Drupal 8.'
 package: Example modules
 core: 8.x
 dependencies:
diff --git a/phpunit_example/phpunit_example.module b/phpunit_example/phpunit_example.module
index b02383d..1dafe85 100644
--- a/phpunit_example/phpunit_example.module
+++ b/phpunit_example/phpunit_example.module
@@ -1,5 +1,4 @@
 <?php
-
 /**
  * @file
  * Module file for phpunit_example.
diff --git a/plugin_type_example/plugin_type_example.api.php b/plugin_type_example/plugin_type_example.api.php
index e3f6e3a..8ad888c 100644
--- a/plugin_type_example/plugin_type_example.api.php
+++ b/plugin_type_example/plugin_type_example.api.php
@@ -1,5 +1,4 @@
 <?php
-
 /**
  * @file
  * Hooks specific to the plugin_type_example module.
diff --git a/plugin_type_example/plugin_type_example.module b/plugin_type_example/plugin_type_example.module
index 426781e..eea5ec8 100644
--- a/plugin_type_example/plugin_type_example.module
+++ b/plugin_type_example/plugin_type_example.module
@@ -11,45 +11,61 @@
  * @{
  * Example of how to define a plugin type.
  *
- * Our example defines a Sandwich plugin type.  Sandwich's come in many flavors,
- * each having their unique qualities, but also sharing things in common.  Our
+ * Our example defines a Sandwich plugin type. Sandwich's come in many flavors,
+ * each having their unique qualities, but also sharing things in common. Our
  * Sandwich plugins will be very simple, each providing a unique description
- * or themselves.  Sandwich plugins also have an id and a property called
- * foobar, which we'll use to demonstrate how you can alter a plugin using an
- * alter hook.
+ * of themselves. In addition to the id property required by all plugin types,
+ * sandwich plugins have description, and calories properties.
+ *
+ * Defining a new plugin type requires the following steps:
+ * - Define an annotation class for your plugin type used to provide
+ *   documentation about the meta-data needed by your plugin type.
+ * - Define an interface for plugins of your new type to implement.
+ * - Define a new plugin manager.
+ * - Provide an optional base class that implements your plugin type's interface
+ *   as a starting point for other plugin implementations.
  *
  * Start at the class \Drupal\plugin_type_example\SandwichPluginManager, which
- * defines our plugin type manager.  Our plugin type manager is exposed to
- * Drupal as a service in the plugin_type_example.services.yml file.
+ * defines our plugin manager. Our plugin manager is exposed to Drupal as a
+ * service in the plugin_type_example.services.yml file, and is used to glue
+ * our Drupal\plugin_type_example\SandwichInterface, and
+ * \Drupal\plugin_type_example\Annotation\Sandwich annotation definition
+ * together into a new plugin type.
  *
  * We've also defined two implementations of our plugin type,
  * \Drupal\plugin_type_example\Plugin\Sandwich\ExampleHamSandwich and
  * \Drupal\plugin_type_example\Plugin\Sandwich\ExampleMeatballSandwich. Take a
- * look at each.  These plugins use annotated plugin discovery, and as such have
- * an annotation block in the doc block for the class.  Other than that, they
+ * look at each. These plugins use annotated plugin discovery, and as such have
+ * an annotation block in the doc block for the class. Other than that, they
  * implement the \Drupal\plugin_type_example\SandwichInterface, which is
  * required, since we've set that interface for the plugin type in
  * \Drupal\plugin_type_example\SandwichPluginManager::__construct.
  *
- * To see the plugins in action visit /examples/plugin_type_example.  The output
+ * In plugin_type_example_sandwich_info_alter() we'll demonstrate how you can
+ * alter a plugin definition using an alter hook.
+ *
+ * To see the plugins in action visit /examples/plugin_type_example. The output
  * is rendered in
  * Drupal\plugin_type_example\Controller\PluginTypeExampleController::description().
- * Read through that method to see how to use plugins using the
+ * Read through that method to see how to make use of plugins using the
  * plugin.manager.sandwich service.
  */
 
 /**
  * Implements hook_sandwich_info_alter().
+ *
+ * Any module can implement hook_sandwich_info_alter() and alter the definitions
+ * of all plugins discovered by the plugin manager.
+ *
+ * Note: Plugin definitions are cached after initially being discovered, and
+ * altered. Any changes you make here will require a cache clear to take effect.
+ *
+ * @see \Drupal\plugin_type_example\SandwichPluginManager
  */
 function plugin_type_example_sandwich_info_alter(&$sandwich_plugin_info) {
-  // Let's change the 'foobar' property for all sandwiches.
+  // Let's change the 'description' property for all sandwiches.
   foreach ($sandwich_plugin_info as $plugin_id => $plugin_definition) {
-    $sandwich_plugin_info[$plugin_id]['foobar'] = t('We have altered this in the alter hook');
+    // Always uppercase the word rocket in sandwich descriptions.
+    $sandwich_plugin_info[$plugin_id]['description'] = str_replace(t('rocket'), t('ROCKET'), $sandwich_plugin_info[$plugin_id]['description']);
   }
-
-  // The ham sandwich is more calorific in our system.  Note that the string
-  // 'ham_sandwich' is the id name of the Ham Sandwich plugin, which is set
-  // in the annotation in
-  // \Drupal\plugin_type_example\Plugin\Sandwich\ExampleHamSandwich.
-  $sandwich_plugin_info['ham_sandwich']['calories'] = 500;
 }
diff --git a/plugin_type_example/plugin_type_example.services.yml b/plugin_type_example/plugin_type_example.services.yml
index bd9c129..db658ff 100644
--- a/plugin_type_example/plugin_type_example.services.yml
+++ b/plugin_type_example/plugin_type_example.services.yml
@@ -1,19 +1,11 @@
-# This declares the plugin manager to the service container.
-# For background information on the service container, see
-# http://symfony.com/doc/current/book/service_container.html.
-# Services declaration files such as this one are compiled to PHP code in
-# sites/default/files/php/service_container, so changes here require that folder
-# to be deleted in order to have Drupal notice them.
+# This declares the plugin manager to the service container. For background
+# information on the service container, see https://www.drupal.org/node/2133171.
+# Changes here require that the cache be cleared in order to have Drupal notice
+# them.
 services:
   # The machine name of the service. This is the string that must be passed to
   # Drupal::service() to get the instantiated plugin manager.
   plugin.manager.sandwich:
     # This tells the service container the name of our plugin manager class.
     class: Drupal\plugin_type_example\SandwichPluginManager
-    # The default parameters to pass to the class constructor,
-    # SandwichPluginManager::__construct().
-    # Arguments beginning with @ are the names of other services that are being
-    # injected into this service. Many common ones are found in
-    # /core/core.services.yml.
-    # See https://drupal.org/node/2133171 for more detail.
-    arguments: ['@container.namespaces', '@cache.default', '@module_handler']
+    parent: default_plugin_manager
diff --git a/plugin_type_example/src/Annotation/Sandwich.php b/plugin_type_example/src/Annotation/Sandwich.php
new file mode 100644
index 0000000..1b8f5a9
--- /dev/null
+++ b/plugin_type_example/src/Annotation/Sandwich.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\plugin_type_example\Annotation\Sandwich.
+ *
+ * Provides an example of how to define a new annotation type for use in
+ * defining a plugin type. Demonstrates documenting the various properties that
+ * can be used in annotations for plugins of this type.
+ */
+
+namespace Drupal\plugin_type_example\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines a Sandwich annotation object.
+ *
+ * @see \Drupal\plugin_type_example\SandwichPluginManager
+ * @see plugin_api
+ *
+ * Note that the "@ Annotation" line below is required and should be the last
+ * line in the docblock. It's used for discovery of Annotation definitions.
+ *
+ * @Annotation
+ */
+class Sandwich extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * A brief, human readable, description of the sandwich type.
+   *
+   * This property is designated as being translatable because it will appear
+   * in the user interface. This provides a hint to other developers that they
+   * should use the Translation() construct in their annotation when declaring
+   * this property.
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   *
+   * @ingroup plugin_translatable
+   */
+  public $description;
+
+  /**
+   * The number of calories per serving of this sandwich type.
+   *
+   * This property is a float value, so we indicate that to other developers
+   * who are writing annotations for a Sandwich plugin.
+   *
+   * @var int
+   */
+  public $calories;
+
+}
diff --git a/plugin_type_example/src/Controller/PluginTypeExampleController.php b/plugin_type_example/src/Controller/PluginTypeExampleController.php
index f1cf8ee..a22834d 100644
--- a/plugin_type_example/src/Controller/PluginTypeExampleController.php
+++ b/plugin_type_example/src/Controller/PluginTypeExampleController.php
@@ -35,10 +35,14 @@ class PluginTypeExampleController extends ControllerBase {
    * Displays a page with an overview of our plugin type and plugins.
    *
    * Lists all the Sandwich plugin definitions by using methods on the
-   * \Drupal\plugin_type_example\SandwichPluginManager class.  Lists out the
+   * \Drupal\plugin_type_example\SandwichPluginManager class. Lists out the
    * description for each plugin found by invoking methods defined on the
-   * plugins themselves.  You can find the plugins we have defined in the
+   * plugins themselves. You can find the plugins we have defined in the
    * \Drupal\plugin_type_example\Plugin\Sandwich namespace.
+   *
+   * @return array
+   *  Render API array with content for the page at
+   *  /examples/plugin_type_example.
    */
   public function description() {
     $build = array();
@@ -48,9 +52,8 @@ class PluginTypeExampleController extends ControllerBase {
     );
 
     // Get the list of all the sandwich plugins defined on the system from the
-    // plugin manager.
-    // Note that at this point, what we have is *definitions* of plugins, not
-    // the plugins themselves.
+    // plugin manager. Note that at this point, what we have is *definitions* of
+    // plugins, not the plugins themselves.
     $sandwich_plugin_definitions = $this->sandwichManager->getDefinitions();
 
     // Let's output a list of the plugin definitions we now have.
@@ -58,11 +61,11 @@ class PluginTypeExampleController extends ControllerBase {
     foreach ($sandwich_plugin_definitions as $sandwich_plugin_definition) {
       // Here we use various properties from the plugin definition. These values
       // are defined in the annotation at the top of the plugin class: see
-      // ExampleHamSandwich.
-      $items[] = t("@id (calories: @calories, foobar: @foobar )", array(
-        '@id'       => $sandwich_plugin_definition['id'],
+      // \Drupal\plugin_type_example\Plugin\Sandwich\ExampleHamSandwich.
+      $items[] = t("@id (calories: @calories, description: @description )", array(
+        '@id' => $sandwich_plugin_definition['id'],
         '@calories' => $sandwich_plugin_definition['calories'],
-        '@foobar'   => $sandwich_plugin_definition['foobar'],
+        '@description' => $sandwich_plugin_definition['description'],
       ));
     }
 
@@ -75,18 +78,20 @@ class PluginTypeExampleController extends ControllerBase {
 
     // If we want just a single plugin definition, we can use getDefinition().
     // This requires us to know the ID of the plugin we want. This is set in the
-    // annotation on the plugin class: see ExampleHamSandwich.
-    $ham_sandwich_plugin_definition = $this->sandwichManager->getDefinition('ham_sandwich');
-
-    // To get an actual plugin, we call createInstance() on the plugin manager,
-    // passing the ID of the plugin we want to load. Let's output a list of the
-    // actual plugins.
+    // annotation on the plugin class: see
+    // \Drupal\plugin_type_example\Plugin\Sandwich\ExampleHamSandwich.
+    $ham_sandwich_plugin_definition = $this->sandwichManager->getDefinition('meatball_sandwich');
+
+    // To get an instance of a plugin, we call createInstance() on the plugin
+    // manager, passing the ID of the plugin we want to load. Let's output a
+    // list of the plugins by loading an instance of each plugin definition and
+    // collecting the description from each.
     $items = array();
     // The array of plugin definitions is keyed by plugin id, so we can just use
-    // that to load our plugins.
+    // that to load our plugin instances.
     foreach ($sandwich_plugin_definitions as $plugin_id => $sandwich_plugin_definition) {
-      // We now have a plugin! From here on it can be treated just as any other
-      // object: have its properties examined, methods called, etc.
+      // We now have a plugin instance. From here on it can be treated just as
+      // any other object; have its properties examined, methods called, etc.
       $plugin = $this->sandwichManager->createInstance($plugin_id, array('of' => 'configuration values'));
       $items[] = $plugin->description();
     }
@@ -103,21 +108,17 @@ class PluginTypeExampleController extends ControllerBase {
   /**
    * {@inheritdoc}
    *
-   * Override the parent method so that we can inject out sandwich plugin
-   * manager service into the controller.  This is dependancy injection at work
-   * for a controller.  Rather than access the global service container via
-   * \Drupal::service(), it's best practice to use dependancy injection.  Notice
-   * our controller extends ControllerBase, which implements the
-   * ContainerInjectionInterface interface.  As such we can implement a create
-   * method to control how our controller in instantiated.  We do just that,
-   * passing in the sandwich plugin manager service that we need.
+   * Override the parent method so that we can inject our sandwich plugin
+   * manager service into the controller.
+   *
+   * For more about how dependency injection works read
+   * https://www.drupal.org/node/2133171
+   *
+   * @see container
    */
   public static function create(ContainerInterface $container) {
-    // Use the service container to instantiate a new instance of our
-    // controller.  The string we pass is the machine name of the service,
-    // which is set in the plugin_type_example.services.yml file.  We get back
-    // an instance of our SandwichPluginManager class, which is passed to the
-    // controller's constructor.
+    // Inject the plugin.manager.sandwich service that represents our plugin
+    // manager as defined in the plugin_type_example.services.yml file.
     return new static($container->get('plugin.manager.sandwich'));
   }
 
diff --git a/plugin_type_example/src/Plugin/Sandwich/ExampleHamSandwich.php b/plugin_type_example/src/Plugin/Sandwich/ExampleHamSandwich.php
index f09c837..5e39eba 100644
--- a/plugin_type_example/src/Plugin/Sandwich/ExampleHamSandwich.php
+++ b/plugin_type_example/src/Plugin/Sandwich/ExampleHamSandwich.php
@@ -2,15 +2,15 @@
 
 namespace Drupal\plugin_type_example\Plugin\Sandwich;
 
-use Drupal\Core\Plugin\PluginBase;
-use Drupal\plugin_type_example\SandwichInterface;
+use Drupal\plugin_type_example\SandwichBase;
 
 /**
  * Provides a ham sandwich.
  *
  * Because the plugin manager class for our plugins uses annotated class
- * discovery, our ham sandwich only needs to exist within the Plugin\Sandwich
- * namespace to be declared as a plugin. This is defined in
+ * discovery, our meatball sandwich only needs to exist within the
+ * Plugin\Sandwich namespace, and provide a Sandwich annotation to be declared
+ *  as a plugin. This is defined in
  * \Drupal\plugin_type_example\SandwichPluginManager::__construct().
  *
  * The following is the plugin annotation. This is parsed by Doctrine to make
@@ -22,22 +22,30 @@ use Drupal\plugin_type_example\SandwichInterface;
  * available plugins where the user selects one. This means many plugin
  * annotations can be reduced to a plugin ID, a label and perhaps a description.
  *
- * @Plugin(
+ * @Sandwich(
  *   id = "ham_sandwich",
- *   foobar = @Translation("This is an example value that is defined in the annotation."),
- *   calories = 426,
+ *   description = @Translation("Ham, mustard, rocket, sun-dried tomatoes."),
+ *   calories = 426
  * )
  */
-class ExampleHamSandwich extends PluginBase implements SandwichInterface {
+class ExampleHamSandwich extends SandwichBase {
 
   /**
-   * Get a description of the sandwich fillings.
+   * Place an order for a sandwich.
    *
    * This is just an example method on our plugin that we can call to get
    * something back.
+   *
+   * @param array $extras
+   *   Array of extras to include with this order.
+   *
+   * @return string
+   *   A description of the sandwich ordered.
    */
-  public function description() {
-    return $this->t('Ham, mustard, rocket, sun-dried tomatoes.');
+  public function order(array $extras) {
+    $ingredients = array('ham, mustard', 'rocket', 'sun-dried tomatoes');
+    $sandwich = array_merge($ingredients, $extras);
+    return 'You ordered an ' . implode(', ', $sandwich) . ' sandwich. Enjoy!';
   }
 
 }
diff --git a/plugin_type_example/src/Plugin/Sandwich/ExampleMeatballSandwich.php b/plugin_type_example/src/Plugin/Sandwich/ExampleMeatballSandwich.php
index 4922769..10aa35f 100644
--- a/plugin_type_example/src/Plugin/Sandwich/ExampleMeatballSandwich.php
+++ b/plugin_type_example/src/Plugin/Sandwich/ExampleMeatballSandwich.php
@@ -2,15 +2,15 @@
 
 namespace Drupal\plugin_type_example\Plugin\Sandwich;
 
-use Drupal\Core\Plugin\PluginBase;
-use Drupal\plugin_type_example\SandwichInterface;
+use Drupal\plugin_type_example\SandwichBase;
 
 /**
  * Provides a meatball sandwich.
  *
  * Because the plugin manager class for our plugins uses annotated class
  * discovery, our meatball sandwich only needs to exist within the
- * Plugin\Sandwich namespace to be declared as a plugin. This is defined in
+ * Plugin\Sandwich namespace, and provide a Sandwich annotation to be declared
+ * as a plugin. This is defined in
  * \Drupal\plugin_type_example\SandwichPluginManager::__construct().
  *
  * The following is the plugin annotation. This is parsed by Doctrine to make
@@ -22,22 +22,42 @@ use Drupal\plugin_type_example\SandwichInterface;
  * available plugins where the user selects one. This means many plugin
  * annotations can be reduced to a plugin ID, a label and perhaps a description.
  *
- * @Plugin(
+ * @Sandwich(
  *   id = "meatball_sandwich",
- *   foobar = @Translation("Right around that corner there is a sandwich shop. They sell meatball sandwiches, the best I ever tasted. Would you go get me two? Come on partner. Thank you. Utah! Get me two!"),
- *   calories = 1200,
+ *   description = @Translation("Italian style meatballs drenched in irresistible marinara sauce, served on freshly baked bread."),
+ *   calories = "1200"
  * )
  */
-class ExampleMeatballSandwich extends PluginBase implements SandwichInterface {
+class ExampleMeatballSandwich extends SandwichBase {
 
   /**
-   * Get a description of the sandwich fillings.
+   * Place an order for a sandwich.
    *
    * This is just an example method on our plugin that we can call to get
    * something back.
+   *
+   * @param array $extras
+   *   Array of extras to include with this order.
+   *
+   * @return string
+   *   A description of the sandwich ordered.
+   */
+  public function order(array $extras) {
+    $ingredients = array('meatballs', 'irresistible marinara sauce');
+    $sandwich = array_merge($ingredients, $extras);
+    return 'You ordered an ' . implode(', ', $sandwich) . ' sandwich. Enjoy!';
+  }
+
+  /**
+   * Override the parent class description method. We'll provide our own in this
+   * case.
    */
   public function description() {
-    return $this->t('Enjoy Italian style meatballs drenched in irresistible marinara sauce, served on freshly baked bread.');
+    // On Sunday we only have day old bread.
+    if (date('D') == 'Sun') {
+      return $this->t("Italian style meatballs drenched in irresistible marinara sauce, served on day old bread.");
+    }
+    return parent::description();
   }
 
 }
diff --git a/plugin_type_example/src/SandwichBase.php b/plugin_type_example/src/SandwichBase.php
new file mode 100644
index 0000000..4f28909
--- /dev/null
+++ b/plugin_type_example/src/SandwichBase.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Drupal\plugin_type_example;
+
+use Drupal\Component\Plugin\PluginBase;
+
+/**
+ * Provides \Drupal\plugin_type_example\SandwichBase.
+ *
+ * This is a helper class which makes it easier for other developers to
+ * implement sandwich plugins in their own modules. In SandwichBase we provide
+ * some generic methods for handling tasks that are common to pretty much all
+ * sandwich plugins. Thereby reducing the amount of boilerplate code required to
+ * implement a sandwich plugin.
+ *
+ * In this case both the description and calories properties can be read from
+ * the @Sandwich annotation. In most cases it is probably fine to just use that
+ * value without any additional processing. However, if an individual plugin
+ * needed to provide special handling around either of these things it could
+ * just override the method in that class definition for that plugin.
+ *
+ * We intentionally declare our base class as abstract, and skip the order()
+ * method required by \Drupal\plugin_type_example\SandwichInterface. This way
+ * even if they are using our base class, developers will always be required to
+ * define an order() method for their custom sandwich type.
+ *
+ * @see \Drupal\plugin_type_example\Annotation\Sandwich
+ * @see \Drupal\plugin_type_example\SandwichInterface
+ */
+abstract class SandwichBase extends PluginBase implements SandwichInterface {
+
+  /**
+   * Retrieve the @description property from the annotation and return it.
+   *
+   * @return string
+   */
+  public function description() {
+    return $this->pluginDefinition['description'];
+  }
+
+  /**
+   * Retrieve the @calories property from the annotation and return it.
+   *
+   * @return float
+   */
+  public function calories() {
+    return (float) $this->pluginDefinition['calories'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  abstract public function order(array $extras);
+
+}
diff --git a/plugin_type_example/src/SandwichInterface.php b/plugin_type_example/src/SandwichInterface.php
index aa5576f..3413268 100644
--- a/plugin_type_example/src/SandwichInterface.php
+++ b/plugin_type_example/src/SandwichInterface.php
@@ -1,5 +1,24 @@
 <?php
 
+/**
+ * @file
+ * Provides \Drupal\plugin_type_example\SandwichInterface.
+ *
+ * When defining a new plugin type you need to define an interface that all
+ * plugins of the new type will implement. This ensures that consumers of the
+ * plugin type have a consistent way of accessing the plugin's functionality. It
+ * should include access to any public properties, and methods for accomplishing
+ * whatever business logic anyone accessing the plugin might want to use.
+ *
+ * For example, an image manipulation plugin might have a "process" method that
+ * takes a known input, probably an image file, and returns the processed
+ * version of the file.
+ *
+ * In our case we'll define methods for accessing the human readable description
+ * of a sandwich and the number of calories per serving. As well as a method for
+ * ordering a sandwich.
+ */
+
 namespace Drupal\plugin_type_example;
 
 /**
@@ -15,4 +34,22 @@ interface SandwichInterface {
    */
   public function description();
 
+  /**
+   * Provide the number of calories per serving for the sandwich.
+   *
+   * @return float
+   *   The number of calories per serving.
+   */
+  public function calories();
+
+  /**
+   * Place an order for a sandwich.
+   *
+   * @param array $extras
+   *   An array of extra ingredients to include with this sandwich.
+   *
+   * @return string
+   *   Description of the sandwich that was just ordered.
+   */
+  public function order(array $extras);
 }
diff --git a/plugin_type_example/src/SandwichPluginManager.php b/plugin_type_example/src/SandwichPluginManager.php
index 4a457c2..9e68b9a 100644
--- a/plugin_type_example/src/SandwichPluginManager.php
+++ b/plugin_type_example/src/SandwichPluginManager.php
@@ -5,15 +5,23 @@ namespace Drupal\plugin_type_example;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\plugin_type_example\SandwichInterface;
+use Drupal\plugin_type_example\Annotation\Sandwich;
 
 /**
- * Manages sandwich plugins.
+ * A plugin manager for sandwich plugins.
  *
  * The SandwichPluginManager class extends the DefaultPluginManager to provide
- * a way to manage sandwich plugins.
+ * a way to manage sandwich plugins. A plugin manager defines a new plugin type
+ * and how instances of any plugin of that type will be discovered, instantiated
+ * and more.
  *
- * As well as this class definition, we need to declare our plugin manager class
- * as a service, in the plugin_type_example.services.yml file.
+ * Using the DefaultPluginManager as a starting point sets up our sandwich
+ * plugin type to use annotated discovery.
+ *
+ * The plugin manager is also declared as a service in
+ * plugin_type_example.services.yml so that it can be easily accessed and used
+ * anytime we need to work with sandwich plugins.
  */
 class SandwichPluginManager extends DefaultPluginManager {
 
@@ -30,17 +38,19 @@ class SandwichPluginManager extends DefaultPluginManager {
    */
   public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
     // We replace the $subdir parameter with our own value.
-    // This tells the plugin system to look for plugins in the 'Plugin/Sandwich'
-    // subfolder inside modules' 'src' folder.
+    // This tells the plugin manager to look for Sandwich plugins in the
+    // 'src/Plugin/Sandwich' subdirectory of any enabled modules. This also
+    // serves to define the PSR-4 subnamespace in which sandwich plugins will
+    // live. Example: Drupal\{module_name}\Plugin\Sandwich\MySandwichPlugin;
     $subdir = 'Plugin/Sandwich';
 
-    // The name of the interface that plugins should adhere to.  Drupal will
-    // enforce this as a requirement.  If a plugin does not implement this
+    // The name of the interface that plugins should adhere to. Drupal will
+    // enforce this as a requirement. If a plugin does not implement this
     // interface, than Drupal will throw an error.
-    $plugin_interface = 'Drupal\plugin_type_example\SandwichInterface';
+    $plugin_interface = SandwichInterface::class;
 
     // The name of the annotation class that contains the plugin definition.
-    $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin';
+    $plugin_definition_annotation_name = Sandwich::class;
 
     parent::__construct($subdir, $namespaces, $module_handler, $plugin_interface, $plugin_definition_annotation_name);
 
@@ -50,15 +60,17 @@ class SandwichPluginManager extends DefaultPluginManager {
     // see plugin_type_example_sandwich_info_alter().
     $this->alterInfo('sandwich_info');
 
-    // This sets the caching method for our plugin definitions.  Plugin
-    // definitions are cached using the provided cache backend.  For our
-    // Sandwich plugin type, we've specified the @cache.default service be used
-    // in the plugin_type_example.services.yml file.  The second argument is a
-    // cache key prefix.  Out of the box Drupal with the default cache backend
-    // setup will store our plugin definition in the cache_default table using
-    // the sandwich_info key.  All that is implementation details however,
-    // all we care about it that caching for our plugin definition is taken
-    // care of by this call.
+    // This sets the caching method for our plugin definitions. Plugin
+    // definitions are discovered by examining the $subdir defined above, for
+    // any classes with an $plugin_definition_annotation_name. The annotations
+    // are read, and then the resulting data is cached using the provided cache
+    // backend. For our Sandwich plugin type, we've specified the @cache.default
+    // service be used in the plugin_type_example.services.yml file. The second
+    // argument is a cache key prefix. Out of the box Drupal with the default
+    // cache backend setup will store our plugin definition in the cache_default
+    // table using the sandwich_info key. All that is implementation details
+    // however, all we care about it that caching for our plugin definition is
+    // taken care of by this call.
     $this->setCacheBackend($cache_backend, 'sandwich_info');
   }
 
diff --git a/plugin_type_example/src/Tests/PluginTypeExampleTest.php b/plugin_type_example/src/Tests/PluginTypeExampleTest.php
index a9a4d54..90f75d2 100644
--- a/plugin_type_example/src/Tests/PluginTypeExampleTest.php
+++ b/plugin_type_example/src/Tests/PluginTypeExampleTest.php
@@ -36,15 +36,15 @@ class PluginTypeExampleTest extends WebTestBase {
 
     $sandwich_plugin_definitions = $manager->getDefinitions();
 
-    // Check we have only one sandwich plugin.
+    // Ensure we have two sandwich plugins defined.
     $this->assertEqual(count($sandwich_plugin_definitions), 2, 'There are two sandwich plugins defined.');
 
     // Check some of the properties of the ham sandwich plugin definition.
     $sandwich_plugin_definition = $sandwich_plugin_definitions['ham_sandwich'];
-    $this->assertEqual($sandwich_plugin_definition['calories'], 500, 'The ham sandwich plugin definition\'s calories property is set.');
+    $this->assertEqual($sandwich_plugin_definition['calories'], 426, 'The ham sandwich plugin definition\'s calories property is set.');
 
     // Check the alter hook fired and changed a property.
-    $this->assertEqual($sandwich_plugin_definition['foobar'], 'We have altered this in the alter hook', 'The ham sandwich plugin definition\'s foobar property is set, and was correctly altered by the plugin info alter hook.');
+    $this->assertIdentical($sandwich_plugin_definition['description'], 'Ham, mustard, ROCKET, sun-dried tomatoes.', 'The ham sandwich plugin definition\'s description property is set, and was correctly altered by the plugin info alter hook.');
 
     // Create an instance of the ham sandwich plugin to check it works.
     $plugin = $manager->createInstance('ham_sandwich', array('of' => 'configuration values'));
@@ -63,7 +63,7 @@ class PluginTypeExampleTest extends WebTestBase {
     $this->assertText(t('ham_sandwich'), 'The plugin ID is output.');
 
     // Check we see the plugin description.
-    $this->assertText(t('Ham, mustard, rocket, sun-dried tomatoes.'), 'The plugin description is output.');
+    $this->assertText(t('Ham, mustard, ROCKET, sun-dried tomatoes.'), 'The plugin description is output.');
   }
 
 }
diff --git a/simpletest_example/simpletest_example.info.yml b/simpletest_example/simpletest_example.info.yml
index 94f5826..6123ca9 100644
--- a/simpletest_example/simpletest_example.info.yml
+++ b/simpletest_example/simpletest_example.info.yml
@@ -1,7 +1,7 @@
 name: SimpleTest Example
 type: module
 hidden: false
-description: 'Demonstrates some SimpleTest-based tests in Drupal 8.'
+description: 'Some demonstrations of SimpleTest-based testing in Drupal 8.'
 package: Example modules
 core: 8.x
 # We have to be as explicit as possible about the dependencies for this module.
diff --git a/tests/src/Functional/ExamplesBrowserTestBase.php b/tests/src/Functional/ExamplesBrowserTestBase.php
deleted file mode 100644
index 00cbfec..0000000
--- a/tests/src/Functional/ExamplesBrowserTestBase.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<?php
-
-namespace Drupal\Tests\examples\Functional;
-
-use Drupal\Tests\BrowserTestBase;
-
-/**
- * A standardized base class for Examples tests.
- *
- * Use this base class if the Examples module being tested requires menus, local
- * tasks, and actions.
- */
-abstract class ExamplesBrowserTestBase extends BrowserTestBase {
-
-  /**
-   * Modules to install.
-   *
-   * @var array
-   */
-  public static $modules = array('examples', 'block');
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-    // Add the system menu blocks to appropriate regions.
-    $this->setupExamplesMenus();
-  }
-
-  /**
-   * Set up menus and tasks in their regions.
-   *
-   * Since menus and tasks are now blocks, we're required to explicitly set them
-   * to regions. This method standardizes the way we do that for Examples.
-   *
-   * Note that subclasses must explicitly declare that the block module is a
-   * dependency.
-   */
-  protected function setupExamplesMenus() {
-    $this->drupalPlaceBlock('system_menu_block:tools', ['region' => 'primary_menu']);
-    $this->drupalPlaceBlock('local_tasks_block', ['region' => 'secondary_menu']);
-    $this->drupalPlaceBlock('local_actions_block', ['region' => 'content']);
-    $this->drupalPlaceBlock('page_title_block', ['region' => 'content']);
-  }
-
-}
diff --git a/tests/src/Functional/ExamplesTest.php b/tests/src/Functional/ExamplesTest.php
deleted file mode 100644
index 9536000..0000000
--- a/tests/src/Functional/ExamplesTest.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-namespace Drupal\Tests\examples\Functional;
-
-/**
- * Minimal test case for the examples module.
- *
- * @group examples
- *
- * @ingroup examples
- */
-class ExamplesTest extends ExamplesBrowserTestBase {
-
-  /**
-   * Modules to install.
-   *
-   * @var array
-   */
-  public static $modules = array('examples', 'toolbar');
-
-  /**
-   * Test whether the module was installed.
-   */
-  public function testExamples() {
-    $assert = $this->assertSession();
-
-    // Verify that the toolbar tab and tray are showing and functioning.
-    $user = $this->drupalCreateUser(array('access toolbar'));
-    $this->drupalLogin($user);
-
-    // Check for the 'Examples' tab.
-    $this->drupalGet('');
-    $assert->statusCodeEquals(200);
-
-    // Assert that the toolbar tab registered by examples is present.
-    $assert->linkExists('Examples');
-
-    // Assert that the toolbar tab registered by examples is present.
-    $this->assertEquals(
-      1,
-      \count($this->xpath('//nav/div/a[@data-toolbar-tray="toolbar-item-examples-tray"]')),
-      'Found the Examples toolbar tab.'
-    );
-
-    // Assert that the toolbar tray registered by examples is present.
-    $this->assertEquals(
-      1,
-      \count($this->xpath('//nav/div/div[@data-toolbar-tray="toolbar-item-examples-tray"]')),
-      'Found the Examples toolbar tray.'
-    );
-    // Assert that PHPUnit link does not appears in the tray.
-    $assert->linkNotExists('PHPUnit example');
-    $assert->pageTextNotContains('<li class="phpunit-example">');
-
-    // Install phpunit_example and see if it appears in the toolbar. We use
-    // phpunit_example because it's very light-weight.
-    $this->container->get('module_installer')->install(array('phpunit_example'), TRUE);
-    // SimpleTest needs for us to reset all the caches.
-    $this->resetAll();
-
-    // Verify that PHPUnit appears in the tray.
-    $this->drupalGet('');
-    $assert->linkExists('PHPUnit example');
-    // Assert that the PHPUnit tray item is present.
-    $this->assertEquals(
-      1,
-      \count($this->xpath('//nav/div/div/nav/ul/li[@class="phpunit-example"]')),
-      'Found the PHPUnit Example tray item.'
-    );
-
-  }
-
-}
diff --git a/tests/src/Kernel/DescriptionTraitTest.php b/tests/src/Kernel/DescriptionTraitTest.php
deleted file mode 100644
index 817fcb7..0000000
--- a/tests/src/Kernel/DescriptionTraitTest.php
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-namespace Drupal\Tests\examples\Kernel;
-
-use Drupal\KernelTests\KernelTestBase;
-use Drupal\examples_description_test\Controller\SampleExampleController;
-
-/**
- * Test of the Description Trait.
- *
- * @group examples
- */
-class DescriptionTraitTest extends KernelTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = ['examples', 'examples_description_test'];
-
-  /**
-   * Make sure that the trait finds the template file and renders it.
-   */
-  public function testTemplateFile() {
-    $sample_controller = SampleExampleController::create($this->container);
-    // We want to test ::getDescriptionTemplatePath(), which is a protected
-    // method. Use a little of the Old Black Reflection Magic.
-    $ref_get_path = new \ReflectionMethod($sample_controller, 'getDescriptionTemplatePath');
-    $ref_get_path->setAccessible(TRUE);
-    $this->assertFileExists($ref_get_path->invoke($sample_controller));
-    // And get our render output.
-    $render_array = $sample_controller->description();
-    // We cast to string, since renderPlain() returns a markup object.
-    $output = (string) $this->container->get('renderer')->renderPlain($render_array);
-    // Did the template load?
-    $this->assertContains('Template loaded!', $output);
-    // Were the variables resolved correctly?
-    $this->assertContains('Used in module: examples_description_test.', $output);
-    $this->assertContains('Our slogan for today: We aim to please.', $output);
-  }
-
-}
diff --git a/tests/src/KernelTests/DescriptionTraitTest.php b/tests/src/KernelTests/DescriptionTraitTest.php
new file mode 100644
index 0000000..eb64a54
--- /dev/null
+++ b/tests/src/KernelTests/DescriptionTraitTest.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Drupal\Tests\examples\KernelTests;
+
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\examples_description_test\Controller\SampleExampleController;
+
+/**
+ * Test of the Description Trait.
+ *
+ * @group examples
+ */
+class DescriptionTraitTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['examples', 'examples_description_test'];
+
+  /**
+   * Make sure that the trait finds the template file and renders it.
+   */
+  public function testTemplateFile() {
+    $sample_controller = SampleExampleController::create($this->container);
+    // We want to test ::getDescriptionTemplatePath(), which is a protected
+    // method. Use a little of the Old Black Reflection Magic.
+    $ref_get_path = new \ReflectionMethod($sample_controller, 'getDescriptionTemplatePath');
+    $ref_get_path->setAccessible(TRUE);
+    $this->assertFileExists($ref_get_path->invoke($sample_controller));
+    // And get our render output.
+    $render_array = $sample_controller->description();
+    // We cast to string, since renderPlain() returns a markup object.
+    $output = (string) $this->container->get('renderer')->renderPlain($render_array);
+    // Did the template load?
+    $this->assertContains('Template loaded!', $output);
+    // Were the variables resolved correctly?
+    $this->assertContains('Used in module: examples_description_test.', $output);
+    $this->assertContains('Our slogan for today: We aim to please.', $output);
+  }
+
+}
diff --git a/tour_example/tour_example.info.yml b/tour_example/tour_example.info.yml
index 6ea4d0d..328ca75 100644
--- a/tour_example/tour_example.info.yml
+++ b/tour_example/tour_example.info.yml
@@ -1,6 +1,6 @@
 name: 'Tour example'
 type: module
-description: 'Demonstrates how to create a tour in Drupal 8.'
+description: 'An example of how to create a tour in Drupal 8.'
 package: Example modules
 core: 8.x
 dependencies:
