Chris@0: connection = $connection; Chris@0: $this->state = $state; Chris@0: Chris@0: $this->tableName = $table; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function addRoutes(RouteCollection $routes) { Chris@0: if (empty($this->routes)) { Chris@0: $this->routes = $routes; Chris@0: } Chris@0: else { Chris@0: $this->routes->addCollection($routes); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Dumps a set of routes to the router table in the database. Chris@0: * Chris@0: * Available options: Chris@0: * - provider: The route grouping that is being dumped. All existing Chris@0: * routes with this provider will be deleted on dump. Chris@0: * - base_class: The base class name. Chris@0: * Chris@0: * @param array $options Chris@0: * An array of options. Chris@0: */ Chris@0: public function dump(array $options = []) { Chris@0: // Convert all of the routes into database records. Chris@0: // Accumulate the menu masks on top of any we found before. Chris@0: $masks = array_flip($this->state->get('routing.menu_masks.' . $this->tableName, [])); Chris@0: // Delete any old records first, then insert the new ones. That avoids Chris@0: // stale data. The transaction makes it atomic to avoid unstable router Chris@0: // states due to random failures. Chris@0: $transaction = $this->connection->startTransaction(); Chris@0: try { Chris@0: // We don't use truncate, because it is not guaranteed to be transaction Chris@0: // safe. Chris@0: try { Chris@0: $this->connection->delete($this->tableName) Chris@0: ->execute(); Chris@0: } Chris@0: catch (\Exception $e) { Chris@0: $this->ensureTableExists(); Chris@0: } Chris@0: Chris@0: // Split the routes into chunks to avoid big INSERT queries. Chris@0: $route_chunks = array_chunk($this->routes->all(), 50, TRUE); Chris@0: foreach ($route_chunks as $routes) { Chris@0: $insert = $this->connection->insert($this->tableName)->fields([ Chris@0: 'name', Chris@0: 'fit', Chris@0: 'path', Chris@0: 'pattern_outline', Chris@0: 'number_parts', Chris@0: 'route', Chris@0: ]); Chris@0: $names = []; Chris@0: foreach ($routes as $name => $route) { Chris@0: /** @var \Symfony\Component\Routing\Route $route */ Chris@18: $route->setOption('compiler_class', RouteCompiler::class); Chris@0: /** @var \Drupal\Core\Routing\CompiledRoute $compiled */ Chris@0: $compiled = $route->compile(); Chris@0: // The fit value is a binary number which has 1 at every fixed path Chris@0: // position and 0 where there is a wildcard. We keep track of all such Chris@0: // patterns that exist so that we can minimize the number of path Chris@0: // patterns we need to check in the RouteProvider. Chris@0: $masks[$compiled->getFit()] = 1; Chris@0: $names[] = $name; Chris@0: $values = [ Chris@0: 'name' => $name, Chris@0: 'fit' => $compiled->getFit(), Chris@0: 'path' => $route->getPath(), Chris@0: 'pattern_outline' => $compiled->getPatternOutline(), Chris@0: 'number_parts' => $compiled->getNumParts(), Chris@0: 'route' => serialize($route), Chris@0: ]; Chris@0: $insert->values($values); Chris@0: } Chris@0: Chris@0: // Insert all new routes. Chris@0: $insert->execute(); Chris@0: } Chris@0: Chris@0: } Chris@0: catch (\Exception $e) { Chris@0: $transaction->rollBack(); Chris@0: watchdog_exception('Routing', $e); Chris@0: throw $e; Chris@0: } Chris@0: // Sort the masks so they are in order of descending fit. Chris@0: $masks = array_keys($masks); Chris@0: rsort($masks); Chris@0: $this->state->set('routing.menu_masks.' . $this->tableName, $masks); Chris@0: Chris@0: $this->routes = NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the routes to match. Chris@0: * Chris@0: * @return \Symfony\Component\Routing\RouteCollection Chris@0: * A RouteCollection instance representing all routes currently in the Chris@0: * dumper. Chris@0: */ Chris@0: public function getRoutes() { Chris@0: return $this->routes; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks if the tree table exists and create it if not. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if the table was created, FALSE otherwise. Chris@0: */ Chris@0: protected function ensureTableExists() { Chris@0: try { Chris@0: if (!$this->connection->schema()->tableExists($this->tableName)) { Chris@0: $this->connection->schema()->createTable($this->tableName, $this->schemaDefinition()); Chris@0: return TRUE; Chris@0: } Chris@0: } Chris@0: catch (SchemaObjectExistsException $e) { Chris@0: // If another process has already created the config table, attempting to Chris@0: // recreate it will throw an exception. In this case just catch the Chris@0: // exception and do nothing. Chris@0: return TRUE; Chris@0: } Chris@0: return FALSE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Defines the schema for the router table. Chris@0: * Chris@0: * @return array Chris@0: * The schema API definition for the SQL storage table. Chris@0: * Chris@0: * @internal Chris@0: */ Chris@0: protected function schemaDefinition() { Chris@0: $schema = [ Chris@0: 'description' => 'Maps paths to various callbacks (access, page and title)', Chris@0: 'fields' => [ Chris@0: 'name' => [ Chris@0: 'description' => 'Primary Key: Machine name of this route', Chris@0: 'type' => 'varchar_ascii', Chris@0: 'length' => 255, Chris@0: 'not null' => TRUE, Chris@0: 'default' => '', Chris@0: ], Chris@0: 'path' => [ Chris@0: 'description' => 'The path for this URI', Chris@0: 'type' => 'varchar', Chris@0: 'length' => 255, Chris@0: 'not null' => TRUE, Chris@0: 'default' => '', Chris@0: ], Chris@0: 'pattern_outline' => [ Chris@0: 'description' => 'The pattern', Chris@0: 'type' => 'varchar', Chris@0: 'length' => 255, Chris@0: 'not null' => TRUE, Chris@0: 'default' => '', Chris@0: ], Chris@0: 'fit' => [ Chris@0: 'description' => 'A numeric representation of how specific the path is.', Chris@0: 'type' => 'int', Chris@0: 'not null' => TRUE, Chris@0: 'default' => 0, Chris@0: ], Chris@0: 'route' => [ Chris@0: 'description' => 'A serialized Route object', Chris@0: 'type' => 'blob', Chris@0: 'size' => 'big', Chris@0: ], Chris@0: 'number_parts' => [ Chris@0: 'description' => 'Number of parts in this router path.', Chris@0: 'type' => 'int', Chris@0: 'not null' => TRUE, Chris@0: 'default' => 0, Chris@0: 'size' => 'small', Chris@0: ], Chris@0: ], Chris@0: 'indexes' => [ Chris@0: 'pattern_outline_parts' => ['pattern_outline', 'number_parts'], Chris@0: ], Chris@0: 'primary key' => ['name'], Chris@0: ]; Chris@0: Chris@0: return $schema; Chris@0: } Chris@0: Chris@0: }