image_to_media_swapper-2.x-dev/src/BatchHandler.php
src/BatchHandler.php
<?php
declare(strict_types=1);
namespace Drupal\image_to_media_swapper;
/**
* Handles batch processing for converting image fields to media.
*/
class BatchHandler {
/**
* Processes a chunk of entities to convert image fields to media.
*
* @throws \Drupal\Core\Entity\EntityStorageException
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
public static function processChunk(string $fieldSelector, array $entity_ids, string $category, array &$context): void {
/** @var \Drupal\image_to_media_swapper\BatchProcessorService $processor */
$processor = \Drupal::service('image_to_media_swapper.batch_processor_service');
$entity_type_id = explode('.', $fieldSelector)[0];
$storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);
/** @var \Drupal\Core\Entity\FieldableEntityInterface[] $entities */
$entities = $storage->loadMultiple($entity_ids);
// Store all attempted entities before processing.
$allAttemptedEntities = $entities;
$converted = $processor->processContentByCategory($fieldSelector, $entities, $category);
// Identify failed entities by comparing attempted vs successful.
$convertedIds = array_map(fn($entity) => $entity->id(), $converted);
$failedEntities = array_filter($allAttemptedEntities, fn($entity) => !in_array($entity->id(), $convertedIds));
if (!isset($context['results']['processed'])) {
$context['results']['processed'] = 0;
$context['results']['updated_entities'] = [];
$context['results']['failed_entities'] = [];
$context['results']['group'] = $category;
}
foreach ($converted as $entity) {
$context['results']['processed']++;
$context['results']['updated_entities'][] = [
'entity_type' => $entity->getEntityTypeId(),
'bundle' => $entity->bundle(),
'id' => $entity->id(),
];
}
// Track failed entities that weren't successfully converted.
foreach ($failedEntities as $failedEntity) {
$context['results']['failed_entities'][] = [
'entity_type' => $failedEntity->getEntityTypeId(),
'bundle' => $failedEntity->bundle(),
'id' => $failedEntity->id(),
];
// Create failed swap record.
$processor->createSwapRecord($fieldSelector, $failedEntity, $category, 'failed', [
'error_message' => 'Failed to convert file to media entity.',
]);
}
$totalProcessed = count($converted) + count($failedEntities);
$context['message'] = t('Processed chunk of @total entities (@converted converted, @failed failed).', [
'@total' => $totalProcessed,
'@converted' => count($converted),
'@failed' => count($failedEntities),
]);
}
/**
* Processes a single MediaSwapRecord entity.
*
* @param int $record_id
* The ID of the MediaSwapRecord to process.
* @param array &$context
* The batch context.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public static function processRecord(int $record_id, array &$context): void {
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager */
$entityTypeManager = \Drupal::service('entity_type.manager');
/** @var \Drupal\image_to_media_swapper\BatchProcessorService $processor */
$processor = \Drupal::service('image_to_media_swapper.batch_processor_service');
// Initialize results if needed.
if (!isset($context['results']['processed'])) {
$context['results']['processed'] = 0;
$context['results']['successful'] = 0;
$context['results']['failed'] = 0;
$context['results']['skipped'] = 0;
}
try {
// Load the MediaSwapRecord.
$storage = $entityTypeManager->getStorage('media_swap_record');
/** @var \Drupal\image_to_media_swapper\Entity\MediaSwapRecordInterface $record */
$record = $storage->load($record_id);
if (!$record) {
$context['results']['failed']++;
$context['message'] = t('Record with ID @id not found.', ['@id' => $record_id]);
return;
}
// Skip if the record is not pending.
if ($record->getStatus() !== 'pending') {
$context['results']['skipped']++;
$context['message'] = t('Skipped record @id (status: @status).', [
'@id' => $record_id,
'@status' => $record->getStatus(),
]);
return;
}
// Get the target entity.
$entityTypeId = $record->getTargetEntityType();
$entityId = $record->getTargetEntityId();
$entity = $entityTypeManager->getStorage($entityTypeId)->load($entityId);
if (!$entity) {
$record->setProcessingStatus('failed');
$record->setErrorMessage('Target entity no longer exists.');
$record->save();
$context['results']['failed']++;
$context['message'] = t('Failed to process record @id: Target entity not found.', ['@id' => $record_id]);
return;
}
// Process the entity.
$fieldSelector = $record->getFieldSelector();
$category = $record->getBatchCategory();
$entities = [$entity->id() => $entity];
$result = $processor->processContentByCategory($fieldSelector, $entities, $category);
if (!empty($result)) {
$record->setProcessingStatus('completed');
$record->save();
$context['results']['successful']++;
$context['results']['processed']++;
$context['message'] = t('Successfully processed record @id.', ['@id' => $record_id]);
}
else {
$record->setProcessingStatus('failed');
$record->setErrorMessage('No changes were made to the content.');
$record->save();
$context['results']['failed']++;
$context['message'] = t('Failed to process record @id: No changes made.', ['@id' => $record_id]);
}
}
catch (\Exception $e) {
// Handle any exceptions.
if (isset($record)) {
$record->setProcessingStatus('failed');
$record->setErrorMessage($e->getMessage());
$record->save();
}
$context['results']['failed']++;
$context['message'] = t('Error processing record @id: @message', [
'@id' => $record_id,
'@message' => $e->getMessage(),
]);
// Log the error.
\Drupal::logger('image_to_media_swapper')->error('Error processing record @id: @message', [
'@id' => $record_id,
'@message' => $e->getMessage(),
]);
}
}
/**
* Handles the completion of the batch process.
*
* @param bool $success
* The success status of the batch operation.
* @param array $results
* The results of the batch operation.
* @param array $operations
* The operations that were performed during the batch.
*/
public static function batchFinished(bool $success, array $results, array $operations): void {
$messenger = \Drupal::messenger();
if ($success) {
$convertedCount = $results['processed'] ?? 0;
$failedCount = count($results['failed_entities'] ?? []);
$totalCount = $convertedCount + $failedCount;
if (empty($totalCount)) {
$messenger->addError(t('No operations were performed.'));
return;
}
if ($failedCount > 0) {
$messenger->addStatus(t('Successfully processed @converted items. @failed items failed processing.', [
'@converted' => $convertedCount,
'@failed' => $failedCount,
]));
$messenger->addWarning(t('@failed entities could not be converted. Check the media swap records for details.', [
'@failed' => $failedCount,
]));
}
else {
$messenger->addStatus(t('Successfully processed @count items.', ['@count' => $convertedCount]));
}
// Handle results from processRecord method.
if (isset($results['successful']) || isset($results['failed']) || isset($results['skipped'])) {
$successful = $results['successful'] ?? 0;
$failed = $results['failed'] ?? 0;
$skipped = $results['skipped'] ?? 0;
if ($successful > 0 || $failed > 0 || $skipped > 0) {
$messenger->addStatus(t('Record processing complete: @successful successful, @failed failed, @skipped skipped.', [
'@successful' => $successful,
'@failed' => $failed,
'@skipped' => $skipped,
]));
}
}
}
else {
$messenger->addError(t('The batch process encountered an error.'));
}
}
}
