Browse Source

Pull the batch out to a separate service.

pull/896/head
Adam Vessey 3 years ago
parent
commit
dacec854a3
No known key found for this signature in database
GPG Key ID: 89B39535BF6D0D39
  1. 6
      islandora.services.yml
  2. 190
      src/Form/AddChildrenWizard/BatchProcessor.php
  3. 66
      src/Form/AddChildrenWizard/FieldTrait.php
  4. 230
      src/Form/AddChildrenWizard/FileSelectionForm.php
  5. 11
      src/Form/AddChildrenWizard/Form.php
  6. 59
      src/Form/AddChildrenWizard/MediaTypeTrait.php
  7. 40
      src/Form/AddChildrenWizard/WizardTrait.php

6
islandora.services.yml

@ -59,3 +59,9 @@ services:
arguments: ['@jwt.authentication.jwt']
tags:
- { name: event_subscriber }
islandora.upload_children.batch_processor:
class: Drupal\islandora\Form\AddChildrenWizard\BatchProcessor
arguments:
- '@entity_type.manager'
- '@database'
- '@current_user'

190
src/Form/AddChildrenWizard/BatchProcessor.php

@ -0,0 +1,190 @@
<?php
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\islandora\IslandoraUtils;
use Drupal\node\NodeInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
/**
* Children addition batch processor.
*/
class BatchProcessor {
use FieldTrait;
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|null
*/
protected ?EntityTypeManagerInterface $entityTypeManager;
/**
* The database connection serivce.
*
* @var \Drupal\Core\Database\Connection|null
*/
protected ?Connection $database;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountProxyInterface|null
*/
protected ?AccountProxyInterface $currentUser;
/**
* Constructor.
*/
public function __construct(
EntityTypeManagerInterface $entity_type_manager,
Connection $database,
AccountProxyInterface $current_user
) {
$this->entityTypeManager = $entity_type_manager;
$this->database = $database;
$this->currentUser = $current_user;
}
/**
* Implements callback_batch_operation() for our child addition batch.
*/
public function batchOperation($delta, $info, array $values, &$context) {
$transaction = $this->database->startTransaction();
try {
$taxonomy_term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
/** @var \Drupal\file\FileInterface $file */
$file = $this->entityTypeManager->getStorage('file')->load($info['target_id']);
$file->setPermanent();
if ($file->save() !== SAVED_UPDATED) {
throw new \Exception("Failed to update file '{$file->id()}' to be permanent.");
}
$node_storage = $this->entityTypeManager->getStorage('node');
$parent = $node_storage->load($values['node']);
// Create a node (with the filename?) (and also belonging to the target
// node).
/** @var \Drupal\node\NodeInterface $node */
$node = $node_storage->create([
'type' => $values['bundle'],
'title' => $file->getFilename(),
IslandoraUtils::MEMBER_OF_FIELD => $parent,
'uid' => $this->currentUser->id(),
'status' => NodeInterface::PUBLISHED,
IslandoraUtils::MODEL_FIELD => ($values['model'] ?
$taxonomy_term_storage->load($values['model']) :
NULL),
]);
if ($node->save() !== SAVED_NEW) {
throw new \Exception("Failed to create node for file '{$file->id()}'.");
}
// Create a media with the file attached and also pointing at the node.
$field = $this->getField($values);
$media_values = array_merge(
[
'bundle' => $values['media_type'],
'name' => $file->getFilename(),
IslandoraUtils::MEDIA_OF_FIELD => $node,
IslandoraUtils::MEDIA_USAGE_FIELD => ($values['use'] ?
$taxonomy_term_storage->loadMultiple($values['use']) :
NULL),
'uid' => $this->currentUser->id(),
// XXX: Published... no constant?
'status' => 1,
],
[
$field->getName() => [
$info,
],
]
);
$media = $this->entityTypeManager->getStorage('media')->create($media_values);
if ($media->save() !== SAVED_NEW) {
throw new \Exception("Failed to create media for file '{$file->id()}.");
}
$context['results'] = array_merge_recursive($context['results'], [
'validation_violations' => $this->validationClassification([
$file,
$media,
$node,
]),
]);
$context['results']['count'] += 1;
}
catch (HttpExceptionInterface $e) {
$transaction->rollBack();
throw $e;
}
catch (\Exception $e) {
$transaction->rollBack();
throw new HttpException(500, $e->getMessage(), $e);
}
}
/**
* Helper to bulk process validatable entities.
*
* @param array $entities
* An array of entities to scan for validation violations.
*
* @return array
* An associative array mapping entity type IDs to entity IDs to a count
* of validation violations found on then given entity.
*/
protected function validationClassification(array $entities) {
$violations = [];
foreach ($entities as $entity) {
$entity_violations = $entity->validate();
if ($entity_violations->count() > 0) {
$violations[$entity->getEntityTypeId()][$entity->id()] = $entity_violations->count();
}
}
return $violations;
}
/**
* Implements callback_batch_finished() for our child addition batch.
*/
public function batchProcessFinished($success, $results, $operations): void {
if ($success) {
$this->messenger()->addMessage($this->formatPlural(
$results['count'],
'Added 1 child node.',
'Added @count child nodes.'
));
foreach ($results['validation_violations'] ?? [] as $entity_type => $info) {
foreach ($info as $id => $count) {
$this->messenger()->addWarning($this->formatPlural(
$count,
'1 validation error present in <a href=":uri">bulk created entity of type %type, with ID %id</a>.',
'@count validation errors present in <a href=":uri">bulk created entity of type %type, with ID %id</a>.',
[
'%type' => $entity_type,
':uri' => Url::fromRoute("entity.{$entity_type}.canonical", [$entity_type => $id])->toString(),
'%id' => $id,
]
));
}
}
}
else {
$this->messenger()->addError($this->t('Encountered an error when adding children.'));
}
}
}

