Skip to content

Events Reference#

Entity Builder uses Symfony events to allow modules to hook into the operation lifecycle.

Event Overview#

sequenceDiagram
    participant S as Subscriber
    participant VM as ValidationManager
    participant OP as OperationProcessor
    participant O as Operation

    Note over VM,O: Validation Phase
    VM->>S: PRE_VALIDATE event
    S-->>VM: (may modify)
    VM->>O: validate()
    VM->>S: POST_VALIDATE event
    S-->>VM: (may add errors)

    Note over OP,O: Execution Phase
    OP->>S: PRE_EXECUTE event
    S-->>OP: (may cancel)
    OP->>O: execute()
    OP->>S: POST_EXECUTE event
    S-->>OP: (may log/react)

Event Constants#

All events are defined in Drupal\eb\Event\OperationEvents:

Constant Event Name Description
PRE_VALIDATE eb.operation.pre_validate Before operation validation
POST_VALIDATE eb.operation.post_validate After operation validation
PRE_EXECUTE eb.operation.pre_execute Before operation execution
POST_EXECUTE eb.operation.post_execute After operation execution

Event Classes#

OperationPreValidateEvent#

Dispatched before an operation is validated.

Namespace: Drupal\eb\Event\OperationPreValidateEvent

Methods:

Method Return Type Description
getOperation() OperationInterface The operation being validated

Use Cases:

  • Modify operation data before validation
  • Add contextual information

OperationPostValidateEvent#

Dispatched after an operation is validated.

Namespace: Drupal\eb\Event\OperationPostValidateEvent

Methods:

Method Return Type Description
getOperation() OperationInterface The validated operation
getResult() ValidationResult Validation result
addError() void Add validation error
addWarning() void Add validation warning

Use Cases:

  • Add custom validation rules
  • Log validation results
  • Enforce business rules

OperationPreExecuteEvent#

Dispatched before an operation is executed.

Namespace: Drupal\eb\Event\OperationPreExecuteEvent

Methods:

Method Return Type Description
getOperation() OperationInterface The operation to execute
cancel(string $message) void Cancel execution
isCancelled() bool Check if cancelled
getCancellationMessage() string Get cancellation message

Use Cases:

  • Prevent execution based on external conditions
  • Add pre-execution logging
  • Trigger external systems

OperationPostExecuteEvent#

Dispatched after an operation is executed.

Namespace: Drupal\eb\Event\OperationPostExecuteEvent

Methods:

Method Return Type Description
getOperation() OperationInterface The executed operation
getResult() ExecutionResult Execution result

Use Cases:

  • Log execution results
  • Trigger cache invalidation
  • Notify external systems
  • Update related entities

Creating an Event Subscriber#

Step 1: Create the Subscriber Class#

<?php

namespace Drupal\my_module\EventSubscriber;

use Drupal\eb\Event\OperationEvents;
use Drupal\eb\Event\OperationPreExecuteEvent;
use Drupal\eb\Event\OperationPostExecuteEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Subscribes to Entity Builder operation events.
 */
class MyOperationSubscriber implements EventSubscriberInterface {

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      OperationEvents::PRE_EXECUTE => ['onPreExecute', 0],
      OperationEvents::POST_EXECUTE => ['onPostExecute', 0],
    ];
  }

  /**
   * Reacts before operation execution.
   */
  public function onPreExecute(OperationPreExecuteEvent $event): void {
    $operation = $event->getOperation();
    $operationType = $operation->getDataValue('operation');

    // Example: Prevent bundle deletion on production
    if ($operationType === 'delete_bundle' && $this->isProduction()) {
      $event->cancel('Bundle deletion is disabled on production.');
    }
  }

  /**
   * Reacts after operation execution.
   */
  public function onPostExecute(OperationPostExecuteEvent $event): void {
    $operation = $event->getOperation();
    $result = $event->getResult();

    if ($result->isSuccess()) {
      // Log to external system
      $this->logToExternalSystem($operation);
    }
  }

  /**
   * Check if running on production.
   */
  protected function isProduction(): bool {
    // Implementation
    return FALSE;
  }

  /**
   * Log to external system.
   */
  protected function logToExternalSystem($operation): void {
    // Implementation
  }

}

Step 2: Register as a Service#

1
2
3
4
5
6
# my_module.services.yml
services:
  my_module.operation_subscriber:
    class: Drupal\my_module\EventSubscriber\MyOperationSubscriber
    tags:
      - { name: event_subscriber }

Example: Custom Validation#

Add custom validation rules:

<?php

namespace Drupal\my_module\EventSubscriber;

use Drupal\eb\Event\OperationEvents;
use Drupal\eb\Event\OperationPostValidateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Adds custom validation rules.
 */
class CustomValidationSubscriber implements EventSubscriberInterface {

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      OperationEvents::POST_VALIDATE => ['onPostValidate', 0],
    ];
  }

  /**
   * Adds custom validation.
   */
  public function onPostValidate(OperationPostValidateEvent $event): void {
    $operation = $event->getOperation();
    $operationType = $operation->getDataValue('operation');

    if ($operationType === 'create_bundle') {
      $bundleId = $operation->getDataValue('bundle_id');

      // Enforce naming convention
      if (!str_starts_with($bundleId, 'myprefix_')) {
        $event->addError(
          'Bundle ID must start with "myprefix_".',
          'bundle_id',
          'naming_convention'
        );
      }
    }
  }

}

Example: Audit Logging#

Log all operations to an external system:

<?php

namespace Drupal\my_module\EventSubscriber;

use Drupal\eb\Event\OperationEvents;
use Drupal\eb\Event\OperationPostExecuteEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Logs operations to external audit system.
 */
class AuditLogSubscriber implements EventSubscriberInterface {

  /**
   * Constructor.
   */
  public function __construct(
    protected LoggerInterface $logger,
  ) {}

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents(): array {
    return [
      OperationEvents::POST_EXECUTE => ['onPostExecute', -100],
    ];
  }

  /**
   * Logs operation result.
   */
  public function onPostExecute(OperationPostExecuteEvent $event): void {
    $operation = $event->getOperation();
    $result = $event->getResult();

    $this->logger->info('EB Operation: @type - @status', [
      '@type' => $operation->getDataValue('operation'),
      '@status' => $result->isSuccess() ? 'success' : 'failed',
    ]);
  }

}

Event Priority#

Subscribers can specify priority (higher runs first):

1
2
3
4
5
6
7
8
9
public static function getSubscribedEvents(): array {
  return [
    // Run early (priority 100)
    OperationEvents::PRE_EXECUTE => ['onPreExecute', 100],

    // Run late (priority -100)
    OperationEvents::POST_EXECUTE => ['onPostExecute', -100],
  ];
}

Best Practices#

  1. Keep subscribers lightweight - Don't perform expensive operations
  2. Use appropriate priority - Let core handlers run first
  3. Handle exceptions - Don't break the execution flow
  4. Log sparingly - Avoid excessive logging in production
  5. Test thoroughly - Events can have unexpected side effects