diff --git a/core/includes/common.inc b/core/includes/common.inc
index aea8529060a7aece2f29e36482a7d806e8186f6b..6879def87811994fd8e4c3333369b389fc282ee6 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1,8 +1,9 @@
 <?php
 
-use Drupal\Component\Utility\NestedArray;
+use Symfony\Component\DependencyInjection\Container;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Template\Attribute;
@@ -6829,6 +6830,7 @@ function drupal_flush_all_caches() {
   // Rebuild the menu router based on all rebuilt data.
   // Important: This rebuild must happen last, so the menu router is guaranteed
   // to be based on up to date information.
+  drupal_container()->get('router.builder')->rebuild();
   menu_router_rebuild();
 
   // Re-initialize the maintenance theme, if the current request attempted to
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 4dca80430891c7bb5ddcaf6ea603a3fa2b167fb2..c1abfe55ca69b4369b390d369c36775ac8017df6 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -15,6 +15,8 @@
 use Symfony\Component\HttpKernel\Bundle\Bundle;
 use Symfony\Component\DependencyInjection\Compiler\PassConfig;
 
+use Drupal\Core\Database\Database;
+
 /**
  * Bundle class for mandatory core services.
  *
@@ -54,12 +56,25 @@ public function build(ContainerBuilder $container) {
       ->addArgument('slave');
     $container->register('typed_data', 'Drupal\Core\TypedData\TypedDataManager');
 
+    $container->register('router.dumper', '\Drupal\Core\Routing\MatcherDumper')
+      ->addArgument(new Reference('database'));
+    $container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder')
+      ->addArgument(new Reference('router.dumper'));
+
     // @todo Replace below lines with the commented out block below it when it's
     //   performant to do so: http://drupal.org/node/1706064.
     $dispatcher = $container->get('dispatcher');
-    $matcher = new \Drupal\Core\LegacyUrlMatcher();
+    $matcher = new \Drupal\Core\Routing\ChainMatcher();
+    $matcher->add(new \Drupal\Core\LegacyUrlMatcher());
+
+    $nested = new \Drupal\Core\Routing\NestedMatcher();
+    $nested->setInitialMatcher(new \Drupal\Core\Routing\PathMatcher(Database::getConnection()));
+    $nested->addPartialMatcher(new \Drupal\Core\Routing\HttpMethodMatcher());
+    $nested->setFinalMatcher(new \Drupal\Core\Routing\FirstEntryFinalMatcher());
+    $matcher->add($nested, 5);
+
     $content_negotation = new \Drupal\Core\ContentNegotiation();
-    $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\RouterListener($matcher));
+    $dispatcher->addSubscriber(new \Symfony\Component\HttpKernel\EventListener\RouterListener($matcher));
     $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\ViewSubscriber($content_negotation));
     $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\AccessSubscriber());
     $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\MaintenanceModeSubscriber());
@@ -69,6 +84,7 @@ public function build(ContainerBuilder $container) {
     $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\FinishResponseSubscriber());
     $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\RequestCloseSubscriber());
     $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\ConfigGlobalOverrideSubscriber());
+    $dispatcher->addSubscriber(new \Drupal\Core\EventSubscriber\RouteProcessorSubscriber());
     $container->set('content_negotiation', $content_negotation);
     $dispatcher->addSubscriber(\Drupal\Core\ExceptionController::getExceptionListener($container));
 
diff --git a/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
index f088f7ef2e2e7373761dd317e7bdefe406d6ccba..7009eff9154ed3128de8e40cbbe9491d8f5c0894 100644
--- a/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/LegacyControllerSubscriber.php
@@ -36,11 +36,18 @@ class LegacyControllerSubscriber implements EventSubscriberInterface {
    *   The Event to process.
    */
   public function onKernelControllerLegacy(FilterControllerEvent $event) {
-    $router_item = $event->getRequest()->attributes->get('drupal_menu_item');
+    $request = $event->getRequest();
+    $router_item = $request->attributes->get('drupal_menu_item');
     $controller = $event->getController();
 
     // This BC logic applies only to functions. Otherwise, skip it.
     if (is_string($controller) && function_exists($controller)) {
+      // Flag this as a legacy request.  We need to use this for subrequest
+      // handling so that we can treat older page callbacks and new routes
+      // differently.
+      // @todo Remove this line as soon as possible.
+      $request->attributes->set('_legacy', TRUE);
+
       $new_controller = function() use ($router_item) {
         return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
       };
diff --git a/core/lib/Drupal/Core/EventSubscriber/RouteProcessorSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/RouteProcessorSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..61c778848afcea4161ff768d41965296d8d340bd
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/RouteProcessorSubscriber.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\EventSubscriber\RouteProcessorSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
+use Symfony\Component\HttpKernel\Log\LoggerInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+
+/**
+ * Listener to process request controller information.
+ */
+class RouteProcessorSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Sets a default controller for a route if one was not specified.
+   *
+   * @param Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   *   Event that is created to create a response for a request.
+   */
+  public function onRequestSetController(GetResponseEvent $event) {
+    $request = $event->getRequest();
+
+    if (!$request->attributes->has('_controller') && $request->attributes->has('_content')) {
+      $request->attributes->set('_controller', '\Drupal\Core\HtmlPageController::content');
+    }
+  }
+
+  /**
+   * Registers the methods in this class that should be listeners.
+   *
+   * @return array
+   *   An array of event listener definitions.
+   */
+  static function getSubscribedEvents() {
+    // The RouterListener has priority 32, and we need to run after that.
+    $events[KernelEvents::REQUEST][] = array('onRequestSetController', 30);
+
+    return $events;
+  }
+}
diff --git a/core/lib/Drupal/Core/EventSubscriber/RouterListener.php b/core/lib/Drupal/Core/EventSubscriber/RouterListener.php
deleted file mode 100644
index 7b158fdf5f656cca04f9d977b1a7afd77db8b32a..0000000000000000000000000000000000000000
--- a/core/lib/Drupal/Core/EventSubscriber/RouterListener.php
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\Core\EventSubscriber\RouterListener.
- */
-
-namespace Drupal\Core\EventSubscriber;
-
-use Symfony\Component\HttpKernel\HttpKernelInterface;
-use Symfony\Component\HttpKernel\EventListener\RouterListener as SymfonyRouterListener;
-use Symfony\Component\HttpKernel\Event\GetResponseEvent;
-use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
-use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
-use Symfony\Component\HttpKernel\Log\LoggerInterface;
-use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
-use Symfony\Component\Routing\Exception\ResourceNotFoundException;
-
-/**
- * Drupal-specific Router listener.
- *
- * This is the bridge from the kernel to the UrlMatcher.
- */
-class RouterListener extends SymfonyRouterListener {
-
-  /**
-   * The Matcher object for this listener.
-   *
-   * This property is private in the base class, so we have to hack around it.
-   *
-   * @var Symfony\Component\Router\Matcher\UrlMatcherInterface
-   */
-  protected $urlMatcher;
-
-  /**
-   * The Logging object for this listener.
-   *
-   * This property is private in the base class, so we have to hack around it.
-   *
-   * @var Symfony\Component\HttpKernel\Log\LoggerInterface
-   */
-  protected $logger;
-
-  public function __construct(UrlMatcherInterface $urlMatcher, LoggerInterface $logger = null) {
-    parent::__construct($urlMatcher, $logger);
-    $this->urlMatcher = $urlMatcher;
-    $this->logger = $logger;
-  }
-
-  /**
-   * {@inheritdoc}
-   *
-   * This method is nearly identical to the parent, except it passes the
-   * $request->attributes->get('system_path') variable to the matcher.
-   * That is where Drupal stores its processed, de-aliased, and sanitized
-   * internal path. We also pass the full request object to the URL Matcher,
-   * since we want attributes to be available to the matcher and to controllers.
-   */
-  public function onKernelRequest(GetResponseEvent $event) {
-    $request = $event->getRequest();
-
-    if (HttpKernelInterface::MASTER_REQUEST === $event->getRequestType()) {
-      $this->urlMatcher->getContext()->fromRequest($request);
-      $this->urlMatcher->setRequest($request);
-    }
-
-    if ($request->attributes->has('_controller')) {
-      // Routing is already done.
-      return;
-    }
-
-    // Add attributes based on the path info (routing).
-    try {
-      $parameters = $this->urlMatcher->match($request->attributes->get('system_path'));
-
-      if (null !== $this->logger) {
-          $this->logger->info(sprintf('Matched route "%s" (parameters: %s)', $parameters['_route'], $this->parametersToString($parameters)));
-      }
-
-      $request->attributes->add($parameters);
-      unset($parameters['_route']);
-      unset($parameters['_controller']);
-      $request->attributes->set('_route_params', $parameters);
-    }
-    catch (ResourceNotFoundException $e) {
-      $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo());
-
-      throw new NotFoundHttpException($message, $e);
-    }
-    catch (MethodNotAllowedException $e) {
-      $message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getPathInfo(), strtoupper(implode(', ', $e->getAllowedMethods())));
-
-      throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e);
-    }
-  }
-}
diff --git a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
index 637d48d5f60941cab3203944d79e444d3538151b..e88525e7ed4fbc3c09a480b88bdeef69d771eece 100644
--- a/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/ViewSubscriber.php
@@ -10,6 +10,7 @@
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
 use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
