Version 1
[yaffs-website] / web / core / lib / Drupal / Core / Batch / BatchStorage.php
1 <?php
2
3 namespace Drupal\Core\Batch;
4
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Database\SchemaObjectExistsException;
7 use Symfony\Component\HttpFoundation\Session\SessionInterface;
8 use Drupal\Core\Access\CsrfTokenGenerator;
9
10 class BatchStorage implements BatchStorageInterface {
11
12   /**
13    * The table name.
14    */
15   const TABLE_NAME = 'batch';
16
17   /**
18    * The database connection.
19    *
20    * @var \Drupal\Core\Database\Connection
21    */
22   protected $connection;
23
24   /**
25    * The session.
26    *
27    * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
28    */
29   protected $session;
30
31   /**
32    * The CSRF token generator.
33    *
34    * @var \Drupal\Core\Access\CsrfTokenGenerator
35    */
36   protected $csrfToken;
37
38   /**
39    * Constructs the database batch storage service.
40    *
41    * @param \Drupal\Core\Database\Connection $connection
42    *   The database connection.
43    * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
44    *   The session.
45    * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
46    *   The CSRF token generator.
47    */
48   public function __construct(Connection $connection, SessionInterface $session, CsrfTokenGenerator $csrf_token) {
49     $this->connection = $connection;
50     $this->session = $session;
51     $this->csrfToken = $csrf_token;
52   }
53
54   /**
55    * {@inheritdoc}
56    */
57   public function load($id) {
58     // Ensure that a session is started before using the CSRF token generator.
59     $this->session->start();
60     try {
61       $batch = $this->connection->query("SELECT batch FROM {batch} WHERE bid = :bid AND token = :token", [
62         ':bid' => $id,
63         ':token' => $this->csrfToken->get($id),
64       ])->fetchField();
65     }
66     catch (\Exception $e) {
67       $this->catchException($e);
68       $batch = FALSE;
69     }
70     if ($batch) {
71       return unserialize($batch);
72     }
73     return FALSE;
74   }
75
76   /**
77    * {@inheritdoc}
78    */
79   public function delete($id) {
80     try {
81       $this->connection->delete('batch')
82         ->condition('bid', $id)
83         ->execute();
84     }
85     catch (\Exception $e) {
86       $this->catchException($e);
87     }
88   }
89
90   /**
91    * {@inheritdoc}
92    */
93   public function update(array $batch) {
94     try {
95       $this->connection->update('batch')
96         ->fields(['batch' => serialize($batch)])
97         ->condition('bid', $batch['id'])
98         ->execute();
99     }
100     catch (\Exception $e) {
101       $this->catchException($e);
102     }
103   }
104
105   /**
106    * {@inheritdoc}
107    */
108   public function cleanup() {
109     try {
110       // Cleanup the batch table and the queue for failed batches.
111       $this->connection->delete('batch')
112         ->condition('timestamp', REQUEST_TIME - 864000, '<')
113         ->execute();
114     }
115     catch (\Exception $e) {
116       $this->catchException($e);
117     }
118   }
119
120   /**
121    * {@inheritdoc}
122    */
123   public function create(array $batch) {
124     // Ensure that a session is started before using the CSRF token generator.
125     $this->session->start();
126     $try_again = FALSE;
127     try {
128       // The batch table might not yet exist.
129       $this->doCreate($batch);
130     }
131     catch (\Exception $e) {
132       // If there was an exception, try to create the table.
133       if (!$try_again = $this->ensureTableExists()) {
134         // If the exception happened for other reason than the missing table,
135         // propagate the exception.
136         throw $e;
137       }
138     }
139     // Now that the table has been created, try again if necessary.
140     if ($try_again) {
141       $this->doCreate($batch);
142     }
143   }
144
145   /**
146    * Saves a batch.
147    *
148    * @param array $batch
149    *   The array representing the batch to create.
150    */
151   protected function doCreate(array $batch) {
152     $this->connection->insert('batch')
153       ->fields([
154         'bid' => $batch['id'],
155         'timestamp' => REQUEST_TIME,
156         'token' => $this->csrfToken->get($batch['id']),
157         'batch' => serialize($batch),
158       ])
159       ->execute();
160   }
161
162   /**
163    * Check if the table exists and create it if not.
164    */
165   protected function ensureTableExists() {
166     try {
167       $database_schema = $this->connection->schema();
168       if (!$database_schema->tableExists(static::TABLE_NAME)) {
169         $schema_definition = $this->schemaDefinition();
170         $database_schema->createTable(static::TABLE_NAME, $schema_definition);
171         return TRUE;
172       }
173     }
174     // If another process has already created the batch table, attempting to
175     // recreate it will throw an exception. In this case just catch the
176     // exception and do nothing.
177     catch (SchemaObjectExistsException $e) {
178       return TRUE;
179     }
180     return FALSE;
181   }
182
183   /**
184    * Act on an exception when batch might be stale.
185    *
186    * If the table does not yet exist, that's fine, but if the table exists and
187    * yet the query failed, then the batch is stale and the exception needs to
188    * propagate.
189    *
190    * @param $e
191    *   The exception.
192    *
193    * @throws \Exception
194    */
195   protected function catchException(\Exception $e) {
196     if ($this->connection->schema()->tableExists(static::TABLE_NAME)) {
197       throw $e;
198     }
199   }
200
201   /**
202    * Defines the schema for the batch table.
203    */
204   public function schemaDefinition() {
205     return [
206       'description' => 'Stores details about batches (processes that run in multiple HTTP requests).',
207       'fields' => [
208         'bid' => [
209           'description' => 'Primary Key: Unique batch ID.',
210           // This is not a serial column, to allow both progressive and
211           // non-progressive batches. See batch_process().
212           'type' => 'int',
213           'unsigned' => TRUE,
214           'not null' => TRUE,
215         ],
216         'token' => [
217           'description' => "A string token generated against the current user's session id and the batch id, used to ensure that only the user who submitted the batch can effectively access it.",
218           'type' => 'varchar_ascii',
219           'length' => 64,
220           'not null' => TRUE,
221         ],
222         'timestamp' => [
223           'description' => 'A Unix timestamp indicating when this batch was submitted for processing. Stale batches are purged at cron time.',
224           'type' => 'int',
225           'not null' => TRUE,
226         ],
227         'batch' => [
228           'description' => 'A serialized array containing the processing data for the batch.',
229           'type' => 'blob',
230           'not null' => FALSE,
231           'size' => 'big',
232         ],
233       ],
234       'primary key' => ['bid'],
235       'indexes' => [
236         'token' => ['token'],
237       ],
238     ];
239   }
240
241 }