diff --git a/modules/custom/activity_creator/activity_creator.install b/modules/custom/activity_creator/activity_creator.install index a38b43f5b75..db8bca0e2d6 100644 --- a/modules/custom/activity_creator/activity_creator.install +++ b/modules/custom/activity_creator/activity_creator.install @@ -5,9 +5,7 @@ * Installation code for the activity_creator module. */ -use Drupal\activity_creator\ActivityInterface; use Drupal\Core\Database\Database; -use Drupal\Core\Site\Settings; /** * Implements hook_schema(). @@ -142,56 +140,9 @@ function activity_creator_update_8801() { /** * Remove activities notification status if related entity not exist. + * + * @see activity_creator_post_update_8802_remove_activities_with_no_related_entities() */ function activity_creator_update_8802(&$sandbox) { - $database = Drupal::database(); - if (!isset($sandbox['total'])) { - $query = $database->select('activity_notification_status', 'ans'); - $query->addExpression('COUNT(*)'); - $count = $query->execute()->fetchField(); - $sandbox['total'] = $count; - $sandbox['current'] = 0; - } - - // Activities per one batch operation. - $activities_per_batch = Settings::get('activity_update_batch_size', 100); - - // Get activity IDs. - $aids = $database - ->select('activity_notification_status', 'ans') - ->fields('ans', ['aid']) - ->range($sandbox['current'], $sandbox['current'] + $activities_per_batch) - ->execute() - ->fetchCol(); - - // Get activity storage. - $activity_storage = $activity = Drupal::entityTypeManager() - ->getStorage('activity'); - - foreach ($aids as $aid) { - /** @var \Drupal\activity_creator\ActivityInterface $activity */ - $activity = $activity_storage->load($aid); - - if (!$activity instanceof ActivityInterface) { - $aids_for_delete[] = $aid; - } - elseif (is_null($activity->getRelatedEntity())) { - $activity_storage->delete([$activity]); - $aids_for_delete[] = $aid; - } - - $sandbox['current']++; - } - - if (!empty($aids_for_delete)) { - Drupal::service('activity_creator.activity_notifications') - ->deleteNotificationsbyIds($aids_for_delete); - } - - if ($sandbox['total'] == 0) { - $sandbox['#finished'] = 1; - } - else { - $sandbox['#finished'] = ($sandbox['current'] / $sandbox['total']); - } + // Removed in https://www.drupal.org/project/social/issues/3171563. } diff --git a/modules/custom/activity_creator/activity_creator.post_update.php b/modules/custom/activity_creator/activity_creator.post_update.php index 8d55e57f8c9..469ef17c29a 100644 --- a/modules/custom/activity_creator/activity_creator.post_update.php +++ b/modules/custom/activity_creator/activity_creator.post_update.php @@ -5,6 +5,9 @@ * Contains post update hook implementations. */ +use Drupal\activity_creator\ActivityInterface; +use Drupal\Core\Site\Settings; + /** * Migrate all the activity status information to new table. * @@ -67,3 +70,124 @@ function activity_creator_post_update_8001_one_to_many_activities(&$sandbox) { // Print some progress. return t('@count activities data has been migrated to activity_notification_table.', ['@count' => $sandbox['current']]); } + +/** + * Remove activities notification status if related entity not exist. + * + * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException + * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException + * @throws \Drupal\Core\Entity\EntityStorageException + */ +function activity_creator_post_update_8802_remove_activities_with_no_related_entities(&$sandbox) { + // On the first run, we gather all of our initial + // data as well as initialize all of our sandbox variables to be used in + // managing the future batch requests. + if (!isset($sandbox['progress'])) { + // To set up our batch process, we need to collect all of the + // necessary data during this initialization step, which will + // only ever be run once. + // We start the batch by running some SELECT queries up front + // as concisely as possible. The results of these expensive queries + // will be cached by the Batch API so we do not have to look up + // this data again during each iteration of the batch. + $database = \Drupal::database(); + + // Get activity IDs. + /** @var \Drupal\Core\Database\Query\Select $query */ + $activity_ids = $database->select('activity_notification_status', 'ans') + ->fields('ans', ['aid']) + ->execute() + ->fetchCol(); + + // Now we initialize the sandbox variables. + // These variables will persist across the Batch API’s subsequent calls + // to our update hook, without us needing to make those initial + // expensive SELECT queries above ever again. + // 'max' is the number of total records we’ll be processing. + $sandbox['max'] = count($activity_ids); + // If 'max' is empty, we have nothing to process. + if (empty($sandbox['max'])) { + $sandbox['#finished'] = 1; + return; + } + + // 'progress' will represent the current progress of our processing. + $sandbox['progress'] = 0; + + // 'activities_per_batch' is a custom amount that we’ll use to limit + // how many activities we’re processing in each batch. + // This is a large part of how we limit expensive batch operations. + $sandbox['activities_per_batch'] = Settings::get('activity_update_batch_size', 100); + + // 'activities_id' will store the activity IDs from activity notification + // table that we just queried for above during this initialization phase. + $sandbox['activities_id'] = $activity_ids; + } + + // Initialization code done. The following code will always run: + // both during the first run AND during any subsequent batches. + // Now let’s remove the missing activity ids. + $activity_storage = \Drupal::entityTypeManager()->getStorage('activity'); + + // Calculates current batch range. + $range_end = $sandbox['progress'] + $sandbox['activities_per_batch']; + if ($range_end > $sandbox['max']) { + $range_end = $sandbox['max']; + } + + // Loop over current batch range, creating a new BAR node each time. + for ($i = $sandbox['progress']; $i < $range_end; $i++) { + + // Take activity ids from $sandbox['activities_id']. + $activity_id = $sandbox['activities_id'][$i]; + + /** @var \Drupal\activity_creator\ActivityInterface $activity */ + $activity = $activity_storage->load($activity_id); + + // Add invalid ids for deletion. + if (!$activity instanceof ActivityInterface) { + $aids_for_delete[] = $activity_id; + } + // Add not required $activity. + elseif (is_null($activity->getRelatedEntity())) { + $entity_ids[] = $activity_id; + $activities_for_delete[$activity_id] = $activity; + } + else { + // If the database has more than 100K of activities to be processed + // Drupal will generate a lot of static cache, as we are using load + // function above. So, it is necessary to reset cache in order to + // prevent memory outage. + $activity_storage->resetCache([$activity_id]); + } + } + + // Remove notifications. + if (!empty($aids_for_delete)) { + \Drupal::service('activity_creator.activity_notifications') + ->deleteNotificationsbyIds($aids_for_delete); + } + + // Delete not required activity entities. + if (!empty($activities_for_delete)) { + $activity_storage->delete($activities_for_delete); + } + + // Update the batch variables to track our progress. + // We can calculate our current progress via a mathematical fraction. + // Drupal’s Batch API will stop executing our update hook as soon as + // $sandbox['#finished'] == 1 (viz., it evaluates to TRUE). + $sandbox['progress'] = $range_end; + $progress_fraction = $sandbox['progress'] / $sandbox['max']; + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : $progress_fraction; + + // While processing our batch requests, we can send a helpful message + // to the command line, so developers can track the batch progress. + if (function_exists('drush_print')) { + drush_print('Progress: ' . (round($progress_fraction * 100)) . '% (' . + $sandbox['progress'] . ' of ' . $sandbox['max'] . ' activities processed)'); + } + + // That’s it! + // The update hook and Batch API manage the rest of the process. +}