From 2c79c025d2d2ba659d3b0bb68ef268d4d3aa3349 Mon Sep 17 00:00:00 2001 From: Larry Garfield Date: Mon, 28 May 2012 16:19:18 -0500 Subject: [PATCH] Copy in old dumping logic. Still being refactored. --- .../Drupal/Core/Routing/UrlMatcherDumper.php | 220 +++++++++++++++++- 1 file changed, 215 insertions(+), 5 deletions(-) diff --git a/core/lib/Drupal/Core/Routing/UrlMatcherDumper.php b/core/lib/Drupal/Core/Routing/UrlMatcherDumper.php index 105cd2e06aa1..022e72b2b0ef 100644 --- a/core/lib/Drupal/Core/Routing/UrlMatcherDumper.php +++ b/core/lib/Drupal/Core/Routing/UrlMatcherDumper.php @@ -3,17 +3,21 @@ 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; /** - * Description of UrlMatcherDumper - * - * @author crell + * Description of UrlMatcherDumper. */ class UrlMatcherDumper 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. * @@ -58,7 +62,34 @@ class UrlMatcherDumper implements MatcherDumperInterface { * * @return string A PHP class representing the matcher class */ - function dump(array $options = array()) { + public function dump(array $options = array()) { + $options += array( + 'route_set' => '', + ); + + $compiled = $this->compileRoutes($this->routes, $route_set); + + // Convert all of the routes into database records. + $insert = $this->connection->insert('router'); + + + + foreach ($this->routes as $name => $route) { + $insert->values($record); + } + + // 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('router') + ->condition('route_set', $options['route_set']) + ->execute(); + + $insert->execute(); + + // Transaction ends here. } @@ -67,8 +98,187 @@ class UrlMatcherDumper implements MatcherDumperInterface { * * @return RouteCollection A RouteCollection instance */ - function getRoutes() { + public function getRoutes() { return $this->routes; } + + protected function compileRoutes(RouteCollection $routes, $route_set) { + + // First pass: separate callbacks from paths, making paths ready for + // matching. Calculate fitness, and fill some default values. + $menu = array(); + $masks = array(); + foreach ($routes as $name => $item) { + $path = $item->getPattern(); + $move = FALSE; + + $parts = explode('/', $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; + + $num_placeholders = count(array_filter($parts, function($value) { + return strpos($value, '{') !== FALSE; + })); + + $fit = $this->getFit($path); + + if ($fit) { + $move = TRUE; + } + else { + // If there is no placeholder, it fits maximally. + $fit = (1 << $number_parts) - 1; + } + + $masks[$fit] = 1; + $item += array( + 'title' => '', + 'weight' => 0, + 'type' => MENU_NORMAL_ITEM, + 'module' => '', + '_number_parts' => $number_parts, + '_parts' => $parts, + '_fit' => $fit, + ); + + if ($move) { + $new_path = implode('/', $item['_parts']); + $menu[$new_path] = $item; + $sort[$new_path] = $number_parts; + } + else { + $menu[$path] = $item; + $sort[$path] = $number_parts; + } + } + + // Sort the route list. + array_multisort($sort, SORT_NUMERIC, $menu); + // Apply inheritance rules. + foreach ($menu as $path => $v) { + $item = &$menu[$path]; + + for ($i = $item['_number_parts'] - 1; $i; $i--) { + $parent_path = implode('/', array_slice($item['_parts'], 0, $i)); + if (isset($menu[$parent_path])) { + + $parent = &$menu[$parent_path]; + + // If an access callback is not found for a default local task we use + // the callback from the parent, since we expect them to be identical. + // In all other cases, the access parameters must be specified. + if (($item['type'] == MENU_DEFAULT_LOCAL_TASK) && !isset($item['access callback']) && isset($parent['access callback'])) { + $item['access callback'] = $parent['access callback']; + if (!isset($item['access arguments']) && isset($parent['access arguments'])) { + $item['access arguments'] = $parent['access arguments']; + } + } + + // Same for theme callbacks. + if (!isset($item['theme callback']) && isset($parent['theme callback'])) { + $item['theme callback'] = $parent['theme callback']; + if (!isset($item['theme arguments']) && isset($parent['theme arguments'])) { + $item['theme arguments'] = $parent['theme arguments']; + } + } + } + } + if (!isset($item['access callback']) && isset($item['access arguments'])) { + // Default callback. + $item['access callback'] = 'user_access'; + } + if (!isset($item['access callback']) || empty($item['page callback'])) { + $item['access callback'] = 0; + } + if (is_bool($item['access callback'])) { + $item['access callback'] = intval($item['access callback']); + } + + $item += array( + 'access arguments' => array(), + 'access callback' => '', + 'page arguments' => array(), + 'page callback' => '', + 'delivery callback' => '', + 'title arguments' => array(), + 'title callback' => 't', + 'theme arguments' => array(), + 'theme callback' => '', + 'description' => '', + 'position' => '', + 'context' => 0, + 'tab_parent' => '', + 'tab_root' => $path, + 'path' => $path, + 'file' => '', + 'file path' => '', + 'include file' => '', + ); + + // Calculate out the file to be included for each callback, if any. + if ($item['file']) { + $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']); + $item['include file'] = $file_path . '/' . $item['file']; + } + } + + // Sort the masks so they are in order of descending fit. + $masks = array_keys($masks); + rsort($masks); + + return array($menu, $masks); + + + // The old menu_router record structure, copied here for easy referencing. + array( + 'path' => $item['path'], + 'load_functions' => $item['load_functions'], + 'to_arg_functions' => $item['to_arg_functions'], + 'access_callback' => $item['access callback'], + 'access_arguments' => serialize($item['access arguments']), + 'page_callback' => $item['page callback'], + 'page_arguments' => serialize($item['page arguments']), + 'delivery_callback' => $item['delivery callback'], + 'fit' => $item['_fit'], + 'number_parts' => $item['_number_parts'], + 'context' => $item['context'], + 'tab_parent' => $item['tab_parent'], + 'tab_root' => $item['tab_root'], + 'title' => $item['title'], + 'title_callback' => $item['title callback'], + 'title_arguments' => ($item['title arguments'] ? serialize($item['title arguments']) : ''), + 'theme_callback' => $item['theme callback'], + 'theme_arguments' => serialize($item['theme arguments']), + 'type' => $item['type'], + 'description' => $item['description'], + 'position' => $item['position'], + 'weight' => $item['weight'], + 'include_file' => $item['include file'], + ); + } + + /** + * 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; + } }