Problem/Motivation

In the UI Patterns ecosystem, including Display builder, there is a recurrent needs of rendering any single source, or a list of source. Because the source tree has not always a SourceComponent as a single root node.

So, the community do weird tricks like:

    foreach ($sources as $source_data) {
      $fake_build = $this->componentElementBuilder->buildSource($fake_build, 'content', [], $source_data, $contexts);
    }
    $build = $fake_build['#slots']['content'] ?? [];
    $build['#cache'] = $fake_build['#cache'] ?? [];
    // The render array is built based on decisions made by SourceStorage
    // plugins, and therefore it needs to depend on the accumulated
    // cacheability of those decisions.
    $cacheability->applyTo($build);

where a single public function buildSources(array $sources, array $contexts = []): array may be enough.

Proposed resolutions

  1. Add a SourceTreeRendererInterface with a ::buildSources() method
  2. Implement it:
    • in ComponentElementBuilder: to keep things simple. SourceTreeRendererInterface namespace can become a service alias. ComponentElementBuilder::buildSource() can be deprecated and become protected or private later.
    • OR in a brand new service, using SourceTreeRendererInterface namespace as service ID. This will allow a cleaner implementation and to also add SourceTreeRendererInterface::buildSource() later. ComponentElementBuilder could become @internal or deprecarde.
  3. Use SourceTreeRendererInterface::buildSources() everywhere a "fake component' is used:
    • modules/ui_patterns_field/src/Plugin/Field/FieldFormatter/SourceFormatter.php
    • modules/ui_patterns_field_formatters/src/Plugin/Field/FieldFormatter/SourceFormatter.php
    • modules/custom/ui_patterns/src/Plugin/UiPatterns/Source/LayoutSource.php
  4. The implementation in ComponentElementBuilder can be naive and keep the "fake component" mechanism (at leas, it is centralized in a single place now):

      /**
       * {@inheritdoc}
       */
      public function buildSources(array $sources, array $contexts = []): array {
        $cacheability = new CacheableMetadata();
        $fake_build = [];
    
        foreach ($sources as $source_data) {
          $fake_build = $this->buildSource($fake_build, 'content', [], $source_data, $contexts);
        }
        $build = $fake_build['#slots']['content'] ?? [];
        $build['#cache'] = $fake_build['#cache'] ?? [];
        // The render array is built based on decisions made by SourceStorage
        // plugins, and therefore it needs to depend on the accumulated
        // cacheability of those decisions.
        $cacheability->applyTo($build);
    
        return $build;
      }
    

    Or being more ambitious, like the implementation in a new service would be.

    Remaining tasks

    Follow-up: in Display Builder, use SourceTreeRendererInterface::buildSources() in:

  • EntityViewDisplayTrait
  • DisplayBuilderPageVariant
  • PreprocessViewsView
  • BuilderPanel
  • PreviewPanel
  • Maybe more...
Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

pdureau created an issue. See original summary.

pdureau’s picture

Title: Add a mechanism to build the renderable of a single source » Make sources rendering easier
just_like_good_vibes’s picture

very nice idea, thanks !

maybe we could discuss the naming. let's do this during the next weekly

pdureau’s picture

Status: Active » Needs work

Work started and first commit pushed in MR.

Tested with #3576386: Simplify single source rendering in Display Builder, it seems to work OK.

However, the first commit is very naive and straightforward, and ::buildSources():

  • is just a hacky wrapper around ::buildSource()
  • doesn't address the big issue of ComponentElementBuilder: the source tree rendering is bigger than SDC components and most of the logic may be a moved trait in the ComponentSource
  • doesn't mark anything from ComponentElementBuilder as deprecated
  • keeps 3 calls to ::buildSource(): SourceFormatter and UIPatternsSourceFieldPropertySource