Skip to content

Commit 80cff8d

Browse files
author
catch
committed
Issue #3510939 by mikelutz, balazswmann, godotislate, mxr576: Avoid registering multiple batch sets with node_access_rebuild()'s batch mode
1 parent 719d084 commit 80cff8d

File tree

4 files changed

+89
-6
lines changed

4 files changed

+89
-6
lines changed

lib/Drupal/Core/Batch/BatchBuilder.php

+50
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,23 @@
1919
* }
2020
* batch_set($batch_builder->toArray());
2121
* @endcode
22+
*
23+
* To prevent duplicate batches from being created inadvertently in the same
24+
* page request, you can use ::isSetIdRegistered and ::registerSetId to check
25+
* and see if this batch has been built before.
26+
* @code
27+
* if (!BatchBuilder::isSetIdRegistered('my_unique_id')) {
28+
* $batch_builder = (new BatchBuilder())
29+
* ->registerSetId('my_unique_id')
30+
* ->setTitle(t('Batch Title'))
31+
* ->setFinishCallback('batch_example_finished_callback')
32+
* ->setInitMessage(t('The initialization message (optional)'));
33+
* foreach ($ids as $id) {
34+
* $batch_builder->addOperation('batch_example_callback', [$id]);
35+
* }
36+
* batch_set($batch_builder->toArray());
37+
* }
38+
* @endcode
2239
*/
2340
class BatchBuilder {
2441

@@ -110,6 +127,13 @@ class BatchBuilder {
110127
*/
111128
protected $queue;
112129

130+
/**
131+
* A static array of custom batch ids.
132+
*
133+
* @var string[]
134+
*/
135+
protected static array $registeredSetIds = [];
136+
113137
/**
114138
* Sets the default values for the batch builder.
115139
*/
@@ -317,6 +341,32 @@ public function addOperation(callable $callback, array $arguments = []) {
317341
return $this;
318342
}
319343

344+
/**
345+
* Checks if a set ID has been registered during this request.
346+
*
347+
* @param string $setId
348+
* The set ID to check.
349+
*
350+
* @return bool
351+
* True if this set ID has been registered.
352+
*/
353+
public static function isSetIdRegistered(string $setId): bool {
354+
return isset(static::$registeredSetIds[$setId]);
355+
}
356+
357+
/**
358+
* Registers a set ID for this batch.
359+
*
360+
* @param string $setId
361+
* The set ID to register.
362+
*
363+
* @return $this
364+
*/
365+
public function registerSetId(string $setId): self {
366+
static::$registeredSetIds[$setId] = TRUE;
367+
return $this;
368+
}
369+
320370
/**
321371
* Converts a \Drupal\Core\Batch\Batch object into an array.
322372
*

modules/node/node.module

+11-6
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,9 @@ function node_access_needs_rebuild($rebuild = NULL) {
532532
* has a large number of nodes). hook_update_N() and any form submit handler
533533
* are safe contexts to use the 'batch mode'. Less decidable cases (such as
534534
* calls from hook_user(), hook_taxonomy(), etc.) might consider using the
535-
* non-batch mode. Defaults to FALSE.
535+
* non-batch mode. Defaults to FALSE. Calling this method multiple times in
536+
* the same request with $batch_mode set to TRUE will only result in one batch
537+
* set being added.
536538
*
537539
* @see node_access_needs_rebuild()
538540
*/
@@ -544,11 +546,14 @@ function node_access_rebuild($batch_mode = FALSE) {
544546
// Only recalculate if the site is using a node_access module.
545547
if (\Drupal::moduleHandler()->hasImplementations('node_grants')) {
546548
if ($batch_mode) {
547-
$batch_builder = (new BatchBuilder())
548-
->setTitle(t('Rebuilding content access permissions'))
549-
->addOperation('_node_access_rebuild_batch_operation', [])
550-
->setFinishCallback('_node_access_rebuild_batch_finished');
551-
batch_set($batch_builder->toArray());
549+
if (!BatchBuilder::isSetIdRegistered(__FUNCTION__)) {
550+
$batch_builder = (new BatchBuilder())
551+
->setTitle(t('Rebuilding content access permissions'))
552+
->addOperation('_node_access_rebuild_batch_operation', [])
553+
->setFinishCallback('_node_access_rebuild_batch_finished')
554+
->registerSetId(__FUNCTION__);
555+
batch_set($batch_builder->toArray());
556+
}
552557
}
553558
else {
554559
// Try to allocate enough time to rebuild node grants

modules/node/tests/src/Kernel/NodeAccessTest.php

+15
Original file line numberDiff line numberDiff line change
@@ -156,4 +156,19 @@ public function testQueryWithBaseTableJoin(): void {
156156
$this->assertEquals(4, $query->countQuery()->execute()->fetchField());
157157
}
158158

159+
/**
160+
* Tests that multiple calls to node_access_rebuild only result in one batch.
161+
*/
162+
public function testDuplicateBatchRebuild(): void {
163+
$this->enableModules(['node_access_test']);
164+
$batch = batch_get();
165+
$this->assertEmpty($batch);
166+
node_access_rebuild(TRUE);
167+
$batch = batch_get();
168+
$this->assertCount(1, $batch['sets']);
169+
node_access_rebuild(TRUE);
170+
$batch = batch_get();
171+
$this->assertCount(1, $batch['sets']);
172+
}
173+
159174
}

tests/Drupal/Tests/Core/Batch/BatchBuilderTest.php

+13
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,19 @@ public function testAddOperation(): void {
248248
], $batch['operations']);
249249
}
250250

251+
/**
252+
* Tests registering IDs of built batches.
253+
*
254+
* @covers ::isSetIdRegistered
255+
* @covers ::registerSetId
256+
*/
257+
public function testRegisterIds(): void {
258+
$setId = $this->randomMachineName();
259+
$this->assertFalse(BatchBuilder::isSetIdRegistered($setId));
260+
(new BatchBuilder())->registerSetId($setId);
261+
$this->assertTrue(BatchBuilder::isSetIdRegistered($setId));
262+
}
263+
251264
/**
252265
* Empty callback for the tests.
253266
*

0 commit comments

Comments
 (0)