-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
🏷️ type:productionProduction codebase (src/, modules/, core functionality)Production codebase (src/, modules/, core functionality)📂 area:blocksLayout Builder block A/B testing featuresLayout Builder block A/B testing features📂 area:coreCore plugin system, interfaces, managers, and base functionalityCore plugin system, interfaces, managers, and base functionality🔥 priority:highImportant features, significant bugs affecting many usersImportant features, significant bugs affecting many users
Description
Implement proxy rendering engine with cache metadata handling
Parent Issue: #22 - Add A/B Testing Proxy Block Plugin
Phase: Foundation
Depends On: #23 (plugin skeleton), #24 (configuration form)
Recommended Assignee: AI (systematic implementation following established patterns)
Objective
Implement the core rendering logic for the AbTestProxyBlock, including target block instantiation, render array construction, and proper cache metadata bubbling.
Tasks
1. Implement build() Method
Core Functionality:
- Check render mode and handle accordingly
- Instantiate target block plugin with configuration
- Execute target block's build() method
- Bubble up cache metadata properly
- Return appropriate render array
2. Target Block Instantiation Logic
protected function createTargetBlock(): ?BlockPluginInterface {
$config = $this->getConfiguration();
$plugin_id = $config['target_block_plugin'] ?? '';
$block_config = $config['target_block_config'] ?? [];
if (empty($plugin_id)) {
return NULL;
}
try {
return $this->blockManager->createInstance($plugin_id, $block_config);
}
catch (PluginException $e) {
\Drupal::logger('ab_blocks')->warning('Failed to create target block @plugin: @message', [
'@plugin' => $plugin_id,
'@message' => $e->getMessage(),
]);
return NULL;
}
}3. Main Build Method Implementation
public function build(): array {
$config = $this->getConfiguration();
// Handle empty render mode
if ($config['render_mode'] === 'empty') {
return [
'#markup' => '',
'#cache' => [
'contexts' => $this->getCacheContexts(),
'tags' => $this->getCacheTags(),
'max-age' => $this->getCacheMaxAge(),
],
];
}
// Create and render target block
$target_block = $this->createTargetBlock();
if (\!$target_block) {
return $this->buildErrorState();
}
// Check target block access
$access_result = $target_block->access(\Drupal::currentUser(), TRUE);
if (\!$access_result->isAllowed()) {
$build = [
'#markup' => '',
'#cache' => [
'contexts' => $this->getCacheContexts(),
'tags' => $this->getCacheTags(),
'max-age' => $this->getCacheMaxAge(),
],
];
CacheableMetadata::createFromObject($access_result)->applyTo($build);
return $build;
}
// Build target block
$build = $target_block->build();
// Bubble up cache metadata from target block
$this->bubbleTargetBlockCacheMetadata($build, $target_block);
return $build;
}4. Cache Metadata Handling
protected function bubbleTargetBlockCacheMetadata(array &$build, BlockPluginInterface $target_block): void {
$cache_metadata = CacheableMetadata::createFromRenderArray($build);
// Add target block's cache contexts, tags, and max-age
$cache_metadata->addCacheContexts($target_block->getCacheContexts());
$cache_metadata->addCacheTags($target_block->getCacheTags());
$cache_metadata->setCacheMaxAge(
Cache::mergeMaxAges($cache_metadata->getCacheMaxAge(), $target_block->getCacheMaxAge())
);
// Add proxy block's own cache metadata
$cache_metadata->addCacheContexts($this->getCacheContexts());
$cache_metadata->addCacheTags($this->getCacheTags());
$cache_metadata->setCacheMaxAge(
Cache::mergeMaxAges($cache_metadata->getCacheMaxAge(), $this->getCacheMaxAge())
);
$cache_metadata->applyTo($build);
}5. Error State Handling
protected function buildErrorState(): array {
return [
'#markup' => $this->t('Block configuration error: Target block could not be loaded.'),
'#cache' => [
'contexts' => $this->getCacheContexts(),
'tags' => $this->getCacheTags(),
'max-age' => $this->getCacheMaxAge(),
],
];
}6. Cache Method Overrides
public function getCacheContexts(): array {
$cache_contexts = parent::getCacheContexts();
// Add contexts based on configuration
$config = $this->getConfiguration();
if (\!empty($config['target_block_plugin'])) {
$target_block = $this->createTargetBlock();
if ($target_block) {
$cache_contexts = Cache::mergeContexts($cache_contexts, $target_block->getCacheContexts());
}
}
return $cache_contexts;
}
public function getCacheTags(): array {
$cache_tags = parent::getCacheTags();
$config = $this->getConfiguration();
if (\!empty($config['target_block_plugin'])) {
$target_block = $this->createTargetBlock();
if ($target_block) {
$cache_tags = Cache::mergeTags($cache_tags, $target_block->getCacheTags());
}
}
// Add config-based cache tag
$cache_tags[] = 'ab_test_proxy_block:' . $this->getPluginId();
return $cache_tags;
}
public function getCacheMaxAge(): int {
$max_age = parent::getCacheMaxAge();
$config = $this->getConfiguration();
if (\!empty($config['target_block_plugin'])) {
$target_block = $this->createTargetBlock();
if ($target_block) {
$max_age = Cache::mergeMaxAges($max_age, $target_block->getCacheMaxAge());
}
}
return $max_age;
}Implementation Guidelines
Error Handling Strategy
- Log errors for debugging without exposing to users
- Graceful degradation when target blocks fail
- Respect access control from target blocks
- Return empty renders for invalid configurations
Performance Considerations
- Cache target block instances when possible
- Avoid recreating blocks unnecessarily
- Proper cache invalidation triggers
- Efficient cache metadata aggregation
Access Control
- Respect target block access restrictions
- Apply access results to cache metadata
- Handle access-denied scenarios gracefully
Required Imports
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Component\Plugin\Exception\PluginException;Testing Guidelines
Unit Testing Scenarios
// Test empty render mode
public function testEmptyRenderMode() {
// Set render_mode to 'empty'
// Assert build() returns empty markup with proper cache metadata
}
// Test target block rendering
public function testTargetBlockRendering() {
// Mock target block with known output
// Assert proxy block returns target block's output
// Assert cache metadata is properly bubbled up
}
// Test invalid target block
public function testInvalidTargetBlock() {
// Set non-existent target block plugin
// Assert error state is returned
// Assert error is logged
}
// Test access control
public function testAccessControl() {
// Mock target block that denies access
// Assert empty render with access metadata
}Manual Testing
- Place proxy block with different target blocks
- Verify output matches target block's output
- Check cache headers in browser dev tools
- Test with access-restricted target blocks
Cache Behavior Requirements
Cache Contexts
- Include all target block cache contexts
- Add proxy-specific contexts if needed
- Merge contexts properly using
Cache::mergeContexts()
Cache Tags
- Include all target block cache tags
- Add proxy block configuration tag
- Use
Cache::mergeTags()for proper merging
Cache Max Age
- Use most restrictive max age between proxy and target
- Use
Cache::mergeMaxAges()for proper calculation - Handle Cache::PERMANENT appropriately
Acceptance Criteria
- Empty render mode returns empty markup with proper cache metadata
- Target block instantiation works for valid plugins
- Target block build() method executed and output returned
- Cache metadata properly bubbled from target blocks
- Access control respected from target blocks
- Error states handled gracefully with logging
- No PHP errors or warnings during rendering
- Cache invalidation works correctly
Context for AI Implementation
This is a systematic implementation task that follows established Drupal patterns:
- Standard Block Rendering: Follow how other blocks handle target instantiation
- Cache Metadata Patterns: Study core blocks that proxy other plugins
- Error Handling: Use Drupal logging standards
- Access Control: Follow core access result handling patterns
Key References in Codebase:
- Study existing block plugins in
/core/modules/block/src/Plugin/Block/ - Look at Layout Builder's block handling patterns
- Reference cache metadata handling in core render systems
Testing Strategy:
- Focus on systematic testing of all render paths
- Mock dependencies appropriately for unit tests
- Verify cache behavior matches expectations
Sequential Dependencies:
- Requires Create AbTestProxyBlock plugin skeleton and configuration schema #23 (plugin skeleton) and Implement block selection UI and validation logic #24 (configuration) to be complete
- Provides foundation for Implement proxy rendering engine with cache metadata handling #25 (error handling) and Add comprehensive error handling and edge case management #26 (documentation)
Metadata
Metadata
Assignees
Labels
🏷️ type:productionProduction codebase (src/, modules/, core functionality)Production codebase (src/, modules/, core functionality)📂 area:blocksLayout Builder block A/B testing featuresLayout Builder block A/B testing features📂 area:coreCore plugin system, interfaces, managers, and base functionalityCore plugin system, interfaces, managers, and base functionality🔥 priority:highImportant features, significant bugs affecting many usersImportant features, significant bugs affecting many users