66
src/Form/AddChildrenWizard/FieldTrait.php

@ -0,0 +1,66 @@
<?php
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Field lookup helper trait.
*/
trait FieldTrait {
use MediaTypeTrait;
/**
* The entity field manager service.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface|null
*/
protected ?EntityFieldManagerInterface $entityFieldManager;
/**
* Helper; get field instance, given our required values.
*
* @param array $values
* See ::getMediaType() for which values are required.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface
* The target field.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getField(array $values): FieldDefinitionInterface {
$media_type = $this->getMediaType($values);
$media_source = $media_type->getSource();
$source_field = $media_source->getSourceFieldDefinition($media_type);
$fields = $this->entityFieldManager()->getFieldDefinitions('media', $media_type->id());
return $fields[$source_field->getFieldStorageDefinition()->getName()] ??
$media_source->createSourceField();
}
/**
* Lazy-initialization of the entity field manager service.
*
* @return \Drupal\Core\Entity\EntityFieldManagerInterface
* The entity field manager service.
*/
protected function entityFieldManager() : EntityFieldManagerInterface {
if ($this->entityFieldManager === NULL) {
$this->setEntityFieldManager(\Drupal::service('entity_field.manager'));
}
return $this->entityFieldManager;
}
/**
* Setter for entity field manager.
*/
public function setEntityFieldManager(EntityFieldManagerInterface $entity_field_manager) : self {
$this->entityFieldManager = $entity_field_manager;
return $this;
}
}

230
src/Form/AddChildrenWizard/FileSelectionForm.php

