Drupal 11 中渲染前更改实体视图构建器配置的方法及动态设置尺寸技巧

Drupal 11 中渲染前更改实体视图构建器配置的方法及动态设置尺寸技巧

Drupal 11: How To Alter Entity View Builder Configuration Before Rendering

我最近在一个 Drupal 11 网站上遇到了一个问题,当时我有一个区块正在渲染一个实体以在页面上显示。

渲染过程本身并没有什么异常,但在这种情况下,我需要向实体标记中添加一些属性,结果发现这个过程并不简单。解决方案是使用预渲染回调方法在渲染过程进行到一半时进行拦截。

由于这个过程并不简单,我做了一些笔记,并决定将其整理成一篇文章,展示如何实现同样的操作。在本文中,我们将研究如何使用视图构建器生成实体的可渲染视图,然后探讨如何在不使用预处理钩子的情况下更改视图模式的属性。

渲染实体

要使内容实体准备好进行渲染,我们需要使用 entity_type.manager 服务来获取相关实体的正确视图构建器。一旦我们获得了视图构建器对象,就可以使用 view() 方法来获取可渲染数组。

例如,假设我们有某种类型的媒体实体,我们可以使用以下代码使该实体准备好进行渲染。

变量现在将包含使用实体的“默认”视图模式渲染实体所需的数组。数组的内容取决于你要渲染的内容,但如果是媒体实体,那么实体对象、视图构建器对象、实体的视图模式和主题以及一些缓存信息都将存在。

就其本身而言,这个数组还没有包含足够的信息来让我们更改最终渲染的某些方面。例如,如果我们想向图像中注入属性,那么我们需要将它们注入到一个属性数组中,但这个数组在这个级别并不存在。

如果我们将其作为渲染数组返回(甚至是其中的一部分),那么媒体项将通过渲染管道进行完全渲染。

虽然这很有效,但问题是我们只能使用这个视图模式。如果我们想向媒体中注入自定义属性,我们可以创建另一个带有更多模板的视图模式,但如果我们有很多自定义属性需要注入,这很快就会变得难以管理。

解决这个问题的方法可能是使用预处理钩子,但问题是我们会失去我们正在做的事情的上下文。预处理钩子会拦截实体渲染过程,但我们不确定实体来自哪里,因此可能很难以正确的方式对其进行更改。

解决这个问题的方法是使用 #pre_render 属性来更改渲染过程的输出,这让我们对最终结果有了更大的控制权。

使用 #pre_render 来更改渲染

#pre_render 添加到我们的渲染数组中非常简单,但它不能单独使用。以下代码将展示如何使用这种方法向图像媒体实体项添加高度和宽度属性。

首先,我们只需要将 #pre_render 渲染属性注入到 $mediaView 渲染数组中,并为其提供一个回调函数,以便它可以调用该函数来执行操作。

在下面的示例中,我们将 #pre_render 属性设置为一个名为 setDimensions 的方法,该方法存在于我们设置渲染数组的同一个类中。

这段代码将是用于渲染项目的方法的一部分。例如,如果我们在一个区块插件中,那么代码将加载并准备好实体以进行渲染,并将其作为数组返回。

由于区块是一种相当自包含的类,我们将在本文的其余示例中使用它。为了渲染我们的媒体项,我们可以使用以下代码在区块的 build() 方法中生成媒体项的渲染数组。

关于回调函数需要注意的一点是,我们需要告知 Drupal 我们的回调函数是有效的,这是系统内置的一种安全机制。为此,我们需要实现 Drupal\Core\Security\TrustedCallbackInterface 接口,该接口要求添加一个名为 trustedCallbacks() 的方法。如果没有这些元素,Drupal 将抛出错误并拒绝调用回调方法。

