Skip to content

Developer Getting Started#

Learn how to extend Entity Builder with custom operations, validators, and extensions.

Architecture Overview#

Entity Builder uses three plugin types:

Plugin Type Purpose Manager
EbOperation Execute operations (create, update, delete) plugin.manager.eb_operation
EbValidator Validate operations (cross-cutting concerns) plugin.manager.eb_validator
EbExtension Add custom YAML keys, operations, dependencies plugin.manager.eb_extension

Development Environment Setup#

1. Clone the Repository#

cd web/modules/custom
git clone [repository-url] eb

2. Install Dependencies#

cd eb
composer install

3. Enable Development Mode#

Edit eb.settings.yml:

1
2
3
eb.settings:
  debug_mode: true
  log_operations: true

4. Run Tests#

1
2
3
4
5
6
7
8
# Unit tests
./vendor/bin/phpunit tests/src/Unit

# Kernel tests
./vendor/bin/phpunit tests/src/Kernel

# All tests
./vendor/bin/phpunit

Service Overview#

Core Services#

// Operation building
$operationBuilder = \Drupal::service('eb.operation_builder');
$operationDataBuilder = \Drupal::service('eb.operation_data_builder');

// Validation
$validationManager = \Drupal::service('eb.validation_manager');

// Execution
$operationProcessor = \Drupal::service('eb.operation_processor');
$rollbackManager = \Drupal::service('eb.rollback_manager');

// Discovery
$discoveryService = \Drupal::service('eb.discovery_service');

// Parsing
$yamlParser = \Drupal::service('eb.yaml_parser');

Plugin Managers#

1
2
3
4
5
6
7
8
// Operations
$operationManager = \Drupal::service('plugin.manager.eb_operation');

// Validators
$validatorManager = \Drupal::service('plugin.manager.eb_validator');

// Extensions
$extensionManager = \Drupal::service('plugin.manager.eb_extension');

Key Interfaces#

OperationInterface#

All operations must implement OperationInterface:

1
2
3
4
5
6
interface OperationInterface {
    public function setData(array $data): void;
    public function getData(): array;
    public function getDataValue(string $key, mixed $default = NULL): mixed;
    public function validate(): ValidationResult;
}

FullOperationInterface#

Operations that execute changes implement FullOperationInterface:

1
2
3
interface FullOperationInterface extends OperationInterface {
    public function execute(): ExecutionResult;
}

ReversibleOperationInterface#

Operations that support rollback implement ReversibleOperationInterface:

1
2
3
interface ReversibleOperationInterface {
    public function rollback(): RollbackResult;
}

PreviewableOperationInterface#

Operations that show previews implement PreviewableOperationInterface:

1
2
3
interface PreviewableOperationInterface {
    public function preview(): PreviewResult;
}

Result Classes#

ValidationResult#

1
2
3
4
$result = new ValidationResult();
$result->addError('Field type not found', 'field_type', 'field_type_not_found');
$result->addWarning('Widget may not be compatible');
$result->isValid(); // false

ExecutionResult#

1
2
3
4
5
6
7
8
$result = new ExecutionResult(TRUE);
$result->addMessage('Field created successfully.');
$result->addAffectedEntity([
    'type' => 'field_config',
    'id' => 'node.article.field_body',
    'label' => 'Body field',
]);
$result->setRollbackData(['field_id' => 'node.article.field_body']);

PreviewResult#

1
2
3
4
5
6
$preview = new PreviewResult();
$preview->addOperation('create', 'field_config', 'node.article.field_body', 'Create Body field');
$preview->addDetails([
    'Field Type' => 'text_long',
    'Required' => 'Yes',
]);

RollbackResult#

1
2
3
$result = new RollbackResult(TRUE);
$result->addMessage('Field deleted successfully.');
$result->addRestoredEntity(['type' => 'field_config', 'id' => 'node.article.field_body']);

Quick Examples#

Create a Simple Operation#

<?php

namespace Drupal\my_module\Plugin\EbOperation;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\eb\Attribute\EbOperation;
use Drupal\eb\PluginBase\OperationBase;
use Drupal\eb\Result\ExecutionResult;
use Drupal\eb\Result\ValidationResult;

#[EbOperation(
    id: 'my_custom_operation',
    label: new TranslatableMarkup('My Custom Operation'),
    description: new TranslatableMarkup('Does something custom'),
    operationType: 'create',
)]
class MyCustomOperation extends OperationBase {

    public function validate(): ValidationResult {
        $result = new ValidationResult();
        $this->validateRequiredFields(['name'], $result);
        return $result;
    }

    public function execute(): ExecutionResult {
        $name = $this->getDataValue('name');
        // Do something with the name value.
        $result = new ExecutionResult(TRUE);
        $result->addMessage("Created: $name");
        return $result;
    }

}

Create a Simple Validator#

<?php

namespace Drupal\my_module\Plugin\EbValidator;

use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\eb\Attribute\EbValidator;
use Drupal\eb\PluginBase\ValidatorBase;
use Drupal\eb\PluginInterfaces\OperationInterface;
use Drupal\eb\Result\ValidationResult;

#[EbValidator(
    id: 'my_custom_validator',
    label: new TranslatableMarkup('My Custom Validator'),
    description: new TranslatableMarkup('Validates custom rules'),
)]
class MyCustomValidator extends ValidatorBase {

    public function validate(OperationInterface $operation, array $context = []): ValidationResult {
        $result = new ValidationResult();

        // Add your validation logic
        $name = $operation->getDataValue('name');
        if (strlen($name) < 3) {
            $result->addError('Name must be at least 3 characters', 'name', 'name_too_short');
        }

        return $result;
    }

}

Next Steps#