diff --git a/core/modules/layout_builder/layout_builder.services.yml b/core/modules/layout_builder/layout_builder.services.yml index a648eef6b1b867c2866e19ad2b272588c376b2d1..4fe50929bd4884866a809be711ca2870224a72f4 100644 --- a/core/modules/layout_builder/layout_builder.services.yml +++ b/core/modules/layout_builder/layout_builder.services.yml @@ -36,3 +36,6 @@ services: arguments: ['@current_user'] tags: - { name: event_subscriber } + logger.channel.layout_builder: + parent: logger.channel_base + arguments: ['layout_builder'] diff --git a/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php b/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php index d32e71baa28f48c1c39daa93e23e186f108a4f08..bb8ed11797cf04f4314f36d1d7868a4eb193677a 100644 --- a/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php +++ b/core/modules/layout_builder/src/Plugin/Block/FieldBlock.php @@ -20,6 +20,7 @@ use Drupal\Core\Render\Element; use Drupal\Core\Session\AccountInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; +use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -81,6 +82,13 @@ class FieldBlock extends BlockBase implements ContextAwarePluginInterface, Conta */ protected $moduleHandler; + /** + * The logger. + * + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + /** * Constructs a new FieldBlock. * @@ -96,11 +104,14 @@ class FieldBlock extends BlockBase implements ContextAwarePluginInterface, Conta * The formatter manager. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. + * @param \Psr\Log\LoggerInterface $logger + * The logger. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, FormatterPluginManager $formatter_manager, ModuleHandlerInterface $module_handler) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityFieldManagerInterface $entity_field_manager, FormatterPluginManager $formatter_manager, ModuleHandlerInterface $module_handler, LoggerInterface $logger) { $this->entityFieldManager = $entity_field_manager; $this->formatterManager = $formatter_manager; $this->moduleHandler = $module_handler; + $this->logger = $logger; // Get the entity type and field name from the plugin ID. list (, $entity_type_id, $bundle, $field_name) = explode(static::DERIVATIVE_SEPARATOR, $plugin_id, 4); @@ -121,7 +132,8 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_definition, $container->get('entity_field.manager'), $container->get('plugin.manager.field.formatter'), - $container->get('module_handler') + $container->get('module_handler'), + $container->get('logger.channel.layout_builder') ); } @@ -141,7 +153,13 @@ protected function getEntity() { public function build() { $display_settings = $this->getConfiguration()['formatter']; $entity = $this->getEntity(); - $build = $entity->get($this->fieldName)->view($display_settings); + try { + $build = $entity->get($this->fieldName)->view($display_settings); + } + catch (\Exception $e) { + $build = []; + $this->logger->warning('The field "%field" failed to render with the error of "%error".', ['%field' => $this->fieldName, '%error' => $e->getMessage()]); + } if (!empty($entity->in_preview) && !Element::getVisibleChildren($build)) { $build['content']['#markup'] = new TranslatableMarkup('Placeholder for the "@field" field', ['@field' => $this->getFieldDefinition()->getLabel()]); } diff --git a/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php b/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php index 8c529cc1e1b93111dc82d2e1c770470714473c71..d77a2397182e630c0cb8c916bce476f4887672d2 100644 --- a/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/FieldBlockTest.php @@ -7,13 +7,19 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\FormatterPluginManager; use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Session\AccountInterface; use Drupal\KernelTests\Core\Entity\EntityKernelTestBase; use Drupal\layout_builder\Plugin\Block\FieldBlock; +use Prophecy\Argument; +use Prophecy\Promise\PromiseInterface; +use Prophecy\Promise\ReturnPromise; +use Prophecy\Promise\ThrowPromise; use Prophecy\Prophecy\ProphecyInterface; +use Psr\Log\LoggerInterface; /** * @coversDefaultClass \Drupal\layout_builder\Plugin\Block\FieldBlock @@ -21,6 +27,30 @@ */ class FieldBlockTest extends EntityKernelTestBase { + /** + * The entity field manager. + * + * @var \Drupal\Core\Entity\EntityFieldManagerInterface + */ + protected $entityFieldManager; + + /** + * The logger. + * + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->entityFieldManager = $this->prophesize(EntityFieldManagerInterface::class); + $this->logger = $this->prophesize(LoggerInterface::class); + } + /** * Tests entity access. * @@ -180,7 +210,6 @@ protected function getTestBlock(ProphecyInterface $entity_prophecy, array $confi 'entity' => new ContextDefinition('entity:entity_test', 'Test', TRUE), ], ]; - $entity_field_manager = $this->prophesize(EntityFieldManagerInterface::class); $formatter_manager = $this->prophesize(FormatterPluginManager::class); $module_handler = $this->prophesize(ModuleHandlerInterface::class); @@ -188,12 +217,93 @@ protected function getTestBlock(ProphecyInterface $entity_prophecy, array $confi $configuration, 'field_block:entity_test:entity_test:the_field_name', $plugin_definition, - $entity_field_manager->reveal(), + $this->entityFieldManager->reveal(), $formatter_manager->reveal(), - $module_handler->reveal() + $module_handler->reveal(), + $this->logger->reveal() ); $block->setContextValue('entity', $entity_prophecy->reveal()); return $block; } + /** + * @covers ::build + * @dataProvider providerTestBuild + */ + public function testBuild(PromiseInterface $promise, $in_preview, $expected_markup, $log_message = '', $log_arguments = []) { + $entity = $this->prophesize(FieldableEntityInterface::class); + $field = $this->prophesize(FieldItemListInterface::class); + $entity->get('the_field_name')->willReturn($field->reveal()); + $entity->in_preview = $in_preview; + $field->view(Argument::type('array'))->will($promise); + + $field_definition = $this->prophesize(FieldDefinitionInterface::class); + $field_definition->getLabel()->willReturn('The Field Label'); + $this->entityFieldManager->getFieldDefinitions('entity_test', 'entity_test')->willReturn(['the_field_name' => $field_definition]); + + if ($log_message) { + $this->logger->warning($log_message, $log_arguments)->shouldBeCalled(); + } + else { + $this->logger->warning(Argument::cetera())->shouldNotBeCalled(); + } + + $block = $this->getTestBlock($entity); + $expected = [ + '#cache' => [ + 'contexts' => [], + 'tags' => [], + 'max-age' => 0, + ], + ]; + if ($expected_markup) { + $expected['content']['#markup'] = $expected_markup; + } + + $actual = $block->build(); + $this->assertEquals($expected, $actual); + } + + /** + * Provides test data for ::testBuild(). + */ + public function providerTestBuild() { + $data = []; + $data['array, no preview'] = [ + new ReturnPromise([['content' => ['#markup' => 'The field value']]]), + FALSE, + 'The field value', + ]; + $data['array, preview'] = [ + new ReturnPromise([['content' => ['#markup' => 'The field value']]]), + TRUE, + 'The field value', + ]; + $data['empty array, no preview'] = [ + new ReturnPromise([[]]), + FALSE, + '', + ]; + $data['empty array, preview'] = [ + new ReturnPromise([[]]), + TRUE, + 'Placeholder for the "The Field Label" field', + ]; + $data['exception, no preview'] = [ + new ThrowPromise(new \Exception('The exception message')), + FALSE, + '', + 'The field "%field" failed to render with the error of "%error".', + ['%field' => 'the_field_name', '%error' => 'The exception message'], + ]; + $data['exception, preview'] = [ + new ThrowPromise(new \Exception('The exception message')), + TRUE, + 'Placeholder for the "The Field Label" field', + 'The field "%field" failed to render with the error of "%error".', + ['%field' => 'the_field_name', '%error' => 'The exception message'], + ]; + return $data; + } + }