Commit 18bb5abf authored by catch's avatar catch

Issue #2543340 by Wim Leers, Fabianx: Convert BlockViewBuilder to use...

Issue #2543340 by Wim Leers, Fabianx: Convert BlockViewBuilder to use #lazy_builder (but don't yet let context-aware blocks be placeholdered)
parent 45aca272
......@@ -10,6 +10,7 @@
use Drupal\Core\Asset\AssetResolverInterface;
use Drupal\Core\Asset\AttachedAssets;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\EnforcedResponseException;
use Symfony\Component\HttpFoundation\RequestStack;
/**
......@@ -103,7 +104,18 @@ public function processAttachments(AttachmentsInterface $response) {
// attachments to be added to the response, which the attachment
// placeholders rendered by renderHtmlResponseAttachmentPlaceholders() will
// need to include.
$response = $this->renderPlaceholders($response);
//
// @todo Exceptions should not be used for code flow control. However, the
// Form API does not integrate with the HTTP Kernel based architecture of
// Drupal 8. In order to resolve this issue properly it is necessary to
// completely separate form submission from rendering.
// @see https://www.drupal.org/node/2367555
try {
$response = $this->renderPlaceholders($response);
}
catch (EnforcedResponseException $e) {
return $e->getResponse();
}
$attached = $response->getAttachments();
......
......@@ -126,6 +126,61 @@ function hook_block_view_BASE_BLOCK_ID_alter(array &$build, \Drupal\Core\Block\B
$build['#title'] = t('New title of the block');
}
/**
* Alter the result of \Drupal\Core\Block\BlockBase::build().
*
* Unlike hook_block_view_alter(), this hook is called very early, before the
* block is being assembled. Therefore, it is early enough to alter the
* cacheability metadata (change #cache), or to explicitly placeholder the block
* (set #create_placeholder).
*
* In addition to hook_block_build_alter(), which is called for all blocks,
* there is hook_block_build_BASE_BLOCK_ID_alter(), which can be used to target
* a specific block or set of similar blocks.
*
* @param array &$build
* A renderable array of data, only containing #cache.
* @param \Drupal\Core\Block\BlockPluginInterface $block
* The block plugin instance.
*
* @see hook_block_build_BASE_BLOCK_ID_alter()
* @see entity_crud
*
* @ingroup block_api
*/
function hook_block_build_alter(array &$build, \Drupal\Core\Block\BlockPluginInterface $block) {
// Add the 'user' cache context to some blocks.
if ($some_condition) {
$build['#contexts'][] = 'user';
}
}
/**
* Provide a block plugin specific block_build alteration.
*
* In this hook name, BASE_BLOCK_ID refers to the block implementation's plugin
* id, regardless of whether the plugin supports derivatives. For example, for
* the \Drupal\system\Plugin\Block\SystemPoweredByBlock block, this would be
* 'system_powered_by_block' as per that class's annotation. And for the
* \Drupal\system\Plugin\Block\SystemMenuBlock block, it would be
* 'system_menu_block' as per that class's annotation, regardless of which menu
* the derived block is for.
*
* @param array $build
* A renderable array of data, only containing #cache.
* @param \Drupal\Core\Block\BlockPluginInterface $block
* The block plugin instance.
*
* @see hook_block_build_alter()
* @see entity_crud
*
* @ingroup block_api
*/
function hook_block_build_BASE_BLOCK_ID_alter(array &$build, \Drupal\Core\Block\BlockPluginInterface $block) {
// Explicitly enable placeholdering of the specific block.
$build['#create_placeholder'] = TRUE;
}
/**
* Control access to a block instance.
*
......
......@@ -8,18 +8,59 @@
namespace Drupal\block;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Block\MainContentBlockPluginInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityViewBuilder;
use Drupal\Core\Entity\EntityViewBuilderInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\Element;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a Block view builder.
*/
class BlockViewBuilder extends EntityViewBuilder {
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs a new BlockViewBuilder.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, ModuleHandlerInterface $module_handler) {
parent::__construct($entity_type, $entity_manager, $language_manager);
$this->moduleHandler = $module_handler;
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager'),
$container->get('language_manager'),
$container->get('module_handler')
);
}
/**
* {@inheritdoc}
*/
......@@ -40,13 +81,9 @@ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = N
public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
/** @var \Drupal\block\BlockInterface[] $entities */
$build = array();
foreach ($entities as $entity) {
foreach ($entities as $entity) {
$entity_id = $entity->id();
$plugin = $entity->getPlugin();
$plugin_id = $plugin->getPluginId();
$base_id = $plugin->getBaseId();
$derivative_id = $plugin->getDerivativeId();
$configuration = $plugin->getConfiguration();
$cache_tags = Cache::mergeTags($this->getCacheTags(), $entity->getCacheTags());
$cache_tags = Cache::mergeTags($cache_tags, $plugin->getCacheTags());
......@@ -54,20 +91,6 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
// Create the render array for the block as a whole.
// @see template_preprocess_block().
$build[$entity_id] = array(
'#theme' => 'block',
'#attributes' => array(),
// All blocks get a "Configure block" contextual link.
'#contextual_links' => array(
'block' => array(
'route_parameters' => array('block' => $entity->id()),
),
),
'#weight' => $entity->getWeight(),
'#configuration' => $configuration,
'#plugin_id' => $plugin_id,
'#base_plugin_id' => $base_id,
'#derivative_plugin_id' => $derivative_id,
'#id' => $entity->id(),
'#cache' => [
'keys' => ['entity_view', 'block', $entity->id()],
'contexts' => Cache::mergeContexts(
......@@ -77,22 +100,96 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
'tags' => $cache_tags,
'max-age' => $plugin->getCacheMaxAge(),
],
'#pre_render' => [
[$this, 'buildBlock'],
],
// Add the entity so that it can be used in the #pre_render method.
'#block' => $entity,
);
$build[$entity_id]['#configuration']['label'] = SafeMarkup::checkPlain($configuration['label']);
// Don't run in ::buildBlock() to ensure cache keys can be altered. If an
// alter hook wants to modify the block contents, it can append another
// #pre_render hook.
$this->moduleHandler()->alter(array('block_view', "block_view_$base_id"), $build[$entity_id], $plugin);
// Allow altering of cacheability metadata or setting #create_placeholder.
$this->moduleHandler->alter(['block_build', "block_build_" . $plugin->getBaseId()], $build[$entity_id], $plugin);
if ($plugin instanceof MainContentBlockPluginInterface) {
// Immediately build a #pre_render-able block, since this block cannot
// be built lazily.
$build[$entity_id] += static::buildPreRenderableBlock($entity, $this->moduleHandler());
}
else {
// Assign a #lazy_builder callback, which will generate a #pre_render-
// able block lazily (when necessary).
$build[$entity_id] += [
'#lazy_builder' => [static::class . '::lazyBuilder', [$entity_id, $view_mode, $langcode]],
];
}
}
return $build;
}
/**
* Builds a #pre_render-able block render array.
*
* @param \Drupal\block\BlockInterface $entity
* A block config entity.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
*
* @return array
* A render array with a #pre_render callback to render the block.
*/
protected static function buildPreRenderableBlock($entity, ModuleHandlerInterface $module_handler) {
$plugin = $entity->getPlugin();
$plugin_id = $plugin->getPluginId();
$base_id = $plugin->getBaseId();
$derivative_id = $plugin->getDerivativeId();
$configuration = $plugin->getConfiguration();
// Create the render array for the block as a whole.
// @see template_preprocess_block().
$build = [
'#theme' => 'block',
'#attributes' => [],
// All blocks get a "Configure block" contextual link.
'#contextual_links' => [
'block' => [
'route_parameters' => ['block' => $entity->id()],
],
],
'#weight' => $entity->getWeight(),
'#configuration' => $configuration,
'#plugin_id' => $plugin_id,
'#base_plugin_id' => $base_id,
'#derivative_plugin_id' => $derivative_id,
'#id' => $entity->id(),
'#pre_render' => [
static::class . '::preRender',
],
// Add the entity so that it can be used in the #pre_render method.
'#block' => $entity,
];
$build['#configuration']['label'] = SafeMarkup::checkPlain($configuration['label']);
// If an alter hook wants to modify the block contents, it can append
// another #pre_render hook.
$module_handler->alter(['block_view', "block_view_$base_id"], $build, $plugin);
return $build;
}
/**
* #lazy_builder callback; builds a #pre_render-able block.
*
* @param $entity_id
* A block config entity ID.
* @param $view_mode
* The view mode the block is being viewed in.
* @param $langcode
* The langcode the block is being viewed in.
*
* @return array
* A render array with a #pre_render callback to render the block.
*/
public static function lazyBuilder($entity_id, $view_mode, $langcode) {
return static::buildPreRenderableBlock(entity_load('block', $entity_id), \Drupal::service('module_handler'));
}
/**
* #pre_render callback for building a block.
*
......@@ -102,7 +199,7 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
* - if there is content, moves the contextual links from the block content to
* the block itself.
*/
public function buildBlock($build) {
public static function preRender($build) {
$content = $build['#block']->getPlugin()->build();
// Remove the block entity from the render array, to ensure that blocks
// can be rendered without the block config entity.
......
......@@ -22,19 +22,37 @@ function block_test_block_alter(&$block_info) {
*/
function block_test_block_view_test_cache_alter(array &$build, BlockPluginInterface $block) {
if (\Drupal::state()->get('block_test_view_alter_suffix') !== NULL) {
$build['#suffix'] = '<br>Goodbye!';
}
if (\Drupal::state()->get('block_test_view_alter_cache_key') !== NULL) {
$build['#cache']['keys'][] = \Drupal::state()->get('block_test_view_alter_cache_key');
}
if (\Drupal::state()->get('block_test_view_alter_cache_tag') !== NULL) {
$build['#cache']['tags'][] = \Drupal::state()->get('block_test_view_alter_cache_tag');
$build['#attributes']['foo'] = 'bar';
}
if (\Drupal::state()->get('block_test_view_alter_append_pre_render_prefix') !== NULL) {
$build['#pre_render'][] = 'block_test_pre_render_alter_content';
}
}
/**
* Implements hook_block_build_BASE_BLOCK_ID_alter().
*/
function block_test_block_build_test_cache_alter(array &$build, BlockPluginInterface $block) {
// Test altering cache keys, contexts, tags and max-age.
if (\Drupal::state()->get('block_test_block_alter_cache_key') !== NULL) {
$build['#cache']['keys'][] = \Drupal::state()->get('block_test_block_alter_cache_key');
}
if (\Drupal::state()->get('block_test_block_alter_cache_context') !== NULL) {
$build['#cache']['contexts'][] = \Drupal::state()->get('block_test_block_alter_cache_context');
}
if (\Drupal::state()->get('block_test_block_alter_cache_tag') !== NULL) {
$build['#cache']['tags'] = Cache::mergeTags($build['#cache']['tags'], [\Drupal::state()->get('block_test_block_alter_cache_tag')]);
}
if (\Drupal::state()->get('block_test_block_alter_cache_max_age') !== NULL) {
$build['#cache']['max-age'] = \Drupal::state()->get('block_test_block_alter_cache_max_age');
}
// Test setting #create_placeholder.
if (\Drupal::state()->get('block_test_block_alter_create_placeholder') !== NULL) {
$build['#create_placeholder'] = \Drupal::state()->get('block_test_block_alter_create_placeholder');
}
}
/**
* #pre_render callback for a block to alter its content.
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment