# Events Reference

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

## Event Overview

```mermaid
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
<?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

```yaml
# 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
<?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
<?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):

```php
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
