f19b0eed8df277f560bc28ed754f4209d8f59ade
[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     catch (\Exception $e) {
146       $transaction->rollBack();
147       watchdog_exception('Routing', $e);
148       throw $e;
149     }
150     // Sort the masks so they are in order of descending fit.
151     $masks = array_keys($masks);
152     rsort($masks);
153     $this->state->set('routing.menu_masks.' . $this->tableName, $masks);
154
155     $this->routes = NULL;
156   }
157
158   /**
159    * Gets the routes to match.
160    *
161    * @return \Symfony\Component\Routing\RouteCollection
162    *   A RouteCollection instance representing all routes currently in the
163    *   dumper.
164    */
165   public function getRoutes() {
166     return $this->routes;
167   }
168
169   /**
170    * Checks if the tree table exists and create it if not.
171    *
172    * @return bool
173    *   TRUE if the table was created, FALSE otherwise.
174    */
175   protected function ensureTableExists() {
176     try {
177       if (!$this->connection->schema()->tableExists($this->tableName)) {
178         $this->connection->schema()->createTable($this->tableName, $this->schemaDefinition());
179         return TRUE;
180       }
181     }
182     catch (SchemaObjectExistsException $e) {
183       // If another process has already created the config table, attempting to
184       // recreate it will throw an exception. In this case just catch the
185       // exception and do nothing.
186       return TRUE;
187     }
188     return FALSE;
189   }
190
191   /**
192    * Defines the schema for the router table.
193    *
194    * @return array
195    *   The schema API definition for the SQL storage table.
196    *
197    * @internal
198    */
199   protected function schemaDefinition() {
200     $schema = [
201       'description' => 'Maps paths to various callbacks (access, page and title)',
202       'fields' => [
203         'name' => [
204           'description' => 'Primary Key: Machine name of this route',
205           'type' => 'varchar_ascii',
206           'length' => 255,
207           'not null' => TRUE,
208           'default' => '',
209         ],
210         'path' => [
211           'description' => 'The path for this URI',
212           'type' => 'varchar',
213           'length' => 255,
214           'not null' => TRUE,
215           'default' => '',
216         ],
217         'pattern_outline' => [
218           'description' => 'The pattern',
219           'type' => 'varchar',
220           'length' => 255,
221           'not null' => TRUE,
222           'default' => '',
223         ],
224         'fit' => [
225           'description' => 'A numeric representation of how specific the path is.',
226           'type' => 'int',
227           'not null' => TRUE,
228           'default' => 0,
229         ],
230         'route' => [
231           'description' => 'A serialized Route object',
232           'type' => 'blob',
233           'size' => 'big',
234         ],
235         'number_parts' => [
236           'description' => 'Number of parts in this router path.',
237           'type' => 'int',
238           'not null' => TRUE,
239           'default' => 0,
240           'size' => 'small',
241         ],
242       ],
243       'indexes' => [
244         'pattern_outline_parts' => ['pattern_outline', 'number_parts'],
245       ],
246       'primary key' => ['name'],
247     ];
248
249     return $schema;
250   }
251
252 }