Skip to content

Refactor: Extract context serialization/deserialization into dedicated service for ab_blocks #34

@e0ipso

Description

@e0ipso

Summary

The A/B Blocks feature currently has context serialization/deserialization logic embedded directly in two classes, violating the Single Responsibility Principle and creating code duplication. This issue proposes extracting this logic into a dedicated service.

Current Implementation Problems

Code Location Issues

  1. Serialization: TestableBlockComponentRenderArray::serializeSupportedContextValues() (lines 267-302)
  2. Deserialization: AjaxBlockRender::deserializeContextValues() and deserializeContextValue() (lines 168-226)

Problems with Current Approach

  • Duplicated Logic: Both classes contain similar context handling code
  • Mixed Responsibilities: Event subscriber and controller both handle serialization concerns
  • Hard to Test: Context logic is buried within larger classes
  • Maintenance Overhead: Changes to context handling require touching multiple files
  • No Reusability: Other parts of the system can't leverage this functionality

Proposed Solution

New Service: ContextNormalizerService

Create a dedicated service that integrates with Drupal's serialization component to handle context serialization/deserialization:

namespace Drupal\ab_blocks\Service;

use Drupal\Core\Plugin\Context\ContextInterface;
use Drupal\serialization\Normalizer\NormalizerInterface;
use Drupal\serialization\Normalizer\DenormalizerInterface;

class ContextNormalizerService implements NormalizerInterface, DenormalizerInterface {
  
  public function normalize($contexts, $format = NULL, array $context = []): array;
  
  public function denormalize($data, $class, $format = NULL, array $context = []): array;
  
  public function supportsNormalization($data, $format = NULL): bool;
  
  public function supportsDenormalization($data, $type, $format = NULL): bool;
}

Service Registration

# ab_blocks.services.yml
services:
  ab_blocks.context_normalizer:
    class: Drupal\ab_blocks\Service\ContextNormalizerService
    arguments:
      - '@typed_data_manager'
      - '@entity_type.manager'
      - '@language_manager'
    tags:
      - { name: normalizer }

Implementation Details

Supported Context Types

  • EntityAdapter: Serialize to entity:{type}={id} format
  • PrimitiveInterface: JSON encode values
  • Language: Serialize language ID

Methods to Implement

  1. normalize(array $contexts): Convert Context objects to serializable array
  2. denormalize(array $data): Reconstruct Context objects from serialized data
  3. encodeForTransport(array $contexts): Base64 encode for URL transport
  4. decodeFromTransport(string $encoded): Decode from URL transport

Refactored Classes

TestableBlockComponentRenderArray

// Remove serializeSupportedContextValues() method
// Replace with:
$serialized_contexts = $this->contextNormalizer->normalize($event->getContexts());
$encoded_context_values = $this->contextNormalizer->encodeForTransport($serialized_contexts);

AjaxBlockRender

// Remove deserializeContextValues() and deserializeContextValue() methods
// Replace with:
$context_values = $this->contextNormalizer->decodeFromTransport($encoded_contexts);

Benefits

Separation of Concerns

  • Event subscriber focuses on render array modification
  • Controller focuses on Ajax response generation
  • Context handling isolated in dedicated service

Improved Maintainability

  • Single location for context serialization logic
  • Easier to test context handling in isolation
  • Clear service boundaries and responsibilities

Extensibility

  • Other modules can leverage the context normalizer
  • Easy to add support for new context types
  • Follows Drupal's normalizer pattern for consistency

Integration with Drupal Core

  • Uses Drupal's serialization component architecture
  • Follows established patterns for normalizers
  • Can be tagged and discovered by the serialization system

Implementation Steps

  1. Create ContextNormalizerService with normalize/denormalize methods
  2. Add service definition in ab_blocks.services.yml
  3. Refactor TestableBlockComponentRenderArray to use service
  4. Refactor AjaxBlockRender to use service
  5. Add comprehensive unit tests for the new service
  6. Update documentation reflecting the new architecture

Testing Strategy

Unit Tests

  • Test context normalization for all supported types
  • Test edge cases (null values, invalid data)
  • Test encoding/decoding roundtrips
  • Mock dependencies for isolated testing

Integration Tests

  • Verify end-to-end context preservation in A/B block testing
  • Test with various Layout Builder configurations
  • Ensure backwards compatibility

Backward Compatibility

This refactoring maintains full backward compatibility:

  • No changes to public APIs
  • Same serialization format maintained
  • Existing A/B tests continue to work unchanged

Metadata

Metadata

Assignees

No one assigned

    Labels

    🏷️ type:productionProduction codebase (src/, modules/, core functionality)📂 area:blocksLayout Builder block A/B testing features📂 area:coreCore plugin system, interfaces, managers, and base functionality🔥 priority:mediumStandard feature requests, moderate bugs with workarounds

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions