comparison core/lib/Drupal/Core/Routing/MatcherDumper.php @ 0:4c8ae668cc8c

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