annotate core/lib/Drupal/Core/Routing/MatcherDumper.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Routing;
Chris@0 4
Chris@0 5 use Drupal\Core\Database\SchemaObjectExistsException;
Chris@0 6 use Drupal\Core\State\StateInterface;
Chris@0 7 use Symfony\Component\Routing\RouteCollection;
Chris@0 8
Chris@0 9 use Drupal\Core\Database\Connection;
Chris@0 10
Chris@0 11 /**
Chris@0 12 * Dumps Route information to a database table.
Chris@0 13 *
Chris@0 14 * @see \Drupal\Core\Routing\RouteProvider
Chris@0 15 */
Chris@0 16 class MatcherDumper implements MatcherDumperInterface {
Chris@0 17
Chris@0 18 /**
Chris@0 19 * The database connection to which to dump route information.
Chris@0 20 *
Chris@0 21 * @var \Drupal\Core\Database\Connection
Chris@0 22 */
Chris@0 23 protected $connection;
Chris@0 24
Chris@0 25 /**
Chris@0 26 * The routes to be dumped.
Chris@0 27 *
Chris@0 28 * @var \Symfony\Component\Routing\RouteCollection
Chris@0 29 */
Chris@0 30 protected $routes;
Chris@0 31
Chris@0 32 /**
Chris@0 33 * The state.
Chris@0 34 *
Chris@0 35 * @var \Drupal\Core\State\StateInterface
Chris@0 36 */
Chris@0 37 protected $state;
Chris@0 38
Chris@0 39 /**
Chris@0 40 * The name of the SQL table to which to dump the routes.
Chris@0 41 *
Chris@0 42 * @var string
Chris@0 43 */
Chris@0 44 protected $tableName;
Chris@0 45
Chris@0 46 /**
Chris@0 47 * Construct the MatcherDumper.
Chris@0 48 *
Chris@0 49 * @param \Drupal\Core\Database\Connection $connection
Chris@0 50 * The database connection which will be used to store the route
Chris@0 51 * information.
Chris@0 52 * @param \Drupal\Core\State\StateInterface $state
Chris@0 53 * The state.
Chris@0 54 * @param string $table
Chris@0 55 * (optional) The table to store the route info in. Defaults to 'router'.
Chris@0 56 */
Chris@0 57 public function __construct(Connection $connection, StateInterface $state, $table = 'router') {
Chris@0 58 $this->connection = $connection;
Chris@0 59 $this->state = $state;
Chris@0 60
Chris@0 61 $this->tableName = $table;
Chris@0 62 }
Chris@0 63
Chris@0 64 /**
Chris@0 65 * {@inheritdoc}
Chris@0 66 */
Chris@0 67 public function addRoutes(RouteCollection $routes) {
Chris@0 68 if (empty($this->routes)) {
Chris@0 69 $this->routes = $routes;
Chris@0 70 }
Chris@0 71 else {
Chris@0 72 $this->routes->addCollection($routes);
Chris@0 73 }
Chris@0 74 }
Chris@0 75
Chris@0 76 /**
Chris@0 77 * Dumps a set of routes to the router table in the database.
Chris@0 78 *
Chris@0 79 * Available options:
Chris@0 80 * - provider: The route grouping that is being dumped. All existing
Chris@0 81 * routes with this provider will be deleted on dump.
Chris@0 82 * - base_class: The base class name.
Chris@0 83 *
Chris@0 84 * @param array $options
Chris@0 85 * An array of options.
Chris@0 86 */
Chris@0 87 public function dump(array $options = []) {
Chris@0 88 // Convert all of the routes into database records.
Chris@0 89 // Accumulate the menu masks on top of any we found before.
Chris@0 90 $masks = array_flip($this->state->get('routing.menu_masks.' . $this->tableName, []));
Chris@0 91 // Delete any old records first, then insert the new ones. That avoids
Chris@0 92 // stale data. The transaction makes it atomic to avoid unstable router
Chris@0 93 // states due to random failures.
Chris@0 94 $transaction = $this->connection->startTransaction();
Chris@0 95 try {
Chris@0 96 // We don't use truncate, because it is not guaranteed to be transaction
Chris@0 97 // safe.
Chris@0 98 try {
Chris@0 99 $this->connection->delete($this->tableName)
Chris@0 100 ->execute();
Chris@0 101 }
Chris@0 102 catch (\Exception $e) {
Chris@0 103 $this->ensureTableExists();
Chris@0 104 }
Chris@0 105
Chris@0 106 // Split the routes into chunks to avoid big INSERT queries.
Chris@0 107 $route_chunks = array_chunk($this->routes->all(), 50, TRUE);
Chris@0 108 foreach ($route_chunks as $routes) {
Chris@0 109 $insert = $this->connection->insert($this->tableName)->fields([
Chris@0 110 'name',
Chris@0 111 'fit',
Chris@0 112 'path',
Chris@0 113 'pattern_outline',
Chris@0 114 'number_parts',
Chris@0 115 'route',
Chris@0 116 ]);
Chris@0 117 $names = [];
Chris@0 118 foreach ($routes as $name => $route) {
Chris@0 119 /** @var \Symfony\Component\Routing\Route $route */
Chris@18 120 $route->setOption('compiler_class', RouteCompiler::class);
Chris@0 121 /** @var \Drupal\Core\Routing\CompiledRoute $compiled */
Chris@0 122 $compiled = $route->compile();
Chris@0 123 // The fit value is a binary number which has 1 at every fixed path
Chris@0 124 // position and 0 where there is a wildcard. We keep track of all such
Chris@0 125 // patterns that exist so that we can minimize the number of path
Chris@0 126 // patterns we need to check in the RouteProvider.
Chris@0 127 $masks[$compiled->getFit()] = 1;
Chris@0 128 $names[] = $name;
Chris@0 129 $values = [
Chris@0 130 'name' => $name,
Chris@0 131 'fit' => $compiled->getFit(),
Chris@0 132 'path' => $route->getPath(),
Chris@0 133 'pattern_outline' => $compiled->getPatternOutline(),
Chris@0 134 'number_parts' => $compiled->getNumParts(),
Chris@0 135 'route' => serialize($route),
Chris@0 136 ];
Chris@0 137 $insert->values($values);
Chris@0 138 }
Chris@0 139
Chris@0 140 // Insert all new routes.
Chris@0 141 $insert->execute();
Chris@0 142 }
Chris@0 143
Chris@0 144 }
Chris@0 145 catch (\Exception $e) {
Chris@0 146 $transaction->rollBack();
Chris@0 147 watchdog_exception('Routing', $e);
Chris@0 148 throw $e;
Chris@0 149 }
Chris@0 150 // Sort the masks so they are in order of descending fit.
Chris@0 151 $masks = array_keys($masks);
Chris@0 152 rsort($masks);
Chris@0 153 $this->state->set('routing.menu_masks.' . $this->tableName, $masks);
Chris@0 154
Chris@0 155 $this->routes = NULL;
Chris@0 156 }
Chris@0 157
Chris@0 158 /**
Chris@0 159 * Gets the routes to match.
Chris@0 160 *
Chris@0 161 * @return \Symfony\Component\Routing\RouteCollection
Chris@0 162 * A RouteCollection instance representing all routes currently in the
Chris@0 163 * dumper.
Chris@0 164 */
Chris@0 165 public function getRoutes() {
Chris@0 166 return $this->routes;
Chris@0 167 }
Chris@0 168
Chris@0 169 /**
Chris@0 170 * Checks if the tree table exists and create it if not.
Chris@0 171 *
Chris@0 172 * @return bool
Chris@0 173 * TRUE if the table was created, FALSE otherwise.
Chris@0 174 */
Chris@0 175 protected function ensureTableExists() {
Chris@0 176 try {
Chris@0 177 if (!$this->connection->schema()->tableExists($this->tableName)) {
Chris@0 178 $this->connection->schema()->createTable($this->tableName, $this->schemaDefinition());
Chris@0 179 return TRUE;
Chris@0 180 }
Chris@0 181 }
Chris@0 182 catch (SchemaObjectExistsException $e) {
Chris@0 183 // If another process has already created the config table, attempting to
Chris@0 184 // recreate it will throw an exception. In this case just catch the
Chris@0 185 // exception and do nothing.
Chris@0 186 return TRUE;
Chris@0 187 }
Chris@0 188 return FALSE;
Chris@0 189 }
Chris@0 190
Chris@0 191 /**
Chris@0 192 * Defines the schema for the router table.
Chris@0 193 *
Chris@0 194 * @return array
Chris@0 195 * The schema API definition for the SQL storage table.
Chris@0 196 *
Chris@0 197 * @internal
Chris@0 198 */
Chris@0 199 protected function schemaDefinition() {
Chris@0 200 $schema = [
Chris@0 201 'description' => 'Maps paths to various callbacks (access, page and title)',
Chris@0 202 'fields' => [
Chris@0 203 'name' => [
Chris@0 204 'description' => 'Primary Key: Machine name of this route',
Chris@0 205 'type' => 'varchar_ascii',
Chris@0 206 'length' => 255,
Chris@0 207 'not null' => TRUE,
Chris@0 208 'default' => '',
Chris@0 209 ],
Chris@0 210 'path' => [
Chris@0 211 'description' => 'The path for this URI',
Chris@0 212 'type' => 'varchar',
Chris@0 213 'length' => 255,
Chris@0 214 'not null' => TRUE,
Chris@0 215 'default' => '',
Chris@0 216 ],
Chris@0 217 'pattern_outline' => [
Chris@0 218 'description' => 'The pattern',
Chris@0 219 'type' => 'varchar',
Chris@0 220 'length' => 255,
Chris@0 221 'not null' => TRUE,
Chris@0 222 'default' => '',
Chris@0 223 ],
Chris@0 224 'fit' => [
Chris@0 225 'description' => 'A numeric representation of how specific the path is.',
Chris@0 226 'type' => 'int',
Chris@0 227 'not null' => TRUE,
Chris@0 228 'default' => 0,
Chris@0 229 ],
Chris@0 230 'route' => [
Chris@0 231 'description' => 'A serialized Route object',
Chris@0 232 'type' => 'blob',
Chris@0 233 'size' => 'big',
Chris@0 234 ],
Chris@0 235 'number_parts' => [
Chris@0 236 'description' => 'Number of parts in this router path.',
Chris@0 237 'type' => 'int',
Chris@0 238 'not null' => TRUE,
Chris@0 239 'default' => 0,
Chris@0 240 'size' => 'small',
Chris@0 241 ],
Chris@0 242 ],
Chris@0 243 'indexes' => [
Chris@0 244 'pattern_outline_parts' => ['pattern_outline', 'number_parts'],
Chris@0 245 ],
Chris@0 246 'primary key' => ['name'],
Chris@0 247 ];
Chris@0 248
Chris@0 249 return $schema;
Chris@0 250 }
Chris@0 251
Chris@0 252 }