以下是区块类的头部,其中将 TrustedCallbackInterface 添加到了类签名中。


    namespace Drupal\my_module\Plugin\Block;
    use Drupal\Core\Block\Attribute\Block;
    use Drupal\Core\Block\BlockBase;
    use Drupal\Core\Form\FormStateInterface;
    use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
    use Drupal\Core\Security\TrustedCallbackInterface;
    use Drupal\Core\StringTranslation\TranslatableMarkup;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    /**
     * Provides a footer information block.
     */
    #[Block(
      id: "render_entity",
      admin_label: new TranslatableMarkup("Render entity"),
      category: new TranslatableMarkup("Custom Components")
    )]
    class RenderEntity extends BlockBase implements ContainerFactoryPluginInterface, TrustedCallbackInterface {
      // ...
  

trustedCallbacks() 方法只需要返回一个包含该类中使用的回调方法的数组。由于我们只有一个回调方法,因此我们的实现非常简单。


    /**
     * {@inheritdoc}
     */
    public static function trustedCallbacks() {
      return ['setDimensions'];
    }
  

现在我们可以构建 setDimentions() 回调方法,该方法接受一个名为 $build 的单个参数。这是渲染数组的当前状态。

setDimentions() 回调只需要确保媒体图像项存在,然后将高度和宽度尺寸注入到项属性中。


    /**
     * Injects dimensions into the set image.
     *
     * @param array<mixed> $build
     *   The render array for the embedded media.
     *
     * @return array<mixed>
     *   The updated render array.
     */
    public static function setDimensions(array $build):array {
      if (isset($build['field_media_image'][0])) {
        $build['field_media_svg'][0]['#item_attributes']['width'] = 500;
        $build['field_media_svg'][0]['#item_attributes']['height'] = 250;
      }
      return $build;
    }
  

   这里有一个小提示,你可能会看到属性存储为 #item_attributes#attributes,这将取决于前端使用的格式化器类型。如果你只找到 #item_attributes 数组键,那么正常使用它。但是,如果你在这里使用响应式图像格式化器,那么传递给高度和宽度的任何值都将被忽略,而使用你配置的响应式图像样式,这是正确的行为。如果你没有使用格式化器,那么数组将包含数组键 #attributes,你可以以相同的方式使用它。存在哪种属性数组在很大程度上取决于你要渲染的实体类型。

   生成的 HTML 将是媒体项的标准渲染 <img> 标签,但它也将包含我们想要注入的属性。

   作为一个额外的内容,让我们看看如何使尺寸更具动态性,因为硬编码的图像尺寸迟早会出现问题。

   使事情更具动态性

   我们可以将尺寸添加到生成的渲染数组中,而不是将尺寸硬编码到 setDimensions 方法中,这样它就会传递给回调函数。我们添加到初始渲染数组中的任何内容(只要它以 # 为前缀)最终都会传递给回调方法。我们添加的属性不应该干扰渲染过程(除非你将它们命名为与 Drupal 内部使用的内容冲突)。

   为了延续前面的示例,我们可以通过从原始源中提取图像的尺寸来扩展图像示例。这可以使用 image.factory 服务来实现,该服务可以使用图像本身的位置创建一个 Image 对象。一旦我们有了这个 Image 对象,我们就可以提取有关图像的各种信息,包括我们的图像尺寸。

   在 setDimensions 方法内部,我们只需要获取注入到原始渲染数组中的值,并将它们移动到正确的位置。

   现在我们的图像将以原始尺寸的 10% 进行渲染。

   当我渲染需要特定尺寸的 SVG 图像时,我发现这种技术很有用。这意味着无论原始 SVG 图像有多大,最终的图像总是被限制在正确的尺寸。对 SVG 图像执行此方法很有意义,因为不可能通过图像格式化器传递 SVG 图像。可以在显示模式中限制 SVG 的大小,但这意味着需要在网站上为不同的图像设置多个不同的视图模式。

   在 Drupal 的类 Drupal\media\Plugin\Filter\MediaEmbed 中可以看到这种方法的一个示例,该类使用一个名为 disableContextualLinks 的回调方法,当媒体项嵌入到 CKEditor 窗口中时,该方法会从渲染数组中移除上下文链接部分。

   我还应该在这里指出,存在回调类型 #post_render。后渲染回调接受一个标记对象,该对象基本上是渲染管道的完全渲染输出。它们也需要以与 trustedCallbacks() 方法相同的方式进行注册。

   我通常避免使用后渲染回调,除非我真的需要,因为它们需要进行字符串操作来更改内容。一般来说,在内容渲染之前进行操作比在渲染之后更容易。

联系我们

提供基于Drupal的门户网站、电子商务网站、移动应用开发及托管服务

长按加微信
长风云微信
长按关注公众号
长风云公众号