Issue #2160811 by sun, damiankloip, dawehner: Router rebuild fails (even via rebuild script), in case the provider of existing routes has changed.

8.0.x
Alex Pott 2013-12-26 14:48:52 +00:00
parent cfd1c3ae1d
commit 4f2d2ab709
2 changed files with 105 additions and 41 deletions

View File

@ -79,50 +79,66 @@ class MatcherDumper implements MatcherDumperInterface {
$options += array(
'provider' => '',
);
// Convert all of the routes into database records.
$insert = $this->connection->insert($this->tableName)->fields(array(
'name',
'provider',
'fit',
'path',
'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,
'provider' => $options['provider'],
'fit' => $compiled->getFit(),
'path' => $compiled->getPath(),
'pattern_outline' => $compiled->getPatternOutline(),
'number_parts' => $compiled->getNumParts(),
'route' => serialize($route),
);
$insert->values($values);
}
// Delete any old records in this provider first, then insert the new ones.
// That avoids stale data. The transaction makes it atomic to avoid
// unstable router states due to random failures.
$transaction = $this->connection->startTransaction();
try {
// If there are no new routes, just delete any previously existing of this
// provider.
if (empty($this->routes) || !count($this->routes)) {
$this->connection->delete($this->tableName)
->condition('provider', $options['provider'])
->execute();
$insert->execute();
// We want to reuse the dumper for multiple providers, so on dump, flush
// the queued routes.
$this->routes = NULL;
} catch (\Exception $e) {
$transaction->rollback();
watchdog_exception('Routing', $e);
throw $e;
}
// Convert all of the routes into database records.
else {
$insert = $this->connection->insert($this->tableName)->fields(array(
'name',
'provider',
'fit',
'path',
'pattern_outline',
'number_parts',
'route',
));
$names = array();
foreach ($this->routes as $name => $route) {
$route->setOption('compiler_class', '\Drupal\Core\Routing\RouteCompiler');
$compiled = $route->compile();
$names[] = $name;
$values = array(
'name' => $name,
'provider' => $options['provider'],
'fit' => $compiled->getFit(),
'path' => $compiled->getPath(),
'pattern_outline' => $compiled->getPatternOutline(),
'number_parts' => $compiled->getNumParts(),
'route' => serialize($route),
);
$insert->values($values);
}
// Delete any old records of this provider first, then insert the new ones.
// That avoids stale data. The transaction makes it atomic to avoid
// unstable router states due to random failures.
$transaction = $this->connection->startTransaction();
try {
// Previously existing routes might have been moved to a new provider,
// so ensure that none of the names to insert exists. Also delete any
// old records of this provider (which may no longer exist).
$delete = $this->connection->delete($this->tableName);
$or = $delete->orConditionGroup()
->condition('provider', $options['provider'])
->condition('name', $names);
$delete->condition($or);
$delete->execute();
// Insert all new routes.
$insert->execute();
} catch (\Exception $e) {
$transaction->rollback();
watchdog_exception('Routing', $e);
throw $e;
}
}
// The dumper is reused for multiple providers, so reset the queued routes.
$this->routes = NULL;
}
/**

View File

@ -140,6 +140,54 @@ class MatcherDumperTest extends UnitTestBase {
$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.');
}
/**
* Tests that changing the provider of a route updates the dumped value.
*/
public function testDumpRouteProviderRename() {
$connection = Database::getConnection();
$dumper = new MatcherDumper($connection, 'test_routes');
$this->fixtures->createTables($connection);
$route = new Route('/test');
$collection = new RouteCollection();
$collection->add('test', $route);
$dumper->addRoutes($collection);
$dumper->dump(array('provider' => 'module_provider'));
$record = $connection->query("SELECT * FROM {test_routes} WHERE name = :name", array(':name' => 'test'))->fetchObject();
$this->assertEqual($record->provider, 'module_provider');
// Dump the same route name again with a different provider.
$dumper->addRoutes($collection);
$dumper->dump(array('provider' => 'module_provider2'));
// Ensure the route has the new provider.
$record = $connection->query("SELECT * FROM {test_routes} WHERE provider = :provider", array(':provider' => 'module_provider'))->fetchObject();
$this->assertFalse($record);
$record = $connection->query("SELECT * FROM {test_routes} WHERE provider = :provider", array(':provider' => 'module_provider2'))->fetchObject();
$this->assertEqual($record->path, '/test');
$this->assertEqual($record->name, 'test');
// Test dumping an empty route collection.
$dumper->addRoutes(new RouteCollection());
$dumper->dump(array('provider' => 'module_provider2'));
// Ensure the route of the provider no longer exists.
$record = $connection->query("SELECT * FROM {test_routes} WHERE provider = :provider", array(':provider' => 'module_provider2'))->fetchObject();
$this->assertFalse($record);
$dumper->addRoutes($collection);
$dumper->dump(array('provider' => 'module_provider2'));
// Test with an unset $routes property.
$dumper->dump(array('provider' => 'module_provider2'));
// Ensure the route of the provider no longer exists.
$record = $connection->query("SELECT * FROM {test_routes} WHERE provider = :provider", array(':provider' => 'module_provider2'))->fetchObject();
$this->assertFalse($record);
}
}