5e329f7daf857cf851afeb80241a55f9b265f04b
[yaffs-website] / web / core / lib / Drupal / Core / Routing / MatcherDumper.php
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   protected function schemaDefinition() {
199     $schema = [
200       'description' => 'Maps paths to various callbacks (access, page and title)',
201       'fields' => [
202         'name' => [
203           'description' => 'Primary Key: Machine name of this route',
204           'type' => 'varchar_ascii',
205           'length' => 255,
206           'not null' => TRUE,
207           'default' => '',
208         ],
209         'path' => [
210           'description' => 'The path for this URI',
211           'type' => 'varchar',
212           'length' => 255,
213           'not null' => TRUE,
214           'default' => '',
215         ],
216         'pattern_outline' => [
217           'description' => 'The pattern',
218           'type' => 'varchar',
219           'length' => 255,
220           'not null' => TRUE,
221           'default' => '',
222         ],
223         'fit' => [
224           'description' => 'A numeric representation of how specific the path is.',
225           'type' => 'int',
226           'not null' => TRUE,
227           'default' => 0,
228         ],
229         'route' => [
230           'description' => 'A serialized Route object',
231           'type' => 'blob',
232           'size' => 'big',
233         ],
234         'number_parts' => [
235           'description' => 'Number of parts in this router path.',
236           'type' => 'int',
237           'not null' => TRUE,
238           'default' => 0,
239           'size' => 'small',
240         ],
241       ],
242       'indexes' => [
243         'pattern_outline_parts' => ['pattern_outline', 'number_parts'],
244       ],
245       'primary key' => ['name'],
246     ];
247
248     return $schema;
249   }
250
251 }