@@ -46,13 +47,43 @@ public function onView(GetResponseForControllerResultEvent $event) {
 
     $request = $event->getRequest();
 
-    $method = 'on' . $this->negotiation->getContentType($request);
-
-    if (method_exists($this, $method)) {
-      $event->setResponse($this->$method($event));
+    // For a master request, we process the result and wrap it as needed.
+    // For a subrequest, all we want is the string value.  We assume that
+    // is just an HTML string from a controller, so wrap that into a response
+    // object.  The subrequest's response will get dissected and placed into
+    // the larger page as needed.
+    if ($event->getRequestType() == HttpKernelInterface::MASTER_REQUEST) {
+      $method = 'on' . $this->negotiation->getContentType($request);
+
+      if (method_exists($this, $method)) {
+        $event->setResponse($this->$method($event));
+      }
+      else {
+        $event->setResponse(new Response('Unsupported Media Type', 415));
+      }
+    }
+    elseif ($request->attributes->get('_legacy')) {
+      // This is an old hook_menu-based subrequest, which means we assume
+      // the body is supposed to be the complete page.
+      $page_result = $event->getControllerResult();
+      if (!is_array($page_result)) {
+        $page_result = array(
+          '#markup' => $page_result,
+        );
+      }
+      $event->setResponse(new Response(drupal_render_page($page_result)));
     }
     else {
-      $event->setResponse(new Response('Unsupported Media Type', 415));
+      // This is a new-style Symfony-esque subrequest, which means we assume
+      // the body is not supposed to be a complete page but just a page
+      // fragment.
+      $page_result = $event->getControllerResult();
+      if (!is_array($page_result)) {
+        $page_result = array(
+          '#markup' => $page_result,
+        );
+      }
+      $event->setResponse(new Response(drupal_render($page_result)));
     }
   }
 
diff --git a/core/lib/Drupal/Core/HtmlPageController.php b/core/lib/Drupal/Core/HtmlPageController.php
new file mode 100644
index 0000000000000000000000000000000000000000..7d14ac000973389259a2d411e0df2a91ae1f312f
--- /dev/null
+++ b/core/lib/Drupal/Core/HtmlPageController.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\HtmlPageController.
+ */
+
+namespace Drupal\Core;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Default controller for most HTML pages.
+ */
+class HtmlPageController implements ContainerAwareInterface {
+
+  /**
+   * The injection container for this object.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  /**
+   * Injects the service container used by this object.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+   *   The service container this object should use.
+   */
+  public function setContainer(ContainerInterface $container = NULL) {
+    $this->container = $container;
+  }
+
+  /**
+   * Controller method for generic HTML pages.
+   *
+   * @param Request $request
+   *   The request object.
+   * @param callable $_content
+   *   The body content callable that contains the body region of this page.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   A response object.
+   */
+  public function content(Request $request, $_content) {
+
+    // @todo When we have a Generator, we can replace the forward() call with
+    // a render() call, which would handle ESI and hInclude as well.  That will
+    // require an _internal route.  For examples, see:
+    // https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Resources/config/routing/internal.xml
+    // https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Controller/InternalController.php
+    $attributes = $request->attributes;
+    $controller = $_content;
+
+    // We need to clean off the derived information and such so that the
+    // subrequest can be processed properly without leaking data through.
+    $attributes->remove('system_path');
+    $attributes->remove('_content');
+
+    $response = $this->container->get('http_kernel')->forward($controller, $attributes->all(), $request->query->all());
+
+    $page_content = $response->getContent();
+
+    return new Response(drupal_render_page($page_content));
+  }
+}
diff --git a/core/lib/Drupal/Core/LegacyUrlMatcher.php b/core/lib/Drupal/Core/LegacyUrlMatcher.php
index 8828f36d27b4438d6ac587159aef9dba4499ab67..48987a9291f8f5e015d85f281b5d56a19d433eb4 100644
--- a/core/lib/Drupal/Core/LegacyUrlMatcher.php
+++ b/core/lib/Drupal/Core/LegacyUrlMatcher.php
@@ -9,13 +9,14 @@
 
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Routing\Exception\ResourceNotFoundException;
-use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
+use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
+use Symfony\Component\Routing\RequestContextAwareInterface;
 use Symfony\Component\Routing\RequestContext;
 
 /**
  * UrlMatcher matches URL based on a set of routes.
  */
-class LegacyUrlMatcher implements UrlMatcherInterface {
+class LegacyUrlMatcher implements RequestMatcherInterface, RequestContextAwareInterface {
 
   /**
    * The request context for this matcher.
@@ -98,8 +99,8 @@ public function getRequest() {
    *
    * @api
    */
-  public function match($pathinfo) {
-    if ($router_item = $this->matchDrupalItem($pathinfo)) {
+  public function matchRequest(Request $request) {
+    if ($router_item = $this->matchDrupalItem($request->attributes->get('system_path'))) {
       $ret = $this->convertDrupalItem($router_item);
       // Stash the router item in the attributes while we're transitioning.
       $ret['drupal_menu_item'] = $router_item;
diff --git a/core/lib/Drupal/Core/Routing/ChainMatcher.php b/core/lib/Drupal/Core/Routing/ChainMatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..23410bfe3474d8542275fc16a856328e3514a71c
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/ChainMatcher.php
@@ -0,0 +1,165 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\ChainMatcher.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+use Symfony\Component\Routing\RequestContextAwareInterface;
+use Symfony\Component\Routing\RequestContext;
+
+/**
+ * Aggregates multiple matchers together in series.
+ *
+ * The RequestContext is entirely unused. It's included only to satisfy the
+ * interface needed for RouterListener.  Hopefully we can remove it later.
+ */
+class ChainMatcher implements RequestMatcherInterface, RequestContextAwareInterface {
+
+  /**
+   * Array of RequestMatcherInterface objects to be checked in order.
+   *
+   * @var array
+   */
+  protected $matchers = array();
+
+  /**
+   * Array of RequestMatcherInterface objects, sorted.
+   *
+   * @var type
+   */
+  protected $sortedMatchers = array();
+
+  /**
+   * The request context for this matcher.
+   *
+   * This is unused.  It's just to satisfy the interface.
+   *
+   * @var Symfony\Component\Routing\RequestContext
+   */
+  protected $context;
+
+  /**
+   * Constructor.
+   */
+  public function __construct() {
+    // We will not actually use this object, but it's needed to conform to
+    // the interface.
+    $this->context = new RequestContext();
+  }
+
+  /**
+   * Sets the request context.
+   *
+   * This method is just to satisfy the interface, and is largely vestigial.
+   * The request context object does not contain the information we need, so
+   * we will use the original request object.
+   *
+   * @param Symfony\Component\Routing\RequestContext $context
+   *   The context.
+   */
+  public function setContext(RequestContext $context) {
+    $this->context = $context;
+  }
+
+  /**
+   * Gets the request context.
+   *
+   * This method is just to satisfy the interface, and is largely vestigial.
+   * The request context object does not contain the information we need, so
+   * we will use the original request object.
+   *
+   * @return Symfony\Component\Routing\RequestContext
+   *   The context.
+   */
+  public function getContext() {
+    return $this->context;
+  }
+
+  /**
+   * Matches a request against all queued matchers.
+   *
+   * @param Request $request The request to match
+   *
+   * @return array An array of parameters
+   *
+   * @throws \Symfony\Component\Routing\Exception\ResourceNotFoundException
+   *   If no matching resource could be found
+   * @throws \Symfony\Component\Routing\Exception\MethodNotAllowedException
+   *   If a matching resource was found but the request method is not allowed
+   */
+  public function matchRequest(Request $request) {
+    $methodNotAllowed = null;
+
+    foreach ($this->all() as $matcher) {
+      try {
+        return $matcher->matchRequest($request);
+      } catch (ResourceNotFoundException $e) {
+        // Needs special care
+      } catch (MethodNotAllowedException $e) {
+        $methodNotAllowed = $e;
+      }
+    }
+
+    throw $methodNotAllowed ?: new ResourceNotFoundException("None of the matchers in the chain matched this request.");
+  }
+
+  /**
+    * Adds a Matcher to the index.
+    *
+    * @param MatcherInterface $matcher
+    *   The matcher to add.
+    * @param int $priority
+    *   (optional) The priority of the matcher. Higher number matchers will be checked
+    *   first. Default to 0.
+    */
+  public function add(RequestMatcherInterface $matcher, $priority = 0) {
+    if (empty($this->matchers[$priority])) {
+      $this->matchers[$priority] = array();
+    }
+
+    $this->matchers[$priority][] = $matcher;
+    $this->sortedMatchers = array();
+  }
+
+  /**
+    * Sorts the matchers and flattens them.
+    *
+    * @return array
+    *   An array of RequestMatcherInterface objects.
+    */
+  public function all() {
+    if (empty($this->sortedMatchers)) {
+      $this->sortedMatchers = $this->sortMatchers();
+    }
+
+    return $this->sortedMatchers;
+  }
+
+  /**
+    * Sort matchers by priority.
+    *
+    * The highest priority number is the highest priority (reverse sorting).
+    *
+    * @return \Symfony\Component\Routing\RequestMatcherInterface[]
+    *   An array of Matcher objects in the order they should be used.
+    */
+  protected function sortMatchers() {
+    $sortedMatchers = array();
+    krsort($this->matchers);
+
+    foreach ($this->matchers as $matchers) {
+      $sortedMatchers = array_merge($sortedMatchers, $matchers);
+    }
+
+    return $sortedMatchers;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/CompiledRoute.php b/core/lib/Drupal/Core/Routing/CompiledRoute.php
new file mode 100644
index 0000000000000000000000000000000000000000..c5cdde8a994261910ecb7dc17121b7868273b0da
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/CompiledRoute.php
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\CompiledRoute.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\Routing\Route;
+
+/**
+ * Description of CompiledRoute
+ */
+class CompiledRoute {
+
+  /**
+   * The fitness of this route.
+   *
+   * @var int
+   */
+  protected $fit;
+
+  /**
+   * The pattern outline of this route.
+   *
+   * @var string
+   */
+  protected $patternOutline;
+
+  /**
+   * The number of parts in the path of this route.
+   *
+   * @var int
+   */
+  protected $numParts;
+
+  /**
+   * The Route object of which this object is the compiled version.
+   *
+   * @var Symfony\Component\Routing\Route
+   */
+  protected $route;
+
+  /**
+   * The regular expression to match placeholders out of this path.
+   *
+   * @var string
+   */
+  protected $regex;
+
+  /**
+   * Constructs a new CompiledRoute object.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   A original Route instance.
+   * @param int $fit
+   *   The fitness of the route.
+   * @param string $fit
+   *   The pattern outline for this route.
+   * @param int $num_parts
+   *   The number of parts in the path.
+   * @param string $regex
+   *   The regular expression to match placeholders out of this path.
+   */
+  public function __construct(Route $route, $fit, $pattern_outline, $num_parts, $regex) {
+    $this->route = $route;
+    $this->fit = $fit;
+    $this->patternOutline = $pattern_outline;
+    $this->numParts = $num_parts;
+    $this->regex = $regex;
+  }
+
+  /**
+   * Returns the fit of this route.
+   *
+   * See RouteCompiler for a definition of how the fit is calculated.
+   *
+   * @return int
+   *   The fit of the route.
+   */
+  public function getFit() {
+    return $this->fit;
+  }
+
+  /**
+   * Returns the number of parts in this route's path.
+   *
+   * The string "foo/bar/baz" has 3 parts, regardless of how many of them are
+   * placeholders.
+   *
+   * @return int
+   *   The number of parts in the path.
+   */
+  public function getNumParts() {
+    return $this->numParts;
+  }
+
+  /**
+   * Returns the pattern outline of this route.
+   *
+   * The pattern outline of a route is the path pattern of the route, but
+   * normalized such that all placeholders are replaced with %.
+   *
+   * @return string
+   *   The normalized path pattern.
+   */
+  public function getPatternOutline() {
+    return $this->patternOutline;
+  }
+
+  /**
+   * Returns the placeholder regex.
+   *
+   * @return string
+   *   The regex to locate placeholders in this pattern.
+   */
+  public function getRegex() {
+    return $this->regex;
+  }
+
+  /**
+   * Returns the Route instance.
+   *
+   * @return Route
+   *   A Route instance.
+   */
+  public function getRoute() {
+    return $this->route;
+  }
+
+  /**
+   * Returns the pattern.
+   *
+   * @return string
+   *   The pattern.
+   */
+  public function getPattern() {
+    return $this->route->getPattern();
+  }
+
+  /**
+   * Returns the options.
+   *
+   * @return array
+   *   The options.
+   */
+  public function getOptions() {
+    return $this->route->getOptions();
+  }
+
+  /**
+   * Returns the defaults.
+   *
+   * @return array
+   *   The defaults.
+   */
+  public function getDefaults() {
+    return $this->route->getDefaults();
+  }
+
+  /**
+   * Returns the requirements.
+   *
+   * @return array
+   *   The requirements.
+   */
+  public function getRequirements() {
+    return $this->route->getRequirements();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/FinalMatcherInterface.php b/core/lib/Drupal/Core/Routing/FinalMatcherInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..8b85c21847aba87d86e82ccf9614163ec55b3886
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/FinalMatcherInterface.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\FinalMatcherInterface.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * A FinalMatcher returns only one route from a collection of candidate routes.
+ */
+interface FinalMatcherInterface {
+
+  /**
+   * Sets the route collection this matcher should use.
+   *
+   * @param \Symfony\Component\Routing\RouteCollection $collection
+   *   The collection against which to match.
+   *
+   * @return \Drupal\Core\Routing\FinalMatcherInterface
+   *   The current matcher.
+   */
+  public function setCollection(RouteCollection $collection);
+
+  /**
+   * Matches a request against multiple routes.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   A Request object against which to match.
+   *
+   * @return array
+   *   An array of parameters.
+   */
+  public function matchRequest(Request $request);
+}
diff --git a/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php b/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..45d0888c51744baa0534c57879c965d05794f77c
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/FirstEntryFinalMatcher.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\FirstEntryFinalMatcher.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Final matcher that simply returns the first item in the remaining routes.
+ *
+ * This class simply matches the first remaining route.
+ */
+class FirstEntryFinalMatcher implements FinalMatcherInterface {
+
+  /**
+   * The RouteCollection this matcher should match against.
+   *
+   * @var RouteCollection
+   */
+  protected $routes;
+
+  /**
+   * Sets the route collection this matcher should use.
+   *
+   * @param \Symfony\Component\Routing\RouteCollection $collection
+   *   The collection against which to match.
+   *
+   * @return \Drupal\Core\Routing\FinalMatcherInterface
+   *   The current matcher.
+   */
+  public function setCollection(RouteCollection $collection) {
+    $this->routes = $collection;
+
+    return $this;
+  }
+
+  /**
+   * Implements Drupal\Core\Routing\FinalMatcherInterface::matchRequest().
+   */
+  public function matchRequest(Request $request) {
+    // Return whatever the first route in the collection is.
+    foreach ($this->routes as $name => $route) {
+      $path = '/' . $request->attributes->get('system_path');
+
+      $route->setOption('compiler_class', '\Drupal\Core\Routing\RouteCompiler');
+      $compiled = $route->compile();
+
+      preg_match($compiled->getRegex(), $path, $matches);
+
+      return array_merge($this->mergeDefaults($matches, $route->getDefaults()), array('_route' => $name));
+    }
+  }
+
+  /**
+   * Get merged default parameters.
+   *
+   * @param array $params
+   *  The parameters.
+   * @param array $defaults
+   *   The defaults.
+   *
+   * @return array
+   *   Merged default parameters.
+   */
+  protected function mergeDefaults($params, $defaults) {
+    $parameters = $defaults;
+    foreach ($params as $key => $value) {
+      if (!is_int($key)) {
+        $parameters[$key] = $value;
+      }
+    }
+
+    return $parameters;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/HttpMethodMatcher.php b/core/lib/Drupal/Core/Routing/HttpMethodMatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..5064353b7a7c3cdfd9b502f9c899470e0bd71a95
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/HttpMethodMatcher.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\HttpMethodMatcher.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+
+/**
+ * This class filters routes based on their HTTP Method.
+ */
+class HttpMethodMatcher extends PartialMatcher {
+
+  /**
+   * Matches a request against multiple routes.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   A Request object against which to match.
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   *   A RouteCollection of matched routes.
+   */
+  public function matchRequestPartial(Request $request) {
+    $possible_methods = array();
+
+    $method = $request->getMethod();
+
+    $collection = new RouteCollection();
+
+    foreach ($this->routes->all() as $name => $route) {
+      // _method could be a |-delimited list of allowed methods, or null. If
+      // null, we accept any method.
+      $allowed_methods = array_filter(explode('|', strtoupper($route->getRequirement('_method'))));
+      if (empty($allowed_methods) || in_array($method, $allowed_methods)) {
+        $collection->add($name, $route);
+      }
+      else {
+        // Build a list of methods that would have matched. Note that we only
+        // need to do this if a route doesn't match, because if even one route
+        // passes then we'll never throw the exception that needs this array.
+        $possible_methods += $allowed_methods;
+      }
+    }
+
+    if (!count($collection->all())) {
+      throw new MethodNotAllowedException(array_unique($possible_methods));
+    }
+
+    return $collection;
+  }
+
+}
+
diff --git a/core/lib/Drupal/Core/Routing/InitialMatcherInterface.php b/core/lib/Drupal/Core/Routing/InitialMatcherInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..53bc8e75d6523d6c68c8bf6126e5d6d0772bcf14
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/InitialMatcherInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\InitialMatcherInterface.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * A PartialMatcher works like a UrlMatcher, but will return multiple candidate routes.
+ */
+interface InitialMatcherInterface {
+
+  /**
+   * Matches a request against multiple routes.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   A Request object against which to match.
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   *   A RouteCollection of matched routes.
+   */
+  public function matchRequestPartial(Request $request);
+}
diff --git a/core/lib/Drupal/Core/Routing/MatcherDumper.php b/core/lib/Drupal/Core/Routing/MatcherDumper.php
new file mode 100644
index 0000000000000000000000000000000000000000..48b2d8c1d16ba57fd0fe60d7c87b164fca8aa0b3
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/MatcherDumper.php
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\MatcherDumper.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+use Drupal\Core\Database\Connection;
+
+/**
+ * Dumps Route information to a database table.
+ */
+class MatcherDumper implements MatcherDumperInterface {
+
+  /**
+   * The maximum number of path elements for a route pattern;
+   */
+  const MAX_PARTS = 9;
+
+  /**
+   * The database connection to which to dump route information.
+   *
+   * @var Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * The routes to be dumped.
+   *
+   * @var Symfony\Component\Routing\RouteCollection
+   */
+  protected $routes;
+
+  /**
+   * The name of the SQL table to which to dump the routes.
+   *
+   * @var string
+   */
+  protected $tableName;
+
+  /**
+   * Construct the MatcherDumper.
+   *
+   * @param Drupal\Core\Database\Connection $connection
+   *   The database connection which will be used to store the route
+   *   information.
+   * @param string $table
+   *   (optional) The table to store the route info in. Defaults to 'router'.
+   */
+  public function __construct(Connection $connection, $table = 'router') {
+    $this->connection = $connection;
+
+    $this->tableName = $table;
+  }
+
+  /**
+   * Adds additional routes to be dumped.
+   *
+   * @param Symfony\Component\Routing\RouteCollection $routes
+   *   A collection of routes to add to this dumper.
+   */
+  public function addRoutes(RouteCollection $routes) {
+    if (empty($this->routes)) {
+      $this->routes = $routes;
+    }
+    else {
+      $this->routes->addCollection($routes);
+    }
+  }
+
+  /**
+   * Dumps a set of routes to the router table in the database.
+   *
+   * Available options:
+   * - route_set:  The route grouping that is being dumped. All existing
+   *   routes with this route set will be deleted on dump.
+   * - base_class: The base class name.
+   *
+   * @param array $options
+   *   An array of options.
+   */
+  public function dump(array $options = array()) {
+    $options += array(
+      'route_set' => '',
+    );
+
+    // Convert all of the routes into database records.
+    $insert = $this->connection->insert($this->tableName)->fields(array(
+      'name',
+      'route_set',
+      'fit',
+      'pattern',
+      'pattern_outline',
+      'number_parts',
+      'route',
+    ));
+
+    foreach ($this->routes as $name => $route) {
+      $route->setOption('compiler_class', '\Drupal\Core\Routing\RouteCompiler');
+      $compiled = $route->compile();
+      $values = array(
+        'name' => $name,
+        'route_set' => $options['route_set'],
+        'fit' => $compiled->getFit(),
+        'pattern' => $compiled->getPattern(),
+        'pattern_outline' => $compiled->getPatternOutline(),
+        'number_parts' => $compiled->getNumParts(),
+        'route' => serialize($route),
+      );
+      $insert->values($values);
+    }
+
+    // Delete any old records in this route set first, then insert the new ones.
+    // That avoids stale data. The transaction makes it atomic to avoid
+    // unstable router states due to random failures.
+    $txn = $this->connection->startTransaction();
+
+    $this->connection->delete($this->tableName)
+      ->condition('route_set', $options['route_set'])
+      ->execute();
+
+    $insert->execute();
+
+    // We want to reuse the dumper for multiple route sets, so on dump, flush
+    // the queued routes.
+    $this->routes = NULL;
+
+    // Transaction ends here.
+  }
+
+  /**
+   * Gets the routes to match.
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   *   A RouteCollection instance representing all routes currently in the
+   *   dumper.
+   */
+  public function getRoutes() {
+    return $this->routes;
+  }
+
+  /**
+   * Determines the fitness of the provided path.
+   *
+   * @param string $path
+   *   The path whose fitness we want.
+   *
+   * @return int
+   *   The fitness of the path, as an integer.
+   */
+  public function getFit($path) {
+    $fit = 0;
+
+    $parts = explode('/', $path, static::MAX_PARTS);
+    foreach ($parts as $k => $part) {
+      if (strpos($part, '{') === FALSE) {
+        $fit |=  1 << ($slashes - $k);
+      }
+    }
+
+    return $fit;
+  }
+}
diff --git a/core/lib/Drupal/Core/Routing/NestedMatcher.php b/core/lib/Drupal/Core/Routing/NestedMatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..ff53715426e0c62d858799968ff22f4dbf8231d2
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/NestedMatcher.php
@@ -0,0 +1,199 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\NestedMatcher.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * The nested matcher layers multiple partial matchers together.
+ */
+class NestedMatcher implements NestedMatcherInterface {
+
+  /**
+   * The final matcher.
+   *
+   * @var Symfony\Component\Routing\Matcher\RequestMatcherInterface
+   */
+  protected $finalMatcher;
+
+  /**
+   * An array of PartialMatchers.
+   *
+   * @var array
+   */
+  protected $partialMatchers = array();
+
+  /**
+   * Array of PartialMatcherInterface objects, sorted.
+   *
+   * @var type
+   */
+  protected $sortedMatchers = array();
+
+  /**
+   * The initial matcher to match against.
+   *
+   * @var Drupal\core\Routing\InitialMatcherInterface
+   */
+  protected $initialMatcher;
+
+  /**
+   * The request context.
+   *
+   * @var Symfony\Component\Routing\RequestContext
+   */
+  protected $context;
+
+  /**
+   * Adds a partial matcher to the matching plan.
+   *
+   * Partial matchers will be run in the order in which they are added.
+   *
+   * @param \Drupal\Core\Routing\PartialMatcherInterface $matcher
+   *   A partial matcher.
+   * @param int $priority
+   *   (optional) The priority of the matcher. Higher number matchers will be checked
+   *   first. Default to 0.
+   *
+   * @return NestedMatcherInterface
+   *   The current matcher.
+   */
+  public function addPartialMatcher(PartialMatcherInterface $matcher, $priority = 0) {
+    if (empty($this->matchers[$priority])) {
+      $this->matchers[$priority] = array();
+    }
+
+    $this->matchers[$priority][] = $matcher;
+    $this->sortedMatchers = array();
+  }
+
+  /**
+   * Sets the final matcher for the matching plan.
+   *
+   * @param \Drupal\Core\Routing\FinalMatcherInterface $final
+   *   The matcher that will be called last to ensure only a single route is
+   *   found.
+   *
+   * @return \Drupal\Core\Routing\NestedMatcherInterface
+   *   The current matcher.
+   */
+  public function setFinalMatcher(FinalMatcherInterface $final) {
+    $this->finalMatcher = $final;
+
+    return $this;
+  }
+
+  /**
+   * Sets the first matcher for the matching plan.
+   *
+   * Partial matchers will be run in the order in which they are added.
+   *
+   * @param \Drupal\Core\Routing\InitialMatcherInterface $matcher
+   *   An initial matcher.  It is responsible for its own configuration and
+   *   initial route collection
+   *
+   * @return \Drupal\Core\Routing\NestedMatcherInterface
+   *   The current matcher.
+   */
+  public function setInitialMatcher(InitialMatcherInterface $initial) {
+    $this->initialMatcher = $initial;
+
+    return $this;
+  }
+
+  /**
+   * Tries to match a request with a set of routes.
+   *
+   * If the matcher can not find information, it must throw one of the
+   * exceptions documented below.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request to match.
+   *
+   * @return array
+   *   An array of parameters.
+   *
+   * @throws ResourceNotFoundException
+   *   If no matching resource could be found.
+   * @throws MethodNotAllowedException
+   *   If a matching resource was found but the request method is not allowed.
+   */
+  public function matchRequest(Request $request) {
+    $collection = $this->initialMatcher->matchRequestPartial($request);
+
+    foreach ($this->getPartialMatchers() as $matcher) {
+      if ($collection) {
+        $matcher->setCollection($collection);
+      }
+      $collection = $matcher->matchRequestPartial($request);
+    }
+
+    $attributes = $this->finalMatcher->setCollection($collection)->matchRequest($request);
+
+    return $attributes;
+  }
+
+  /**
+    * Sorts the matchers and flattens them.
+    *
+    * @return array
+    *   An array of RequestMatcherInterface objects.
+    */
+  public function getPartialMatchers() {
+    if (empty($this->sortedMatchers)) {
+      $this->sortedMatchers = $this->sortMatchers();
+    }
+
+    return $this->sortedMatchers;
+  }
+
+  /**
+    * Sort matchers by priority.
+    *
+    * The highest priority number is the highest priority (reverse sorting).
+    *
+    * @return \Symfony\Component\Routing\RequestMatcherInterface[]
+    *   An array of Matcher objects in the order they should be used.
+    */
+  protected function sortMatchers() {
+    $sortedMatchers = array();
+    krsort($this->matchers);
+
+    foreach ($this->matchers as $matchers) {
+      $sortedMatchers = array_merge($sortedMatchers, $matchers);
+    }
+
+    return $sortedMatchers;
+  }
+
+  /**
+   * Sets the request context.
+   *
+   * This method is unused. It is here only to satisfy the interface.
+   *
+   * @param \Symfony\Component\Routing\RequestContext $context
+   *   The context
+   */
+  public function setContext(RequestContext $context) {
+    $this->context = $context;
+  }
+
+  /**
+   * Gets the request context.
+   *
+   * This method is unused. It is here only to satisfy the interface.
+   *
+   * @return \Symfony\Component\Routing\RequestContext
+   *   The context
+   */
+  public function getContext() {
+    return $this->context;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/NestedMatcherInterface.php b/core/lib/Drupal/Core/Routing/NestedMatcherInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..6ae09954af78c7f6c120d8e6fa3241e755ff9690
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/NestedMatcherInterface.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\NestedMatcherInterface.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
+
+/**
+ * A NestedMatcher allows for multiple-stage resolution of a route.
+ */
+interface NestedMatcherInterface extends RequestMatcherInterface {
+
+  /**
+   * Sets the first matcher for the matching plan.
+   *
+   * Partial matchers will be run in the order in which they are added.
+   *
+   * @param \Drupal\Core\Routing\InitialMatcherInterface $matcher
+   *   An initial matcher.  It is responsible for its own configuration and
+   *   initial route collection
+   *
+   * @return \Drupal\Core\Routing\NestedMatcherInterface
+   *   The current matcher.
+   */
+  public function setInitialMatcher(InitialMatcherInterface $initial);
+
+  /**
+   * Adds a partial matcher to the matching plan.
+   *
+   * Partial matchers will be run in the order in which they are added.
+   *
+   * @param \Drupal\Core\Routing\PartialMatcherInterface $matcher
+   *   A partial matcher.
+   * @param int $priority
+   *   (optional) The priority of the matcher. Higher number matchers will be checked
+   *   first. Default to 0.
+   *
+   * @return NestedMatcherInterface
+   *   The current matcher.
+   */
+  public function addPartialMatcher(PartialMatcherInterface $matcher, $priority = 0);
+
+  /**
+   * Sets the final matcher for the matching plan.
+   *
+   * @param \Drupal\Core\Routing\FinalMatcherInterface $final
+   *   The matcher that will be called last to ensure only a single route is
+   *   found.
+   *
+   * @return \Drupal\Core\Routing\NestedMatcherInterface
+   *   The current matcher.
+   */
+  public function setFinalMatcher(FinalMatcherInterface $final);
+}
diff --git a/core/lib/Drupal/Core/Routing/PartialMatcher.php b/core/lib/Drupal/Core/Routing/PartialMatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..d28f9ac97a919335fedfc165305d63f6dd819497
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/PartialMatcher.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\PartialMatcher.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Utility base class for partial matchers.
+ */
+abstract class PartialMatcher implements PartialMatcherInterface {
+
+  /**
+   * The RouteCollection this matcher should match against.
+   *
+   * @var \Symfony\Component\Routing\RouteCollection
+   */
+  protected $routes;
+
+  /**
+   * Sets the route collection this matcher should use.
+   *
+   * @param \Symfony\Component\Routing\RouteCollection $collection
+   *   The collection against which to match.
+   *
+   * @return \Drupal\Core\Routing\PartialMatcherInterface
+   *   The current matcher.
+   */
+  public function setCollection(RouteCollection $collection) {
+    $this->routes = $collection;
+
+    return $this;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/PartialMatcherInterface.php b/core/lib/Drupal/Core/Routing/PartialMatcherInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..0d180c6b696dfa1163dfce32cef9c9239f6b9656
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/PartialMatcherInterface.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\PathMatcherInterface.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * A PartialMatcher works like a UrlMatcher, but will return multiple candidate routes.
+ */
+interface PartialMatcherInterface {
+
+  /**
+   * Sets the route collection this matcher should use.
+   *
+   * @param \Symfony\Component\Routing\RouteCollection $collection
+   *   The collection against which to match.
+   *
+   * @return \Drupal\Core\Routing\PartialMatcherInterface
+   *   The current matcher.
+   */
+  public function setCollection(RouteCollection $collection);
+
+  /**
+   * Matches a request against multiple routes.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   A Request object against which to match.
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   *   A RouteCollection of matched routes.
+   */
+  public function matchRequestPartial(Request $request);
+}
diff --git a/core/lib/Drupal/Core/Routing/PathMatcher.php b/core/lib/Drupal/Core/Routing/PathMatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..f037d9acb4100ec70c77c7e7053cd0d77ac6ae2e
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/PathMatcher.php
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\PathMatcher.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+
+use Drupal\Core\Database\Connection;
+
+/**
+ * Initial matcher to match a route against a built database, by path.
+ */
+class PathMatcher implements InitialMatcherInterface {
+
+  /**
+   * The database connection from which to read route information.
+   *
+   * @var Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * The name of the SQL table from which to read the routes.
+   *
+   * @var string
+   */
+  protected $tableName;
+
+  /**
+   * Constructs a new PathMatcher.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   A database connection object.
+   * @param string $table
+   *   The table in the database to use for matching.
+   */
+  public function __construct(Connection $connection, $table = 'router') {
+    $this->connection = $connection;
+    $this->tableName = $table;
+  }
+
+  /**
+   * Matches a request against multiple routes.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   A Request object against which to match.
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   *   A RouteCollection of matched routes.
+   */
+  public function matchRequestPartial(Request $request) {
+
+    $path = rtrim($request->getPathInfo(), '/');
+
+    $parts = array_slice(array_filter(explode('/', $path)), 0, MatcherDumper::MAX_PARTS);
+
+    $ancestors = $this->getCandidateOutlines($parts);
+
+    $routes = $this->connection->query("SELECT name, route FROM {" . $this->connection->escapeTable($this->tableName) . "} WHERE pattern_outline IN (:patterns) ORDER BY fit", array(
+      ':patterns' => $ancestors,
+    ))
+    ->fetchAllKeyed();
+
+    $collection = new RouteCollection();
+    foreach ($routes as $name => $route) {
+      $route = unserialize($route);
+      if (preg_match($route->compile()->getRegex(), $path, $matches)) {
+        $collection->add($name, $route);
+      }
+    }
+
+    if (!count($collection)) {
+      throw new ResourceNotFoundException();
+    }
+
+    return $collection;
+  }
+
+  /**
+   * Returns an array of path pattern outlines that could match the path parts.
+   *
+   * @param array $parts
+   *   The parts of the path for which we want candidates.
+   *
+   * @return array
+   *   An array of outlines that could match the specified path parts.
+   */
+  public function getCandidateOutlines(array $parts) {
+    $number_parts = count($parts);
+    $ancestors = array();
+    $length =  $number_parts - 1;
+    $end = (1 << $number_parts) - 1;
+
+    // The highest possible mask is a 1 bit for every part of the path. We will
+    // check every value down from there to generate a possible outline.
+    $masks = range($end, pow($number_parts - 1, 2));
+
+    // Only examine patterns that actually exist as router items (the masks).
+    foreach ($masks as $i) {
+      if ($i > $end) {
+        // Only look at masks that are not longer than the path of interest.
+        continue;
+      }
+      elseif ($i < (1 << $length)) {
+        // We have exhausted the masks of a given length, so decrease the length.
+        --$length;
+      }
+      $current = '';
+      for ($j = $length; $j >= 0; $j--) {
+        // Check the bit on the $j offset.
+        if ($i & (1 << $j)) {
+          // Bit one means the original value.
+          $current .= $parts[$length - $j];
+        }
+        else {
+          // Bit zero means means wildcard.
+          $current .= '%';
+        }
+        // Unless we are at offset 0, add a slash.
+        if ($j) {
+          $current .= '/';
+        }
+      }
+      $ancestors[] = '/' . $current;
+    }
+    return $ancestors;
+  }
+}
diff --git a/core/lib/Drupal/Core/Routing/RouteBuilder.php b/core/lib/Drupal/Core/Routing/RouteBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..6335ffe625c956734602170d086900e61f075543
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/RouteBuilder.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\RouteBuilder.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\Routing\RouteCompilerInterface;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface;
+
+/**
+ * Managing class for rebuilding the router table.
+ *
+ * Because this class makes use of the modules system, it cannot currently
+ * be unit tested.
+ */
+class RouteBuilder {
+
+  /**
+   * The dumper to which we should send collected routes.
+   *
+   * @var \Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface
+   */
+  protected $dumper;
+
+  /**
+   * Construcs the RouteBuilder using the passed MatcherDumperInterface.
+   *
+   * @param Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface $dumper
+   *   The matcher dumper used to store the route information.
+   */
+  public function __construct(MatcherDumperInterface $dumper) {
+    $this->dumper = $dumper;
+  }
+
+  /**
+   * Rebuilds the route info and dumps to dumper.
+   */
+  public function rebuild() {
+    // We need to manually call each module so that we can know which module
+    // a given item came from.
+
+    foreach (module_implements('route_info') as $module) {
+      $routes = call_user_func($module . '_route_info');
+      drupal_alter('router_info', $routes, $module);
+      $this->dumper->addRoutes($routes);
+      $this->dumper->dump(array('route_set' => $module));
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Routing/RouteCompiler.php b/core/lib/Drupal/Core/Routing/RouteCompiler.php
new file mode 100644
index 0000000000000000000000000000000000000000..2585d749168920036275ccb0e978adbf4b8641e2
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/RouteCompiler.php
@@ -0,0 +1,248 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Routing\RouteCompiler.
+ */
+
+namespace Drupal\Core\Routing;
+
+use Symfony\Component\Routing\RouteCompilerInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Compiler to generate derived information from a Route necessary for matching.
+ */
+class RouteCompiler implements RouteCompilerInterface {
+
+  /**
+   * The maximum number of path elements for a route pattern;
+   */
+  const MAX_PARTS = 9;
+
+  /**
+   * Utility constant to use for regular expressions against the path.
+   */
+  const REGEX_DELIMITER = '#';
+
+  /**
+   * Compiles the current route instance.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   A Route instance.
+   *
+   * @return \Drupal\Core\Routing\CompiledRoute
+   *   A CompiledRoute instance.
+   */
+  public function compile(Route $route) {
+
+    $stripped_path = $this->getPathWithoutDefaults($route);
+
+    $fit = $this->getFit($stripped_path);
+
+    $pattern_outline = $this->getPatternOutline($stripped_path);
+
+    $num_parts = count(explode('/', trim($pattern_outline, '/')));
+
+    $regex = $this->getRegex($route, $route->getPattern());
+
+    return new CompiledRoute($route, $fit, $pattern_outline, $num_parts, $regex);
+  }
+
+  /**
+   * Generates a regular expression that will match this pattern.
+   *
+   * This regex can be used in preg_match() to extract values inside {}.
+   *
+   * This algorithm was lifted directly from Symfony's RouteCompiler class.
+   * It is not factored out nicely there, so we cannot simply subclass it.
+   * @todo Refactor Symfony's RouteCompiler so that it's useful to subclass.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route object.
+   * @param string $pattern
+   *   The pattern for which we want a matching regex.
+   *
+   * @return string
+   *   A regular expression that will match a path against this route.
+   *
+   * @throws \LogicException
+   */
+  public function getRegex(Route $route, $pattern) {
+    $len = strlen($pattern);
+    $tokens = array();
+    $variables = array();
+    $pos = 0;
+    preg_match_all('#.\{(\w+)\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
+    foreach ($matches as $match) {
+      if ($text = substr($pattern, $pos, $match[0][1] - $pos)) {
+        $tokens[] = array('text', $text);
+      }
+
+      $pos = $match[0][1] + strlen($match[0][0]);
+      $var = $match[1][0];
+
+      if ($req = $route->getRequirement($var)) {
+        $regexp = $req;
+      }
+      else {
+        // Use the character preceding the variable as a separator
+        $separators = array($match[0][0][0]);
+
+        if ($pos !== $len) {
+          // Use the character following the variable as the separator when available
+          $separators[] = $pattern[$pos];
+        }
+        $regexp = sprintf('[^%s]+', preg_quote(implode('', array_unique($separators)), self::REGEX_DELIMITER));
+      }
+
+      $tokens[] = array('variable', $match[0][0][0], $regexp, $var);
+
+      if (in_array($var, $variables)) {
+        throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $route->getPattern(), $var));
+      }
+
+      $variables[] = $var;
+    }
+
+    if ($pos < $len) {
+      $tokens[] = array('text', substr($pattern, $pos));
+    }
+
+    // find the first optional token
+    $first_optional = INF;
+    for ($i = count($tokens) - 1; $i >= 0; $i--) {
+        $token = $tokens[$i];
+        if ('variable' === $token[0] && $route->hasDefault($token[3])) {
+            $first_optional = $i;
+        } else {
+            break;
+        }
+    }
+
+    // compute the matching regexp
+    $regexp = '';
+    for ($i = 0, $nbToken = count($tokens); $i < $nbToken; $i++) {
+        $regexp .= $this->computeRegexp($tokens, $i, $first_optional);
+    }
+
+    return self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'s';
+  }
+
+  /**
+   * Computes the regexp used to match a specific token. It can be static text or a subpattern.
+   *
+   * @param array $tokens
+   *   The route tokens
+   * @param integer $index
+   *   The index of the current token
+   * @param integer $first_optional
+   *   The index of the first optional token
+   *
+   * @return string
+   *   The regexp pattern for a single token
+   */
+  private function computeRegexp(array $tokens, $index, $first_optional) {
+    $token = $tokens[$index];
+    if ('text' === $token[0]) {
+      // Text tokens
+      return preg_quote($token[1], self::REGEX_DELIMITER);
+    }
+    else {
+      // Variable tokens
+      if (0 === $index && 0 === $first_optional) {
+        // When the only token is an optional variable token, the separator is
+        // required.
+        return sprintf('%s(?<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]);
+      }
+      else {
+        $regexp = sprintf('%s(?<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]);
+        if ($index >= $first_optional) {
+          // Enclose each optional token in a subpattern to make it optional.
+          // "?:" means it is non-capturing, i.e. the portion of the subject
+          // string that matched the optional subpattern is not passed back.
+          $regexp = "(?:$regexp";
+          $nbTokens = count($tokens);
+          if ($nbTokens - 1 == $index) {
+            // Close the optional subpatterns.
+            $regexp .= str_repeat(")?", $nbTokens - $first_optional - (0 === $first_optional ? 1 : 0));
+          }
+        }
+
+        return $regexp;
+      }
+    }
+  }
+
+  /**
+   * Returns the pattern outline.
+   *
+   * The pattern outline is the path pattern but normalized so that all
+   * placeholders are equal strings and default values are removed.
+   *
+   * @param string $path
+   *   The path for which we want the normalized outline.
+   *
+   * @return string
+   *   The path pattern outline.
+   */
+  public function getPatternOutline($path) {
+    return preg_replace('#\{\w+\}#', '%', $path);
+  }
+
+  /**
+   * Determines the fitness of the provided path.
+   *
+   * @param string $path
+   *   The path whose fitness we want.
+   *
+   * @return int
+   *   The fitness of the path, as an integer.
+   */
+  public function getFit($path) {
+    $parts = explode('/', trim($path, '/'), static::MAX_PARTS);
+    $number_parts = count($parts);
+    // We store the highest index of parts here to save some work in the fit
+    // calculation loop.
+    $slashes = $number_parts - 1;
+
+    $fit = 0;
+    foreach ($parts as $k => $part) {
+      if (strpos($part, '{') === FALSE) {
+        $fit |=  1 << ($slashes - $k);
+      }
+    }
+
+    return $fit;
+  }
+
+  /**
+   * Returns the path of the route, without placeholders with a default value.
+   *
+   * When computing the path outline and fit, we want to skip default-value
+   * placeholders.  If we didn't, the path would never match.  Note that this
+   * only works for placeholders at the end of the path. Infix placeholders
+   * with default values don't make sense anyway, so that should not be a
+   * problem.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to have the placeholders removed from.
+   *
+   * @return string
+   *   The path string, stripped of placeholders that have default values.
+   */
+  protected function getPathWithoutDefaults(Route $route) {
+    $path = $route->getPattern();
+    $defaults = $route->getDefaults();
+
+    // Remove placeholders with default values from the outline, so that they
+    // will still match.
+    $remove = array_map(function($a) {
+      return '/{' . $a . '}';
+    }, array_keys($defaults));
+    $path = str_replace($remove, '', $path);
+
+    return $path;
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/RouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/RouterTest.php
index 90c0fc6839b97520942044d9c11f57e1cba10d3f..94832611476b27c8c9546e49160f7cbf4658e2e3 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/RouterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/RouterTest.php
@@ -178,7 +178,7 @@ function testAuthUserUserLogin() {
 
     $this->drupalGet('user/login');
     // Check that we got to 'user'.
-    $this->assertTrue($this->url == url('user', array('absolute' => TRUE)), "Logged-in user redirected to user on accessing user/login");
+    $this->assertTrue($this->url == url('user/' . $this->loggedInUser->uid, array('absolute' => TRUE)), "Logged-in user redirected to user on accessing user/login");
 
     // user/register should redirect to user/UID/edit.
     $this->drupalGet('user/register');
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/ChainMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/ChainMatcherTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c6b28ccb29737e1f8c957980c2bb639305af1aec
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/ChainMatcherTest.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Routing\ChainMatcherTest.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\Core\Routing\ChainMatcher;
+
+use Exception;
+
+/**
+ * Basic tests for the ChainMatcher.
+ */
+class ChainMatcherTest extends UnitTestBase {
+  
+  public static function getInfo() {
+    return array(
+      'name' => 'Chain matcher tests',
+      'description' => 'Confirm that the chain matcher is working correctly.',
+      'group' => 'Routing',
+    );
+  }
+
+  /**
+   * Confirms that the expected exception is thrown.
+   */
+  public function testMethodNotAllowed() {
+
+    $chain = new ChainMatcher();
+
+    $method_not_allowed = new MockMatcher(function(Request $request) {
+      throw new MethodNotAllowedException(array('POST'));
+    });
+
+    try {
+      $chain->add($method_not_allowed);
+      $chain->matchRequest(Request::create('my/path'));
+    }
+    catch (MethodNotAllowedException $e) {
+      $this->pass('Correct exception thrown.');
+    }
+    catch (Exception $e) {
+      $this->fail('Incorrect exception thrown: ' . get_class($e));
+    }
+  }
+
+  /**
+   * Confirms that the expected exception is thrown.
+   */
+  public function testRequestNotFound() {
+
+    $chain = new ChainMatcher();
+
+    $resource_not_found = new MockMatcher(function(Request $request) {
+      throw new ResourceNotFoundException();
+    });
+
+    try {
+      $chain->add($resource_not_found);
+      $chain->matchRequest(Request::create('my/path'));
+    }
+    catch (ResourceNotFoundException $e) {
+      $this->pass('Correct exception thrown.');
+    }
+    catch (Exception $e) {
+      $this->fail('Incorrect exception thrown: ' . get_class($e));
+    }
+  }
+
+  /**
+   * Confirms that the expected exception is thrown.
+   */
+  public function testRequestFound() {
+
+    $chain = new ChainMatcher();
+
+    $method_not_allowed = new MockMatcher(function(Request $request) {
+      throw new MethodNotAllowedException(array('POST'));
+    });
+
+    $resource_not_found = new MockMatcher(function(Request $request) {
+      throw new ResourceNotFoundException();
+    });
+
+    $found_data = new MockMatcher(function(Request $request) {
+      return array('_controller' => 'foo');
+    });
+
+    try {
+      $chain->add($method_not_allowed);
+      $chain->add($resource_not_found);
+      $chain->add($found_data);
+      $request = Request::create('my/path');
+      $attributes = $chain->matchRequest($request);
+      $this->assertEqual($attributes['_controller'], 'foo', 'Correct attributes returned.');
+    }
+    catch (Exception $e) {
+      $this->fail('Exception thrown when a match should have been successful: ' . get_class($e));
+    }
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a288b9eb16e0af913162f62d7ecd470ab03bd453
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/FirstEntryFinalMatcherTest.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Routing\NestedMatcherTest.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\Core\Routing\HttpMethodMatcher;
+use Drupal\Core\Routing\NestedMatcher;
+use Drupal\Core\Routing\FirstEntryFinalMatcher;
+
+use Exception;
+
+/**
+ * Basic tests for the NestedMatcher class.
+ */
+class FirstEntryFinalMatcherTest extends UnitTestBase {
+
+  /**
+   * A collection of shared fixture data for tests.
+   *
+   * @var RoutingFixtures
+   */
+  protected $fixtures;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'FirstEntryFinalMatcher tests',
+      'description' => 'Confirm that the FirstEntryFinalMatcher is working properly.',
+      'group' => 'Routing',
+    );
+  }
+
+  function __construct($test_id = NULL) {
+    parent::__construct($test_id);
+
+    $this->fixtures = new RoutingFixtures();
+  }
+
+  /**
+   * Confirms the final matcher returns correct attributes for static paths.
+   */
+  public function testFinalMatcherStatic() {
+
+    $collection = new RouteCollection();
+    $collection->add('route_a', new Route('/path/one', array(
+      '_controller' => 'foo',
+    )));
+
+    $request = Request::create('/path/one', 'GET');
+
+    $matcher = new FirstEntryFinalMatcher();
+    $matcher->setCollection($collection);
+    $attributes = $matcher->matchRequest($request);
+
+    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.');
+  }
+
+  /**
+   * Confirms the final matcher returns correct attributes for pattern paths.
+   */
+  public function testFinalMatcherPattern() {
+
+    $collection = new RouteCollection();
+    $collection->add('route_a', new Route('/path/one/{value}', array(
+      '_controller' => 'foo',
+    )));
+
+    $request = Request::create('/path/one/narf', 'GET');
+    $request->attributes->set('system_path', 'path/one/narf');
+
+    $matcher = new FirstEntryFinalMatcher();
+    $matcher->setCollection($collection);
+    $attributes = $matcher->matchRequest($request);
+
+    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.');
+    $this->assertEqual($attributes['value'], 'narf', 'Required placeholder value found.');
+  }
+
+  /**
+   * Confirms the final matcher returns correct attributes with default values.
+   */
+  public function testFinalMatcherPatternDefalts() {
+
+    $collection = new RouteCollection();
+    $collection->add('route_a', new Route('/path/one/{value}', array(
+      '_controller' => 'foo',
+      'value' => 'poink'
+    )));
+
+    $request = Request::create('/path/one', 'GET');
+    $request->attributes->set('system_path', 'path/one');
+
+    $matcher = new FirstEntryFinalMatcher();
+    $matcher->setCollection($collection);
+    $attributes = $matcher->matchRequest($request);
+
+    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+    $this->assertEqual($attributes['_controller'], 'foo', 'The correct controller was found.');
+    $this->assertEqual($attributes['value'], 'poink', 'Optional placeholder value used default.');
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c98da2eef301bd00b0782e07d1d18ee7e85042a8
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/HttpMethodMatcherTest.php
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Routing\HttpMethodMMatcherTest.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\Core\Routing\HttpMethodMatcher;
+use Drupal\Core\Routing\NestedMatcher;
+use Drupal\Core\Routing\FirstEntryFinalMatcher;
+
+use Exception;
+
+/**
+ * Basic tests for the HttpMethodMatcher class.
+ */
+class HttpMethodMatcherTest extends UnitTestBase {
+
+  /**
+   * A collection of shared fixture data for tests.
+   *
+   * @var RoutingFixtures
+   */
+  protected $fixtures;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Partial matcher HTTP Method tests',
+      'description' => 'Confirm that the Http Method partial matcher is functioning properly.',
+      'group' => 'Routing',
+    );
+  }
+
+  function __construct($test_id = NULL) {
+    parent::__construct($test_id);
+
+    $this->fixtures = new RoutingFixtures();
+  }
+  
+  /**
+   * Confirms that the HttpMethod matcher matches properly.
+   */
+  public function testFilterRoutes() {
+
+    $matcher = new HttpMethodMatcher();
+    $matcher->setCollection($this->fixtures->sampleRouteCollection());
+
+    $routes = $matcher->matchRequestPartial(Request::create('path/one', 'GET'));
+
+    $this->assertEqual(count($routes->all()), 4, 'The correct number of routes was found.');
+    $this->assertNotNull($routes->get('route_a'), 'The first matching route was found.');
+    $this->assertNull($routes->get('route_b'), 'The non-matching route was not found.');
+    $this->assertNotNull($routes->get('route_c'), 'The second matching route was found.');
+    $this->assertNotNull($routes->get('route_d'), 'The all-matching route was found.');
+    $this->assertNotNull($routes->get('route_e'), 'The multi-matching route was found.');
+  }
+
+  /**
+   * Confirms we can nest multiple partial matchers.
+   */
+  public function testNestedMatcher() {
+
+    $matcher = new NestedMatcher();
+
+    $matcher->setInitialMatcher(new MockPathMatcher($this->fixtures->sampleRouteCollection()));
+    $matcher->addPartialMatcher(new HttpMethodMatcher());
+    $matcher->setFinalMatcher(new FirstEntryFinalMatcher());
+
+    $request = Request::create('/path/one', 'GET');
+
+    $attributes = $matcher->matchRequest($request);
+
+    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+  }
+
+  /**
+   * Confirms that the HttpMethod matcher throws an exception for no-route.
+   */
+  public function testNoRouteFound() {
+    $matcher = new HttpMethodMatcher();
+
+    // Remove the sample route that would match any method.
+    $routes = $this->fixtures->sampleRouteCollection();
+    $routes->remove('route_d');
+
+    $matcher->setCollection($routes);
+
+    try {
+      $routes = $matcher->matchRequestPartial(Request::create('path/one', 'DELETE'));
+      $this->fail(t('No exception was thrown.'));
+    }
+    catch (Exception $e) {
+      $this->assertTrue($e instanceof MethodNotAllowedException, 'The correct exception was thrown.');
+    }
+
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/MatcherDumperTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/MatcherDumperTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7f6f312c7315904218d982113fc8d26048405c4f
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/MatcherDumperTest.php
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Routing\UrlMatcherDumperTest.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\Core\Database\Database;
+use Drupal\Core\Routing\MatcherDumper;
+
+/**
+ * Basic tests for the UrlMatcherDumper.
+ */
+class MatcherDumperTest extends UnitTestBase {
+
+  /**
+   * A collection of shared fixture data for tests.
+   *
+   * @var RoutingFixtures
+   */
+  protected $fixtures;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Dumper tests',
+      'description' => 'Confirm that the matcher dumper is functioning properly.',
+      'group' => 'Routing',
+    );
+  }
+
+  function __construct($test_id = NULL) {
+    parent::__construct($test_id);
+
+    $this->fixtures = new RoutingFixtures();
+  }
+
+  function setUp() {
+    parent::setUp();
+  }
+
+  /**
+   * Confirms that the dumper can be instantiated successfuly.
+   */
+  function testCreate() {
+    $connection = Database::getConnection();
+    $dumper= new MatcherDumper($connection);
+
+    $class_name = 'Drupal\Core\Routing\MatcherDumper';
+    $this->assertTrue($dumper instanceof $class_name, 'Dumper created successfully');
+  }
+
+  /**
+   * Confirms that we can add routes to the dumper.
+   */
+  function testAddRoutes() {
+    $connection = Database::getConnection();
+    $dumper= new MatcherDumper($connection);
+
+    $route = new Route('test');
+    $collection = new RouteCollection();
+    $collection->add('test_route', $route);
+
+    $dumper->addRoutes($collection);
+
+    $dumper_routes = $dumper->getRoutes()->all();
+    $collection_routes = $collection->all();
+
+    foreach ($dumper_routes as $name => $route) {
+      $this->assertEqual($route->getPattern(), $collection_routes[$name]->getPattern(), 'Routes match');
+    }
+  }
+
+  /**
+   * Confirms that we can add routes to the dumper when it already has some.
+   */
+  function testAddAdditionalRoutes() {
+    $connection = Database::getConnection();
+    $dumper= new MatcherDumper($connection);
+
+    $route = new Route('test');
+    $collection = new RouteCollection();
+    $collection->add('test_route', $route);
+    $dumper->addRoutes($collection);
+
+    $route = new Route('test2');
+    $collection2 = new RouteCollection();
+    $collection2->add('test_route2', $route);
+    $dumper->addRoutes($collection2);
+
+    // Merge the two collections together so we can test them.
+    $collection->addCollection(clone $collection2);
+
+    $dumper_routes = $dumper->getRoutes()->all();
+    $collection_routes = $collection->all();
+
+    $success = TRUE;
+    foreach ($collection_routes as $name => $route) {
+      if (empty($dumper_routes[$name])) {
+        $success = FALSE;
+        $this->fail(t('Not all routes found in the dumper.'));
+      }
+    }
+
+    if ($success) {
+      $this->pass('All routes found in the dumper.');
+    }
+  }
+
+  /**
+   * Confirm that we can dump a route collection to the database.
+   */
+  public function testDump() {
+    $connection = Database::getConnection();
+    $dumper= new MatcherDumper($connection, 'test_routes');
+
+    $route = new Route('/test/{my}/path');
+    $route->setOption('compiler_class', 'Drupal\Core\Routing\RouteCompiler');
+    $collection = new RouteCollection();
+    $collection->add('test_route', $route);
+
+    $dumper->addRoutes($collection);
+
+    $this->fixtures->createTables($connection);
+
+    $dumper->dump(array('route_set' => 'test'));
+
+    $record = $connection->query("SELECT * FROM {test_routes} WHERE name= :name", array(':name' => 'test_route'))->fetchObject();
+
+    $loaded_route = unserialize($record->route);
+
+    $this->assertEqual($record->name, 'test_route', 'Dumped route has correct name.');
+    $this->assertEqual($record->pattern, '/test/{my}/path', 'Dumped route has correct pattern.');
+    $this->assertEqual($record->pattern_outline, '/test/%/path', 'Dumped route has correct pattern outline.');
+    $this->assertEqual($record->fit, 5 /* 101 in binary */, 'Dumped route has correct fit.');
+    $this->assertTrue($loaded_route instanceof Route, 'Route object retrieved successfully.');
+
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/MockMatcher.php b/core/modules/system/lib/Drupal/system/Tests/Routing/MockMatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..71611d464bd11ac6394fcc21c0b485c6e47a11a6
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/MockMatcher.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Routing\MockMatcher.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+use Symfony\Component\Routing\Exception\RouteNotFoundException;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+
+use Closure;
+
+/**
+ * A mock matcher that can be configured with any matching logic for testing.
+ *
+ */
+class MockMatcher implements RequestMatcherInterface {
+
+  /**
+   * The matcher being tested.
+   */
+  protected $matcher;
+
+  public function __construct(Closure $matcher) {
+    $this->matcher = $matcher;
+  }
+
+  public function matchRequest(Request $request) {
+    $matcher = $this->matcher;
+    return $matcher($request);
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/MockPathMatcher.php b/core/modules/system/lib/Drupal/system/Tests/Routing/MockPathMatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..1592cbf079187e44c7b0c6c51341db0bdb9e97fc
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/MockPathMatcher.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\system\Tests\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+use Drupal\Core\Routing\InitialMatcherInterface;
+
+/**
+ * Provides a mock path matcher.
+ */
+class MockPathMatcher implements InitialMatcherInterface {
+
+  /**
+   * Routes to be matched.
+   *
+   * @var Symfony\Component\Routing\RouteCollection
+   */
+  protected $routes;
+
+  /**
+   * Construct the matcher given the route collection.
+   *
+   * @param Symfony\Component\Routing\RouteCollection $routes
+   *   The routes being matched.
+   */
+  public function __construct(RouteCollection $routes) {
+    $this->routes = $routes;
+  }
+
+  /**
+   * Matches a request against multiple routes.
+   *
+   * @param Request $request
+   *   A Request object against which to match.
+   *
+   * @return RouteCollection
+   *   A RouteCollection of matched routes.
+   */
+  public function matchRequestPartial(Request $request) {
+    // For now for testing we'll just do a straight string match.
+
+    $path = $request->getPathInfo();
+
+    $return = new RouteCollection();
+
+    foreach ($this->routes as $name => $route) {
+      if ($route->getPattern() == $path) {
+        $return->add($name, $route);
+      }
+    }
+
+    return $return;
+  }
+
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..444785c49f07b98c99905ab8dde4198b6aca1896
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/NestedMatcherTest.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Routing\NestedMatcherTest.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Exception\MethodNotAllowedException;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\Core\Routing\HttpMethodMatcher;
+use Drupal\Core\Routing\NestedMatcher;
+use Drupal\Core\Routing\FirstEntryFinalMatcher;
+
+use Exception;
+
+/**
+ * Basic tests for the NestedMatcher class.
+ */
+class NestedMatcherTest extends UnitTestBase {
+
+  /**
+   * A collection of shared fixture data for tests.
+   *
+   * @var RoutingFixtures
+   */
+  protected $fixtures;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'NestedMatcher tests',
+      'description' => 'Confirm that the NestedMatcher system is working properly.',
+      'group' => 'Routing',
+    );
+  }
+
+  function __construct($test_id = NULL) {
+    parent::__construct($test_id);
+
+    $this->fixtures = new RoutingFixtures();
+  }
+
+  /**
+   * Confirms we can nest multiple partial matchers.
+   */
+  public function testNestedMatcher() {
+
+    $matcher = new NestedMatcher();
+
+    $matcher->setInitialMatcher(new MockPathMatcher($this->fixtures->sampleRouteCollection()));
+    $matcher->addPartialMatcher(new HttpMethodMatcher(), 1);
+    $matcher->setFinalMatcher(new FirstEntryFinalMatcher());
+
+    $request = Request::create('/path/one', 'GET');
+
+    $attributes = $matcher->matchRequest($request);
+
+    $this->assertEqual($attributes['_route'], 'route_a', 'The correct matching route was found.');
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/PathMatcherTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/PathMatcherTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..078025085dacdc9aae1004c90640d34c55de1e5c
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/PathMatcherTest.php
@@ -0,0 +1,302 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Routing\PartialMatcherTest.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+use Symfony\Component\Routing\Exception\ResourceNotFoundException;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\Core\Routing\PathMatcher;
+use Drupal\Core\Database\Database;
+use Drupal\Core\Routing\MatcherDumper;
+
+use Exception;
+
+/**
+ * Basic tests for the UrlMatcherDumper.
+ */
+class PathMatcherTest extends UnitTestBase {
+
+  /**
+   * A collection of shared fixture data for tests.
+   *
+   * @var RoutingFixtures
+   */
+  protected $fixtures;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Path matcher tests',
+      'description' => 'Confirm that the path matching library is working correctly.',
+      'group' => 'Routing',
+    );
+  }
+
+  function __construct($test_id = NULL) {
+    parent::__construct($test_id);
+
+    $this->fixtures = new RoutingFixtures();
+  }
+
+  public function tearDown() {
+    $this->fixtures->dropTables(Database::getConnection());
+
+    parent::tearDown();
+  }
+
+  /**
+   * Confirms that the correct candidate outlines are generated.
+   */
+  public function testCandidateOutlines() {
+
+    $connection = Database::getConnection();
+    $matcher = new PathMatcher($connection);
+
+    $parts = array('node', '5', 'edit');
+
+    $candidates = $matcher->getCandidateOutlines($parts);
+
+    $candidates = array_flip($candidates);
+
+    $this->assertTrue(count($candidates) == 4, 'Correct number of candidates found');
+    $this->assertTrue(array_key_exists('/node/5/edit', $candidates), 'First candidate found.');
+    $this->assertTrue(array_key_exists('/node/5/%', $candidates), 'Second candidate found.');
+    $this->assertTrue(array_key_exists('/node/%/edit', $candidates), 'Third candidate found.');
+    $this->assertTrue(array_key_exists('/node/%/%', $candidates), 'Fourth candidate found.');
+  }
+
+  /**
+   * Confirms that we can find routes with the exact incoming path.
+   */
+  function testExactPathMatch() {
+    $connection = Database::getConnection();
+    $matcher = new PathMatcher($connection, 'test_routes');
+
+    $this->fixtures->createTables($connection);
+
+    $dumper = new MatcherDumper($connection, 'test_routes');
+    $dumper->addRoutes($this->fixtures->sampleRouteCollection());
+    $dumper->dump();
+
+    $path = '/path/one';
+
+    $request = Request::create($path, 'GET');
+
+    $routes = $matcher->matchRequestPartial($request);
+
+    foreach ($routes as $route) {
+      $this->assertEqual($route->getPattern(), $path, 'Found path has correct pattern');
+    }
+  }
+
+  /**
+   * Confirms that we can find routes whose pattern would match the request.
+   */
+  function testOutlinePathMatch() {
+    $connection = Database::getConnection();
+    $matcher = new PathMatcher($connection, 'test_routes');
+
+    $this->fixtures->createTables($connection);
+
+    $dumper = new MatcherDumper($connection, 'test_routes');
+    $dumper->addRoutes($this->fixtures->complexRouteCollection());
+    $dumper->dump();
+
+    $path = '/path/1/one';
+
+    $request = Request::create($path, 'GET');
+
+    $routes = $matcher->matchRequestPartial($request);
+
+    // All of the matching paths have the correct pattern.
+    foreach ($routes as $route) {
+      $this->assertEqual($route->compile()->getPatternOutline(), '/path/%/one', 'Found path has correct pattern');
+    }
+
+    $this->assertEqual(count($routes->all()), 2, 'The correct number of routes was found.');
+    $this->assertNotNull($routes->get('route_a'), 'The first matching route was found.');
+    $this->assertNotNull($routes->get('route_b'), 'The second matching route was not found.');
+  }
+
+  /**
+   * Confirms that a trailing slash on the request doesn't result in a 404.
+   */
+  function testOutlinePathMatchTrailingSlash() {
+    $connection = Database::getConnection();
+    $matcher = new PathMatcher($connection, 'test_routes');
+
+    $this->fixtures->createTables($connection);
+
+    $dumper = new MatcherDumper($connection, 'test_routes');
+    $dumper->addRoutes($this->fixtures->complexRouteCollection());
+    $dumper->dump();
+
+    $path = '/path/1/one/';
+
+    $request = Request::create($path, 'GET');
+
+    $routes = $matcher->matchRequestPartial($request);
+
+    // All of the matching paths have the correct pattern.
+    foreach ($routes as $route) {
+      $this->assertEqual($route->compile()->getPatternOutline(), '/path/%/one', 'Found path has correct pattern');
+    }
+
+    $this->assertEqual(count($routes->all()), 2, 'The correct number of routes was found.');
+    $this->assertNotNull($routes->get('route_a'), 'The first matching route was found.');
+    $this->assertNotNull($routes->get('route_b'), 'The second matching route was not found.');
+  }
+
+  /**
+   * Confirms that we can find routes whose pattern would match the request.
+   */
+  function testOutlinePathMatchDefaults() {
+    $connection = Database::getConnection();
+    $matcher = new PathMatcher($connection, 'test_routes');
+
+    $this->fixtures->createTables($connection);
+
+    $collection = new RouteCollection();
+    $collection->add('poink', new Route('/some/path/{value}', array(
+      'value' => 'poink',
+    )));
+
+    $dumper = new MatcherDumper($connection, 'test_routes');
+    $dumper->addRoutes($collection);
+    $dumper->dump();
+
+    $path = '/some/path';
+
+    $request = Request::create($path, 'GET');
+
+    try {
+      $routes = $matcher->matchRequestPartial($request);
+
+      // All of the matching paths have the correct pattern.
+      foreach ($routes as $route) {
+        $compiled = $route->compile();
+        $this->assertEqual($route->compile()->getPatternOutline(), '/some/path', 'Found path has correct pattern');
+      }
+
+      $this->assertEqual(count($routes->all()), 1, 'The correct number of routes was found.');
+      $this->assertNotNull($routes->get('poink'), 'The first matching route was found.');
+    }
+    catch (ResourceNotFoundException $e) {
+      $this->fail('No matching route found with default argument value.');
+    }
+  }
+
+  /**
+   * Confirms that we can find routes whose pattern would match the request.
+   */
+  function testOutlinePathMatchDefaultsCollision() {
+    $connection = Database::getConnection();
+    $matcher = new PathMatcher($connection, 'test_routes');
+
+    $this->fixtures->createTables($connection);
+
+    $collection = new RouteCollection();
+    $collection->add('poink', new Route('/some/path/{value}', array(
+      'value' => 'poink',
+    )));
+    $collection->add('narf', new Route('/some/path/here'));
+
+    $dumper = new MatcherDumper($connection, 'test_routes');
+    $dumper->addRoutes($collection);
+    $dumper->dump();
+
+    $path = '/some/path';
+
+    $request = Request::create($path, 'GET');
+
+    try {
+      $routes = $matcher->matchRequestPartial($request);
+
+      // All of the matching paths have the correct pattern.
+      foreach ($routes as $route) {
+        $compiled = $route->compile();
+        $this->assertEqual($route->compile()->getPatternOutline(), '/some/path', 'Found path has correct pattern');
+      }
+
+      $this->assertEqual(count($routes->all()), 1, 'The correct number of routes was found.');
+      $this->assertNotNull($routes->get('poink'), 'The first matching route was found.');
+    }
+    catch (ResourceNotFoundException $e) {
+      $this->fail('No matching route found with default argument value.');
+    }
+  }
+
+  /**
+   * Confirms that we can find routes whose pattern would match the request.
+   */
+  function testOutlinePathMatchDefaultsCollision2() {
+    $connection = Database::getConnection();
+    $matcher = new PathMatcher($connection, 'test_routes');
+
+    $this->fixtures->createTables($connection);
+
+    $collection = new RouteCollection();
+    $collection->add('poink', new Route('/some/path/{value}', array(
+      'value' => 'poink',
+    )));
+    $collection->add('narf', new Route('/some/path/here'));
+
+    $dumper = new MatcherDumper($connection, 'test_routes');
+    $dumper->addRoutes($collection);
+    $dumper->dump();
+
+    $path = '/some/path/here';
+
+    $request = Request::create($path, 'GET');
+
+    try {
+      $routes = $matcher->matchRequestPartial($request);
+
+      // All of the matching paths have the correct pattern.
+      foreach ($routes as $route) {
+        $this->assertEqual($route->compile()->getPatternOutline(), '/some/path/here', 'Found path has correct pattern');
+      }
+
+      $this->assertEqual(count($routes->all()), 1, 'The correct number of routes was found.');
+      $this->assertNotNull($routes->get('narf'), 'The first matching route was found.');
+    }
+    catch (ResourceNotFoundException $e) {
+      $this->fail('No matching route found with default argument value.');
+    }
+  }
+
+  /**
+   * Confirms that an exception is thrown when no matching path is found.
+   */
+  function testOutlinePathNoMatch() {
+    $connection = Database::getConnection();
+    $matcher = new PathMatcher($connection, 'test_routes');
+
+    $this->fixtures->createTables($connection);
+
+    $dumper = new MatcherDumper($connection, 'test_routes');
+    $dumper->addRoutes($this->fixtures->complexRouteCollection());
+    $dumper->dump();
+
+    $path = '/no/such/path';
+
+    $request = Request::create($path, 'GET');
+
+    try {
+      $routes = $matcher->matchRequestPartial($request);
+      $this->fail(t('No exception was thrown.'));
+    }
+    catch (Exception $e) {
+      $this->assertTrue($e instanceof ResourceNotFoundException, 'The correct exception was thrown.');
+    }
+
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouteTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouteTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..4fc7a111b9c0a715161b1bf4848e9520ab34c5f2
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouteTest.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Routing\RouteTest.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Symfony\Component\Routing\Route;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\Core\Database\Database;
+
+/**
+ * Basic tests for the Route.
+ */
+class RouteTest extends UnitTestBase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Routes',
+      'description' => 'Confirm that route object is functioning properly.',
+      'group' => 'Routing',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+  }
+
+  /**
+   * Confirms that a route compiles properly with the necessary data.
+   */
+  public function testCompilation() {
+    $route = new Route('/test/{something}/more');
+    $route->setOption('compiler_class', 'Drupal\Core\Routing\RouteCompiler');
+    $compiled = $route->compile();
+
+    $this->assertEqual($route, $compiled->getRoute(), 'Compiled route has the correct route object.');
+    $this->assertEqual($compiled->getFit(), 5 /* That's 101 binary*/, 'The fit was correct.');
+    $this->assertEqual($compiled->getPatternOutline(), '/test/%/more', 'The pattern outline was correct.');
+  }
+
+  /**
+   * Confirms that a compiled route with default values has the correct outline.
+   */
+  public function testCompilationDefaultValue() {
+    // Because "here" has a default value, it should not factor into the outline
+    // or the fitness.
+    $route = new Route('/test/{something}/more/{here}', array(
+      'here' => 'there',
+    ));
+    $route->setOption('compiler_class', 'Drupal\Core\Routing\RouteCompiler');
+    $compiled = $route->compile();
+
+    $this->assertEqual($route, $compiled->getRoute(), 'Compiled route has the correct route object.');
+    $this->assertEqual($compiled->getFit(), 5 /* That's 101 binary*/, 'The fit was correct.');
+    $this->assertEqual($compiled->getPatternOutline(), '/test/%/more', 'The pattern outline was correct.');
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..413737516ac386c437737fab06fbc240227ed875
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RouterTest.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Routing\RouterTest.
+ */
+
+namespace Drupal\system\Tests\Routing;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Functional class for the full integrated routing system.
+ */
+class RouterTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('block', 'router_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Integrated Router tests',
+      'description' => 'Function Tests for the fully integrated routing system.',
+      'group' => 'Routing',
+    );
+  }
+
+  /**
+   * Confirms that the router can get to a controller.
+   */
+  public function testCanRoute() {
+    $this->drupalGet('router_test/test1');
+    $this->assertRaw('test1', 'The correct string was returned because the route was successful.');
+  }
+
+  /**
+   * Confirms that our default controller logic works properly.
+   */
+  public function testDefaultController() {
+    $this->drupalGet('router_test/test2');
+    $this->assertRaw('test2', 'The correct string was returned because the route was successful.');
+
+    // Confirm that the page wrapping is being added, so we're not getting a
+    // raw body returned.
+    $this->assertRaw('</html>', 'Page markup was found.');
+
+    // In some instances, the subrequest handling may get confused and render
+    // a page inception style.  This test verifies that is not happening.
+    $this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
+  }
+
+  /**
+   * Confirms that placeholders in paths work correctly.
+   */
+  public function testControllerPlaceholders() {
+    $value = $this->randomName();
+    $this->drupalGet('router_test/test3/' . $value);
+    $this->assertRaw($value, 'The correct string was returned because the route was successful.');
+
+    // Confirm that the page wrapping is being added, so we're not getting a
+    // raw body returned.
+    $this->assertRaw('</html>', 'Page markup was found.');
+
+    // In some instances, the subrequest handling may get confused and render
+    // a page inception style.  This test verifies that is not happening.
+    $this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
+  }
+
+  /**
+   * Confirms that default placeholders in paths work correctly.
+   */
+  public function testControllerPlaceholdersDefaultValues() {
+    $this->drupalGet('router_test/test4');
+    $this->assertRaw('narf', 'The correct string was returned because the route was successful.');
+
+    // Confirm that the page wrapping is being added, so we're not getting a
+    // raw body returned.
+    $this->assertRaw('</html>', 'Page markup was found.');
+
+    // In some instances, the subrequest handling may get confused and render
+    // a page inception style.  This test verifies that is not happening.
+    $this->assertNoPattern('#</body>.*</body>#s', 'There was no double-page effect from a misrendered subrequest.');
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Routing/RoutingFixtures.php b/core/modules/system/lib/Drupal/system/Tests/Routing/RoutingFixtures.php
new file mode 100644
index 0000000000000000000000000000000000000000..f243ff134afc3443bc01b28bff7f29039ac72f19
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Routing/RoutingFixtures.php
@@ -0,0 +1,185 @@
+<?php
+
+namespace Drupal\system\Tests\Routing;
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+use Drupal\Core\Database\Connection;
+
+/**
+ * Utility methods to generate sample data, database configuration, etc.
+ */
+class RoutingFixtures {
+
+  /**
+   * Create the tables required for the sample data.
+   *
+   * @param Drupal\Core\Database\Connection $connection
+   *   The connection to use to create the tables.
+   */
+  public function createTables(Connection $connection) {
+    $tables = $this->routingTableDefinition();
+    $schema = $connection->schema();
+
+    foreach ($tables as $name => $table) {
+      $schema->dropTable($name);
+      $schema->createTable($name, $table);
+    }
+  }
+
+  /**
+   * Drop the tables used for the sample data.
+   *
+   * @param Drupal\Core\Database\Connection $connection
+   *   The connection to use to drop the tables.
+   */
+  public function dropTables(Connection $connection) {
+    $tables = $this->routingTableDefinition();
+    $schema = $connection->schema();
+
+    foreach ($tables as $name => $table) {
+      $schema->dropTable($name);
+    }
+  }
+
+  /**
+   * Returns a standard set of routes for testing.
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   */
+  public function sampleRouteCollection() {
+    $collection = new RouteCollection();
+
+    $route = new Route('path/one');
+    $route->setRequirement('_method', 'GET');
+    $collection->add('route_a', $route);
+
+    $route = new Route('path/one');
+    $route->setRequirement('_method', 'PUT');
+    $collection->add('route_b', $route);
+
+    $route = new Route('path/two');
+    $route->setRequirement('_method', 'GET');
+    $collection->add('route_c', $route);
+
+    $route = new Route('path/three');
+    $collection->add('route_d', $route);
+
+    $route = new Route('path/two');
+    $route->setRequirement('_method', 'GET|HEAD');
+    $collection->add('route_e', $route);
+
+    return $collection;
+  }
+
+  /**
+   * Returns a complex set of routes for testing.
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   */
+  public function complexRouteCollection() {
+    $collection = new RouteCollection();
+
+    $route = new Route('/path/{thing}/one');
+    $route->setRequirement('_method', 'GET');
+    $collection->add('route_a', $route);
+
+    $route = new Route('/path/{thing}/one');
+    $route->setRequirement('_method', 'PUT');
+    $collection->add('route_b', $route);
+
+    $route = new Route('/somewhere/{item}/over/the/rainbow');
+    $route->setRequirement('_method', 'GET');
+    $collection->add('route_c', $route);
+
+    $route = new Route('/another/{thing}/about/{item}');
+    $collection->add('route_d', $route);
+
+    $route = new Route('/path/add/one');
+    $route->setRequirement('_method', 'GET|HEAD');
+    $collection->add('route_e', $route);
+
+    return $collection;
+  }
+
+  /**
+   * Returns the table definition for the routing fixtures.
+   *
+   * @return array
+   *   Table definitions.
+   */
+  public function routingTableDefinition() {
+
+    $tables['test_routes'] = array(
+      'description' => 'Maps paths to various callbacks (access, page and title)',
+      'fields' => array(
+        'name' => array(
+          'description' => 'Primary Key: Machine name of this route',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'pattern' => array(
+          'description' => 'The path pattern for this URI',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'pattern_outline' => array(
+          'description' => 'The pattern',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'route_set' => array(
+          'description' => 'The route set grouping to which a route belongs.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'access_callback' => array(
+          'description' => 'The callback which determines the access to this router path. Defaults to user_access.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+          'default' => '',
+        ),
+        'access_arguments' => array(
+          'description' => 'A serialized array of arguments for the access callback.',
+          'type' => 'blob',
+          'not null' => FALSE,
+        ),
+        'fit' => array(
+          'description' => 'A numeric representation of how specific the path is.',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+        ),
+        'number_parts' => array(
+          'description' => 'Number of parts in this router path.',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0,
+          'size' => 'small',
+        ),
+        'route' => array(
+          'description' => 'A serialized Route object',
+          'type' => 'text',
+        ),
+      ),
+      'indexes' => array(
+        'fit' => array('fit'),
+        'pattern_outline' => array('pattern_outline'),
+        'route_set' => array('route_set'),
+      ),
+      'primary key' => array('name'),
+    );
+
+    return $tables;
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
index 86456e5008d4fbdca40b6db16ddcdcae6de229d9..812bde7752a94a5736d8e856dcc1e80c61b7afd1 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UpgradePathTestBase.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Database\Database;
 use Drupal\simpletest\WebTestBase;
 use Exception;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
 
 /**
  * Perform end-to-end tests of the upgrade path.
@@ -246,7 +247,14 @@ protected function performUpgrade($register_errors = TRUE) {
     module_load_all(FALSE, TRUE);
 
     // Rebuild caches.
-    drupal_flush_all_caches();
+    // @todo Remove the try/catch when UpgradePathTestBase::setup() is fixed to
+    //   boot DrupalKernel (as WebTestBase::setup() does).
+    drupal_static_reset();
+    try {
+      drupal_flush_all_caches();
+    }
+    catch (InvalidArgumentException $e) {
+    }
 
     // Reload global $conf array and permissions.
     $this->refreshVariables();
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 66aa5e8c4516f32d06be5f5a82957babb73b7447..7957a648a63e44a8506d78d5d36d874de9a47125 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -565,6 +565,51 @@ function hook_menu_get_item_alter(&$router_item, $path, $original_map) {
   }
 }
 
+/**
+ * Defines routes in the new router system.
+ *
+ * A route is a Symfony Route object.  See the Symfony documentation for more
+ * details on the available options.  Of specific note:
+ *  - _controller: This is the PHP callable that will handle a request matching
+ *              the route.
+ *  - _content: This is the PHP callable that will handle the body of a request
+ *              matching this route.  A default controller will provide the page
+ *              rendering around it.
+ *
+ * Typically you will only specify one or the other of those properties.
+ *
+ * @deprecated
+ *   This mechanism for registering routes is temporary. It will be replaced
+ *   by a more robust mechanism in the near future.  It is documented here
+ *   only for completeness.
+ */
+function hook_route_info() {
+  $collection = new RouteCollection();
+
+  $route = new Route('router_test/test1', array(
+    '_controller' => '\Drupal\router_test\TestControllers::test1'
+  ));
+  $collection->add('router_test_1', $route);
+
+  $route = new Route('router_test/test2', array(
+    '_content' => '\Drupal\router_test\TestControllers::test2'
+  ));
+  $collection->add('router_test_2', $route);
+
+  $route = new Route('router_test/test3/{value}', array(
+    '_content' => '\Drupal\router_test\TestControllers::test3'
+  ));
+  $collection->add('router_test_3', $route);
+
+  $route = new Route('router_test/test4/{value}', array(
+    '_content' => '\Drupal\router_test\TestControllers::test4',
+    'value' => 'narf',
+  ));
+  $collection->add('router_test_4', $route);
+
+  return $collection;
+}
+
 /**
  * Define menu items and page callbacks.
  *
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 3790f2b407d8ee5a4691a377ec4d64826a9dd4e7..05459d03d43d8d4377379cfff9712fd58698998f 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -1225,6 +1225,63 @@ function system_schema() {
     ),
   );
 
+  $schema['router'] = array(
+    'description' => 'Maps paths to various callbacks (access, page and title)',
+    'fields' => array(
+      'name' => array(
+        'description' => 'Primary Key: Machine name of this route',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'pattern' => array(
+        'description' => 'The path pattern for this URI',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'pattern_outline' => array(
+        'description' => 'The pattern',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'route_set' => array(
+        'description' => 'The route set grouping to which a route belongs.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'fit' => array(
+        'description' => 'A numeric representation of how specific the path is.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'route' => array(
+        'description' => 'A serialized Route object',
+        'type' => 'text',
+      ),
+      'number_parts' => array(
+        'description' => 'Number of parts in this router path.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small',
+      ),
+    ),
+    'indexes' => array(
+      'fit' => array('fit'),
+      'pattern_outline' => array('pattern_outline'),
+      'route_set' => array('route_set'),
+    ),
+    'primary key' => array('name'),
+  );
+
   $schema['semaphore'] = array(
     'description' => 'Table for holding semaphores, locks, flags, etc. that cannot be stored as Drupal variables since they must not be cached.',
     'fields' => array(
@@ -1975,6 +2032,75 @@ function system_update_8021() {
   }
 }
 
+/*
+ * Create the new routing table.
+ */
+function system_update_8022() {
+
+  $tables['router'] = array(
+    'description' => 'Maps paths to various callbacks (access, page and title)',
+    'fields' => array(
+      'name' => array(
+        'description' => 'Primary Key: Machine name of this route',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'pattern' => array(
+        'description' => 'The path pattern for this URI',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'pattern_outline' => array(
+        'description' => 'The pattern',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'route_set' => array(
+        'description' => 'The route set grouping to which a route belongs.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'fit' => array(
+        'description' => 'A numeric representation of how specific the path is.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'route' => array(
+        'description' => 'A serialized Route object',
+        'type' => 'text',
+      ),
+      'number_parts' => array(
+        'description' => 'Number of parts in this router path.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small',
+      ),
+    ),
+    'indexes' => array(
+      'fit' => array('fit'),
+      'pattern_outline' => array('pattern_outline'),
+      'route_set' => array('route_set'),
+    ),
+    'primary key' => array('name'),
+  );
+
+  $schema = Database::getConnection()->schema();
+
+  $schema->dropTable('router');
+
+  $schema->createTable('router', $tables['router']);
+}
+
 /**
  * @} End of "defgroup updates-7.x-to-8.x".
  * The next series of updates should start at 9000.
diff --git a/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php
new file mode 100644
index 0000000000000000000000000000000000000000..fa92fd89b3e5969326bb48bf4f2418a369c915f8
--- /dev/null
+++ b/core/modules/system/tests/modules/router_test/lib/Drupal/router_test/TestControllers.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\router_test\TestControllers.
+ */
+
+namespace Drupal\router_test;
+
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Controller routines for testing the routing system.
+ */
+class TestControllers {
+
+  public function test1() {
+    return new Response('test1');
+  }
+
+  public function test2() {
+    return "test2";
+  }
+
+  public function test3($value) {
+    return $value;
+  }
+
+  public function test4($value) {
+    return $value;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/router_test/router_test.info b/core/modules/system/tests/modules/router_test/router_test.info
new file mode 100644
index 0000000000000000000000000000000000000000..d729865bbaf7c2eaa37e91429d4f8d0cb0e6edea
--- /dev/null
+++ b/core/modules/system/tests/modules/router_test/router_test.info
@@ -0,0 +1,6 @@
+name = "Router test"
+description = "Support module for routing testing."
+package = Testing
+version = VERSION
+core = 8.x
+hidden = TRUE
diff --git a/core/modules/system/tests/modules/router_test/router_test.module b/core/modules/system/tests/modules/router_test/router_test.module
new file mode 100644
index 0000000000000000000000000000000000000000..4da939d7059261b7016d5298971298d24a046200
--- /dev/null
+++ b/core/modules/system/tests/modules/router_test/router_test.module
@@ -0,0 +1,34 @@
+<?php
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Implements hook_router_info().
+ */
+function router_test_route_info() {
+  $collection = new RouteCollection();
+
+  $route = new Route('router_test/test1', array(
+    '_controller' => '\Drupal\router_test\TestControllers::test1'
+  ));
+  $collection->add('router_test_1', $route);
+
+  $route = new Route('router_test/test2', array(
+    '_content' => '\Drupal\router_test\TestControllers::test2'
+  ));
+  $collection->add('router_test_2', $route);
+
+  $route = new Route('router_test/test3/{value}', array(
+    '_content' => '\Drupal\router_test\TestControllers::test3'
+  ));
+  $collection->add('router_test_3', $route);
+
+  $route = new Route('router_test/test4/{value}', array(
+    '_content' => '\Drupal\router_test\TestControllers::test4',
+    'value' => 'narf',
+  ));
+  $collection->add('router_test_4', $route);
+
+  return $collection;
+}
diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc
index 9dd2cc99461d77fef95695f99aad43f5143df5cf..90d804e841a538e59a5444513c350d14e3a7e625 100644
--- a/core/modules/user/user.pages.inc
+++ b/core/modules/user/user.pages.inc
@@ -7,6 +7,7 @@
 
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
 
@@ -419,10 +420,7 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') {
 function user_page() {
   global $user;
   if ($user->uid) {
-    // @todo: Cleaner sub request handling.
-    $request = drupal_container()->get('request');
-    $subrequest = Request::create('/user/' . $user->uid, 'GET', $request->query->all(), $request->cookies->all(), array(), $request->server->all());
-    return drupal_container()->get('http_kernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST);
+    return new RedirectResponse(url('user/' . $user->uid, array('absolute' => TRUE)));
   }
   else {
     return drupal_get_form('user_login');
diff --git a/core/update.php b/core/update.php
index 52cf3d01a9929ba48732b8720e26532e4dcd103b..2273b408e4b88c9638bebb1ead3fac5cdc02c4c3 100644
--- a/core/update.php
+++ b/core/update.php
@@ -16,6 +16,7 @@
 
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\DependencyInjection\Reference;
 
 // Change the directory to the Drupal root.
 chdir('..');
@@ -445,6 +446,17 @@ function update_check_requirements($skip_warnings = FALSE) {
 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
 drupal_maintenance_theme();
 
+// @todo Remove after converting update.php to use DrupalKernel.
+$container = drupal_container();
+$container->register('database', 'Drupal\Core\Database\Connection')
+  ->setFactoryClass('Drupal\Core\Database\Database')
+  ->setFactoryMethod('getConnection')
+  ->addArgument('default');
+$container->register('router.dumper', '\Drupal\Core\Routing\MatcherDumper')
+  ->addArgument(new Reference('database'));
+$container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder')
+  ->addArgument(new Reference('router.dumper'));
+
 // Turn error reporting back on. From now on, only fatal errors (which are
 // not passed through the error handler) will cause a message to be printed.
 ini_set('display_errors', TRUE);