@ -2,11 +2,8 @@
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Batch\BatchBuilder;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
@ -15,38 +12,19 @@ use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\islandora\IslandoraUtils;
use Drupal\media\MediaTypeInterface;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
/**
* Children addition wizard's second step.
*/
class FileSelectionForm extends FormBase {
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|null
*/
protected ?EntityTypeManagerInterface $entityTypeManager;
/**
* The widget plugin manager service.
*
* @var \Drupal\Core\Field\WidgetPluginManager|null
*/
protected ?PluginManagerInterface $widgetPluginManager;
/**
* The entity field manager service.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface|null
*/
protected ?EntityFieldManagerInterface $entityFieldManager;
use WizardTrait {
WizardTrait::getField as doGetField;
WizardTrait::getMediaType as doGetMediaType;
WizardTrait::getWidget as doGetWidget;
}
/**
* The database connection serivce.
@ -62,6 +40,13 @@ class FileSelectionForm extends FormBase {
*/
protected ?AccountProxyInterface $currentUser;
/**
* The batch processor service.
*
* @var \Drupal\islandora\Form\AddChildrenWizard\BatchProcessor|null
*/
protected ?BatchProcessor $batchProcessor;
/**
* {@inheritdoc}
*/
@ -74,6 +59,8 @@ class FileSelectionForm extends FormBase {
$instance->database = $container->get('database');
$instance->currentUser = $container->get('current_user');
$instance->batchProcessor = $container->get('islandora.upload_children.batch_processor');
return $instance;
}
@ -100,24 +87,6 @@ class FileSelectionForm extends FormBase {
return $this->doGetMediaType($form_state->getTemporaryValue('wizard'));
}
/**
* Helper; get media type, given our required values.
*
* @param array $values
* An associative array which must contain at least:
* - media_type: The machine name of the media type to load.
*
* @return \Drupal\media\MediaTypeInterface
* The loaded media type.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function doGetMediaType(array $values): MediaTypeInterface {
/** @var \Drupal\media\MediaTypeInterface $media_type */
return $this->entityTypeManager->getStorage('media_type')->load($values['media_type']);
}
/**
* Helper; get field instance, based off discovering from form state.
*
@ -136,26 +105,6 @@ class FileSelectionForm extends FormBase {
return $field;
}
/**
* Helper; get field instance, given our required values.
*
* @param array $values
* See ::doGetMediaType() for which values are required.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface
* The target field.
*/
protected function doGetField(array $values): FieldDefinitionInterface {
$media_type = $this->doGetMediaType($values);
$media_source = $media_type->getSource();
$source_field = $media_source->getSourceFieldDefinition($media_type);
$fields = $this->entityFieldManager->getFieldDefinitions('media', $media_type->id());
return $fields[$source_field->getFieldStorageDefinition()->getName()] ??
$media_source->createSourceField();
}
/**
* Helper; get widget for the field, based on discovering from form state.
*
@ -169,23 +118,6 @@ class FileSelectionForm extends FormBase {
return $this->doGetWidget($this->getField($form_state));
}
/**
* Helper; get the base widget for the given field.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field
* The field for which get obtain the widget.
*
* @return \Drupal\Core\Field\WidgetInterface
* The widget.
*/
protected function doGetWidget(FieldDefinitionInterface $field): WidgetInterface {
return $this->widgetPluginManager->getInstance([
'field_definition' => $field,
'form_mode' => 'default',
'prepare' => TRUE,
]);
}
/**
* {@inheritdoc}
*/
@ -218,12 +150,12 @@ class FileSelectionForm extends FormBase {
$builder = (new BatchBuilder())
->setTitle($this->t('Creating children...'))
->setInitMessage($this->t('Initializing...'))
->setFinishCallback([$this, 'batchProcessFinished']);
->setFinishCallback([$this->batchProcessor, 'batchProcessFinished']);
$values = $form_state->getValue($this->doGetField($cached_values)->getName());
$massaged_values = $widget->massageFormValues($values, $form, $form_state);
foreach ($massaged_values as $delta => $file) {
$builder->addOperation(
[$this, 'batchProcess'],
[$this->batchProcessor, 'batchOperation'],
[$delta, $file, $cached_values]
);
}
@ -231,134 +163,4 @@ class FileSelectionForm extends FormBase {
$form_state->setRedirectUrl(Url::fromUri("internal:/node/{$cached_values['node']}/members"));
}
/**
* Implements callback_batch_operation() for our child addition batch.
*/
public function batchProcess($delta, $info, array $values, &$context) {
$transaction = $this->database->startTransaction();
try {
$taxonomy_term_storage = $this->entityTypeManager->getStorage('taxonomy_term');
/** @var \Drupal\file\FileInterface $file */
$file = $this->entityTypeManager->getStorage('file')->load($info['target_id']);
$file->setPermanent();
if ($file->save() !== SAVED_UPDATED) {
throw new \Exception("Failed to update file '{$file->id()}' to be permanent.");
}
$node_storage = $this->entityTypeManager->getStorage('node');
$parent = $node_storage->load($values['node']);
// Create a node (with the filename?) (and also belonging to the target
// node).
/** @var \Drupal\node\NodeInterface $node */
$node = $node_storage->create([
'type' => $values['bundle'],
'title' => $file->getFilename(),
IslandoraUtils::MEMBER_OF_FIELD => $parent,
'uid' => $this->currentUser->id(),
'status' => NodeInterface::PUBLISHED,
IslandoraUtils::MODEL_FIELD => ($values['model'] ?
$taxonomy_term_storage->load($values['model']) :
NULL),
]);
if ($node->save() !== SAVED_NEW) {
throw new \Exception("Failed to create node for file '{$file->id()}'.");
}
// Create a media with the file attached and also pointing at the node.
$field = $this->doGetField($values);
$media_values = array_merge(
[
'bundle' => $values['media_type'],
'name' => $file->getFilename(),
IslandoraUtils::MEDIA_OF_FIELD => $node,
IslandoraUtils::MEDIA_USAGE_FIELD => ($values['use'] ?
$taxonomy_term_storage->loadMultiple($values['use']) :
NULL),
'uid' => $this->currentUser->id(),
// XXX: Published... no constant?
'status' => 1,
],
[
$field->getName() => [
$info,
],
]
);
$media = $this->entityTypeManager->getStorage('media')->create($media_values);
if ($media->save() !== SAVED_NEW) {
throw new \Exception("Failed to create media for file '{$file->id()}.");
}
$context['results'] = array_merge_recursive($context['results'], [
'validation_violations' => $this->validationClassification([
$file,
$media,
$node,
]),
]);
$context['results']['count'] += 1;
}
catch (HttpExceptionInterface $e) {
$transaction->rollBack();
throw $e;
}
catch (\Exception $e) {
$transaction->rollBack();
throw new HttpException(500, $e->getMessage(), $e);
}
}
/**
* @param array $entities
*
* @return array
*/
protected function validationClassification(array $entities) {
$violations = [];
foreach ($entities as $entity) {
$entity_violations = $entity->validate();
if ($entity_violations->count() > 0) {
$violations[$entity->getEntityTypeId()][$entity->id()] = $entity_violations->count();
}
}
return $violations;
}
/**
* Implements callback_batch_finished() for our child addition batch.
*/
public function batchProcessFinished($success, $results, $operations): void {
if ($success) {
$this->messenger()->addMessage($this->formatPlural(
$results['count'],
'Added 1 child node.',
'Added @count child nodes.'
));
foreach ($results['validation_violations'] ?? [] as $entity_type => $info) {
foreach ($info as $id => $count) {
$this->messenger()->addWarning($this->formatPlural(
$count,
'1 validation error present in <a href=":uri">bulk created entity of type %type, with ID %id</a>.',
'@count validation errors present in <a href=":uri">bulk created entity of type %type, with ID %id</a>.',
[
'%type' => $entity_type,
':uri' => Url::fromRoute("entity.{$entity_type}.canonical", [$entity_type => $id])->toString(),
'%id' => $id,
]
));
}
}
}
else {
$this->messenger()->addError($this->t('Encountered an error when adding children.'));
}
}
}

11
src/Form/AddChildrenWizard/Form.php

@ -2,10 +2,8 @@
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\TempStore\SharedTempStoreFactory;
@ -56,8 +54,6 @@ class Form extends FormWizardBase {
EventDispatcherInterface $event_dispatcher,
RouteMatchInterface $route_match,
$tempstore_id,
IslandoraUtils $utils,
RouteMatchInterface $current_route_match,
AccountProxyInterface $current_user,
$machine_name = NULL,
$step = NULL
@ -65,9 +61,7 @@ class Form extends FormWizardBase {
parent::__construct($tempstore, $builder, $class_resolver, $event_dispatcher, $route_match, $tempstore_id,
$machine_name, $step);
$this->utils = $utils;
$this->currentRoute = $current_route_match;
$this->nodeId = $this->currentRoute->getParameter('node');
$this->nodeId = $this->routeMatch->getParameter('node');
$this->currentUser = $current_user;
}
@ -78,10 +72,9 @@ class Form extends FormWizardBase {
return array_merge(
parent::getParameters(),
[
'utils' => \Drupal::service('islandora.utils'),
'tempstore_id' => 'islandora.upload_children',
'current_route_match' => \Drupal::service('current_route_match'),
'current_user' => \Drupal::service('current_user'),
'batch_processor' => \Drupal::service('islandora.upload_children.batch_processor'),
]
);
}

59
src/Form/AddChildrenWizard/MediaTypeTrait.php

@ -0,0 +1,59 @@
<?php
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\media\MediaTypeInterface;
/**
* Media type lookup helper trait.
*/
trait MediaTypeTrait {
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|null
*/
protected ?EntityTypeManagerInterface $entityTypeManager;
/**
* Helper; get media type, given our required values.
*
* @param array $values
* An associative array which must contain at least:
* - media_type: The machine name of the media type to load.
*
* @return \Drupal\media\MediaTypeInterface
* The loaded media type.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
*/
protected function getMediaType(array $values): MediaTypeInterface {
/** @var \Drupal\media\MediaTypeInterface $media_type */
return $this->entityTypeManager()->getStorage('media_type')->load($values['media_type']);
}
/**
* Lazy-initialization of the entity type manager service.
*
* @return \Drupal\Core\Entity\EntityTypeManagerInterface
* The entity type manager service.
*/
protected function entityTypeManager() : EntityTypeManagerInterface {
if ($this->entityTypeManager === NULL) {
$this->setEntityTypeManager(\Drupal::service('entity_type.manager'));
}
return $this->entityTypeManager;
}
/**
* Setter for the entity type manager service.
*/
public function setEntityTypeManager(EntityTypeManagerInterface $entity_type_manager) : self {
$this->entityTypeManager = $entity_type_manager;
return $this;
}
}

40
src/Form/AddChildrenWizard/WizardTrait.php

@ -0,0 +1,40 @@
<?php
namespace Drupal\islandora\Form\AddChildrenWizard;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\WidgetInterface;
/**
* Wizard/widget lookup helper trait.
*/
trait WizardTrait {
use FieldTrait;
/**
* The widget plugin manager service.
*
* @var \Drupal\Core\Field\WidgetPluginManager
*/
protected PluginManagerInterface $widgetPluginManager;
/**
* Helper; get the base widget for the given field.
*
* @param \Drupal\Core\Field\FieldDefinitionInterface $field
* The field for which get obtain the widget.
*
* @return \Drupal\Core\Field\WidgetInterface
* The widget.
*/
protected function getWidget(FieldDefinitionInterface $field): WidgetInterface {
return $this->widgetPluginManager->getInstance([
'field_definition' => $field,
'form_mode' => 'default',
'prepare' => TRUE,
]);
}
}
Loading…
Cancel
Save