Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
60.13% covered (warning)
60.13%
95 / 158
62.07% covered (warning)
62.07%
18 / 29
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntityViewDisplayTrait
60.13% covered (warning)
60.13%
95 / 158
62.07% covered (warning)
62.07%
18 / 29
254.80
0.00% covered (danger)
0.00%
0 / 1
 calculateDependencies
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
5.01
 isDisplayBuilderEnabled
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 onDependencyRemoval
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
4.01
 getPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getContextRequirement
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 checkInstanceId
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 getBuilderUrl
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 getUrlFromInstanceId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getDisplayUrlFromInstanceId
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getDisplayBuilderOverrideField
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDisplayBuilderOverrideProfile
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 isDisplayBuilderOverridable
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getProfile
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getInstanceId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 initInstanceIfMissing
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getInitialSources
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getInitialContext
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getSources
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 saveSources
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 postSave
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 delete
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 buildMultiple
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
42
 getContextsForEntity
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 contextRepository
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInstance
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 loadDisplayBuilder
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 buildThemeRegistryEntry
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 buildSources
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 getUrlParamsFromInstanceId
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_entity_view\Entity;
6
7use Drupal\Core\Cache\CacheableMetadata;
8use Drupal\Core\Entity\EntityStorageInterface;
9use Drupal\Core\Entity\FieldableEntityInterface;
10use Drupal\Core\Plugin\Context\Context;
11use Drupal\Core\Plugin\Context\ContextDefinition;
12use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
13use Drupal\Core\Plugin\Context\EntityContext;
14use Drupal\Core\StringTranslation\TranslatableMarkup;
15use Drupal\Core\Url;
16use Drupal\display_builder\ConfigFormBuilderInterface;
17use Drupal\display_builder\DisplayBuildableInterface;
18use Drupal\display_builder\InstanceInterface;
19use Drupal\display_builder\ProfileInterface;
20use Drupal\ui_patterns\Plugin\Context\RequirementsContext;
21
22/**
23 * Common methods for entity view display.
24 */
25trait EntityViewDisplayTrait {
26
27  /**
28   * Calculates dependencies for the display builder.
29   *
30   * @return $this
31   *   The current instance.
32   *
33   * @see \Drupal\Core\Entity\Display\EntityViewDisplayInterface
34   */
35  public function calculateDependencies(): self {
36    parent::calculateDependencies();
37
38    if (!$this->getInstanceId()) {
39      // If there is no instance ID, we cannot calculate dependencies.
40      return $this;
41    }
42
43    /** @var \Drupal\display_builder\InstanceInterface $instance */
44    $instance = $this->entityTypeManager->getStorage('display_builder_instance')->load($this->getInstanceId());
45
46    if (!$instance) {
47      return $this;
48    }
49    $contexts = $instance->getContexts();
50
51    if (!$contexts) {
52      return $this;
53    }
54
55    foreach ($this->getSources() as $source_data) {
56      /** @var \Drupal\ui_patterns\SourceInterface $source */
57      $source = $this->sourcePluginManager->getSource('', [], $source_data, $contexts);
58      $this->addDependencies($source->calculateDependencies());
59    }
60
61    return $this;
62  }
63
64  /**
65   * Is display builder enabled?
66   *
67   * @return bool
68   *   The display builder is enabled if there is a Display Builder entity.
69   *
70   * @see \Drupal\display_builder_entity_view\DisplayBuilderEnabledInterface
71   */
72  public function isDisplayBuilderEnabled(): bool {
73    // Display Builder must not be enabled for the '_custom' view mode that is
74    // used for on-the-fly rendering of fields in isolation from the entity.
75    if ($this->getOriginalMode() === static::CUSTOM_MODE) {
76      return FALSE;
77    }
78
79    return (bool) $this->getProfile();
80  }
81
82  /**
83   * Handler for when dependencies are removed.
84   *
85   * @param array $dependencies
86   *   The dependencies that were removed.
87   *
88   * @return bool
89   *   TRUE if the display can be overridden, FALSE otherwise.
90   *
91   * @see \Drupal\Core\Entity\Display\EntityViewDisplayInterface
92   */
93  public function onDependencyRemoval(array $dependencies): bool {
94    $changed = parent::onDependencyRemoval($dependencies);
95
96    // Loop through all sources and determine if the removed dependencies are
97    // used by their plugins.
98    /** @var \Drupal\display_builder\InstanceInterface $instance */
99    $instance = $this->getInstance();
100
101    // @todo not working when content entity type is deleted.
102    if (!$instance) {
103      return TRUE;
104    }
105
106    $contexts = $instance->getContexts();
107
108    foreach ($this->getSources() as $source_data) {
109      /** @var \Drupal\ui_patterns\SourceInterface $source */
110      $source = $this->sourcePluginManager->getSource('', [], $source_data, $contexts);
111      $source_dependencies = $source->calculateDependencies();
112      $source_removed_dependencies = $this->getPluginRemovedDependencies($source_dependencies, $dependencies);
113
114      if ($source_removed_dependencies) {
115        // @todo Allow the plugins to react to their dependency removal in
116        // https://www.drupal.org/project/drupal/issues/2579743.
117        // $this->removeSource($delta);
118        $changed = TRUE;
119      }
120    }
121
122    return $changed;
123  }
124
125  /**
126   * {@inheritdoc}
127   */
128  public static function getPrefix(): string {
129    return 'entity_view__';
130  }
131
132  /**
133   * Returns the context requirement for this entity view display.
134   *
135   * This is used to ensure that the entity context is available when building
136   * the display builder.
137   *
138   * @return string
139   *   The context requirement string.
140   *
141   * @see \Drupal\display_builder\DisplayBuildableInterface
142   */
143  public static function getContextRequirement(): string {
144    return 'entity';
145  }
146
147  /**
148   * {@inheritdoc}
149   *
150   * @see \Drupal\display_builder\DisplayBuildableInterface
151   */
152  public static function checkInstanceId(string $instance_id): ?array {
153    if (!\str_starts_with($instance_id, EntityViewDisplay::getPrefix())) {
154      return NULL;
155    }
156    [, $entity, $bundle, $view_mode] = \explode('__', $instance_id);
157
158    return [
159      'entity' => $entity,
160      'bundle' => $bundle,
161      'view_mode' => $view_mode,
162    ];
163  }
164
165  /**
166   * {@inheritdoc}
167   */
168  public function getBuilderUrl(): Url {
169    $fieldable_entity_type = $this->entityTypeManager->getDefinition($this->getTargetEntityTypeId());
170    $bundle_parameter_key = $fieldable_entity_type->getBundleEntityType() ?: 'bundle';
171    $parameters = [
172      $bundle_parameter_key => $this->getTargetBundle(),
173      'view_mode_name' => $this->getMode(),
174    ];
175    $route_name = \sprintf('display_builder_entity_view.%s', $this->getTargetEntityTypeId());
176
177    return Url::fromRoute($route_name, $parameters);
178  }
179
180  /**
181   * Returns the URL for the display builder from an instance id.
182   *
183   * @param string $instance_id
184   *   The builder instance ID.
185   *
186   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
187   *
188   * @return \Drupal\Core\Url
189   *   The url of the instance.
190   *
191   * @see \Drupal\display_builder\DisplayBuildableInterface
192   */
193  public static function getUrlFromInstanceId(string $instance_id): Url {
194    $params = self::getUrlParamsFromInstanceId($instance_id);
195    $route_name = \sprintf('display_builder_entity_view.%s', $params['entity']);
196
197    return Url::fromRoute($route_name, $params);
198  }
199
200  /**
201   * Returns the URL for the display builder from an instance id.
202   *
203   * @param string $instance_id
204   *   The builder instance ID.
205   *
206   * @return \Drupal\Core\Url
207   *   The url of the instance.
208   *
209   * @see Drupal\display_builder\DisplayBuildableInterface
210   */
211  public static function getDisplayUrlFromInstanceId(string $instance_id): Url {
212    $params = self::getUrlParamsFromInstanceId($instance_id);
213    $route_name = \sprintf('entity.entity_view_display.%s.view_mode', $params['entity']);
214
215    return Url::fromRoute($route_name, $params);
216  }
217
218  /**
219   * Returns the field name used to store overridden displays.
220   *
221   * @return string|null
222   *   The field name used to store overridden displays, or NULL if not set.
223   *
224   * @see \Drupal\display_builder_entity_view\Entity\DisplayBuilderOverridableInterface
225   */
226  public function getDisplayBuilderOverrideField(): ?string {
227    return $this->getThirdPartySetting('display_builder', ConfigFormBuilderInterface::OVERRIDE_FIELD_PROPERTY);
228  }
229
230  /**
231   * Returns the display builder override profile.
232   *
233   * @return \Drupal\display_builder\ProfileInterface|null
234   *   The display builder override profile, or NULL if not set.
235   *
236   * @see \Drupal\display_builder_entity_view\Entity\DisplayBuilderOverridableInterface
237   */
238  public function getDisplayBuilderOverrideProfile(): ?ProfileInterface {
239    $display_builder_id = $this->getThirdPartySetting('display_builder', ConfigFormBuilderInterface::OVERRIDE_PROFILE_PROPERTY);
240
241    if ($display_builder_id === NULL) {
242      return NULL;
243    }
244
245    return $this->loadDisplayBuilder($display_builder_id);
246  }
247
248  /**
249   * Returns TRUE if the display can be overridden.
250   *
251   * @return bool
252   *   TRUE if the display can be overridden, FALSE otherwise.
253   *
254   * @see \Drupal\display_builder_entity_view\Entity\DisplayBuilderOverridableInterface
255   */
256  public function isDisplayBuilderOverridable(): bool {
257    return !empty($this->getDisplayBuilderOverrideField())
258      && $this->getDisplayBuilderOverrideProfile() !== NULL;
259  }
260
261  /**
262   * Returns the display builder instance.
263   *
264   * @return \Drupal\display_builder\ProfileInterface|null
265   *   The display builder instance, or NULL if not set.
266   *
267   * @see \Drupal\display_builder\DisplayBuildableInterface
268   */
269  public function getProfile(): ?ProfileInterface {
270    $display_builder_id = $this->getThirdPartySetting('display_builder', ConfigFormBuilderInterface::PROFILE_PROPERTY);
271
272    if ($display_builder_id === NULL) {
273      return NULL;
274    }
275
276    return $this->loadDisplayBuilder($display_builder_id);
277  }
278
279  /**
280   * Returns the instance ID for the display builder.
281   *
282   * @return string|null
283   *   The instance ID for the display builder, or NULL if the entity is new.
284   *
285   * @see \Drupal\display_builder\DisplayBuildableInterface
286   */
287  public function getInstanceId(): ?string {
288    // Usually an entity is new if no ID exists for it yet.
289    if ($this->isNew()) {
290      return NULL;
291    }
292
293    return \sprintf('%s%s', EntityViewDisplay::getPrefix(), \str_replace('.', '__', $this->id));
294  }
295
296  /**
297   * Initializes the display builder instance if it is missing.
298   *
299   * @see \Drupal\display_builder\DisplayBuildableInterface
300   */
301  public function initInstanceIfMissing(): void {
302    /** @var \Drupal\display_builder\InstanceStorage $storage */
303    $storage = $this->entityTypeManager->getStorage('display_builder_instance');
304
305    /** @var \Drupal\display_builder\InstanceInterface $instance */
306    $instance = $storage->load($this->getInstanceId());
307
308    if (!$instance) {
309      $instance = $storage->createFromImplementation($this);
310      $instance->save();
311    }
312  }
313
314  /**
315   * {@inheritdoc}
316   */
317  public function getInitialSources(): array {
318    // Get the sources stored in config.
319    $sources = $this->getSources();
320
321    if (empty($sources)) {
322      // initialImport() has two implementations:
323      // - EntityViewDisplay::initialImport()
324      // - LayoutBuilderEntityViewDisplay::initialImport()
325      $sources = $this->initialImport();
326    }
327
328    return $sources;
329  }
330
331  /**
332   * {@inheritdoc}
333   */
334  public function getInitialContext(): array {
335    $entity_type_id = $this->getTargetEntityTypeId();
336    $bundle = $this->getTargetBundle();
337    $view_mode = $this->getMode();
338    $sampleEntity = $this->sampleEntityGenerator->get($entity_type_id, $bundle);
339    $contexts = [
340      'entity' => EntityContext::fromEntity($sampleEntity),
341      'bundle' => new Context(ContextDefinition::create('string'), $bundle),
342      'view_mode' => new Context(ContextDefinition::create('string'), $view_mode),
343    ];
344
345    return RequirementsContext::addToContext([self::getContextRequirement()], $contexts);
346  }
347
348  /**
349   * Returns the sources of the display builder.
350   *
351   * @return array
352   *   The sources of the display builder.
353   *
354   * @see \Drupal\display_builder\DisplayBuildableInterface
355   */
356  public function getSources(): array {
357    return $this->getThirdPartySetting('display_builder', ConfigFormBuilderInterface::SOURCES_PROPERTY, []);
358  }
359
360  /**
361   * Saves the sources of the display builder.
362   *
363   * @see \Drupal\display_builder\DisplayBuildableInterface
364   */
365  public function saveSources(): void {
366    $data = $this->getInstance()->getCurrentState();
367    $this->setThirdPartySetting('display_builder', ConfigFormBuilderInterface::SOURCES_PROPERTY, $data);
368    $this->save();
369  }
370
371  /**
372   * Post-save operations for the display builder.
373   *
374   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
375   *   The entity storage.
376   * @param bool $update
377   *   Whether the entity is being updated.
378   *
379   * @see \Drupal\Core\Entity\Display\EntityViewDisplayInterface
380   */
381  public function postSave(EntityStorageInterface $storage, $update = TRUE): void {
382    if ($this->getProfile()) {
383      $this->initInstanceIfMissing();
384    }
385
386    parent::postSave($storage, $update);
387  }
388
389  /**
390   * Deletes the display builder instance if it exists.
391   *
392   * @see \Drupal\Core\Entity\Display\EntityViewDisplayInterface
393   */
394  public function delete(): void {
395    if ($instance = $this->getInstance()) {
396      $instance->delete();
397    }
398    parent::delete();
399  }
400
401  /**
402   * Builds a renderable array for the components of a set of entities.
403   *
404   * @param \Drupal\Core\Entity\FieldableEntityInterface[] $entities
405   *   The entities being displayed.
406   *
407   * @return array
408   *   A renderable array for the entities, indexed by the same keys as the
409   *   $entities array parameter.
410   *
411   * @see \Drupal\Core\Entity\Display\EntityViewDisplayInterface
412   */
413  public function buildMultiple(array $entities): array {
414    $build_list = parent::buildMultiple($entities);
415
416    // If no display builder enabled, stop here and return:
417    // - 'Manage Display' build if this trait is used in EntityViewDisplay
418    // - 'Layout Builder' build if used in LayoutBuilderEntityViewDisplay.
419    if (!$this->isDisplayBuilderEnabled()) {
420      // This is also preventing the availability of Display Builder overrides
421      // when Display Builder is not used for the entity view display.
422      // @todo Is it something we want to keep like that?
423      // @see https://www.drupal.org/project/display_builder/issues/3540048
424      return $build_list;
425    }
426
427    // We alter the registry here instead of implementing
428    // hook_theme_registry_alter in order keep the alteration specific to each
429    // display.
430    $entry = $this->buildThemeRegistryEntry();
431    // Theme hook suggestion of the current entity view display.
432    // Example: 'node__article__teaser'.
433    $suggestion = \implode('__', [$this->getTargetEntityTypeId(), $this->getTargetBundle(), $this->getMode()]);
434    $this->themeRegistry->getRuntime()->set($suggestion, $entry);
435
436    if ($this->getMode() === 'default') {
437      // Full page displays don't work without this 'hack'.
438      // @todo Do we need to check if full is not already set?
439      $suggestion = \implode('__', [$this->getTargetEntityTypeId(), $this->getTargetBundle(), 'full']);
440      $this->themeRegistry->getRuntime()->set($suggestion, $entry);
441    }
442
443    foreach ($entities as $id => $entity) {
444      $sources = [];
445
446      if ($this->isDisplayBuilderOverridable()) {
447        $display_builder_field = $this->getDisplayBuilderOverrideField();
448        $overridden_field = $entity->get($display_builder_field);
449        \assert($overridden_field instanceof DisplayBuildableInterface);
450        $sources = $overridden_field->getSources();
451      }
452
453      // If the overridden field is empty fallback to the entity view.
454      if (\count($sources) === 0) {
455        $sources = $this->getSources();
456      }
457
458      // We clear the display because we only want our renderable.
459      $build_list[$id] = [];
460      // @see entity.html.twig
461      $build_list[$id]['content'] = $this->buildSources($entity, $sources);
462    }
463
464    return $build_list;
465  }
466
467  /**
468   * Gets the available contexts for a given entity.
469   *
470   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
471   *   The entity.
472   *
473   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
474   *   An array of context objects for a given entity.
475   */
476  protected function getContextsForEntity(FieldableEntityInterface $entity): array {
477    $available_context_ids = \array_keys($this->contextRepository()->getAvailableContexts());
478
479    return [
480      'view_mode' => new Context(ContextDefinition::create('string'), $this->getMode()),
481      'entity' => EntityContext::fromEntity($entity),
482      'display' => EntityContext::fromEntity($this),
483    ] + $this->contextRepository()->getRuntimeContexts($available_context_ids);
484  }
485
486  /**
487   * Wraps the context repository service.
488   *
489   * @return \Drupal\Core\Plugin\Context\ContextRepositoryInterface
490   *   The context repository service.
491   */
492  protected function contextRepository(): ContextRepositoryInterface {
493    return \Drupal::service('context.repository');
494  }
495
496  /**
497   * Gets the Display Builder instance.
498   *
499   * @return \Drupal\display_builder\InstanceInterface|null
500   *   A display builder instance.
501   */
502  protected function getInstance(): ?InstanceInterface {
503    if (!isset($this->instance)) {
504      /** @var \Drupal\display_builder\InstanceInterface|null $instance */
505      $instance = $this->entityTypeManager->getStorage('display_builder_instance')->load($this->getInstanceId());
506      $this->instance = $instance;
507    }
508
509    return $this->instance;
510  }
511
512  /**
513   * Loads display builder by id.
514   *
515   * @param string $display_builder_id
516   *   The display builder ID.
517   *
518   * @return \Drupal\display_builder\ProfileInterface|null
519   *   The display builder, or NULL if not found.
520   */
521  private function loadDisplayBuilder(string $display_builder_id): ?ProfileInterface {
522    if (empty($display_builder_id)) {
523      return NULL;
524    }
525    $storage = $this->entityTypeManager->getStorage('display_builder_profile');
526
527    /** @var \Drupal\display_builder\ProfileInterface $display_builder */
528    $display_builder = $storage->load($display_builder_id);
529
530    return $display_builder;
531  }
532
533  /**
534   * Build the theme registry entry.
535   *
536   * @return array
537   *   A theme registry entry.
538   */
539  private function buildThemeRegistryEntry(): array {
540    $theme_registry = $this->themeRegistry->get();
541    // Identical to the entity type entry with an unified template.
542    $entry = $theme_registry[$this->getTargetEntityTypeId()];
543    $entry['path'] = $this->modules->getPath('display_builder_entity_view') . '/templates';
544    $entry['template'] = 'entity';
545
546    return $entry;
547  }
548
549  /**
550   * Builds the render array for the sources of a given entity.
551   *
552   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
553   *   The entity.
554   * @param array $sources
555   *   The sources to build.
556   *
557   * @return array
558   *   The render array representing the sources of the entity.
559   */
560  private function buildSources(FieldableEntityInterface $entity, array $sources): array {
561    $contexts = $this->getContextsForEntity($entity);
562    $label = new TranslatableMarkup('@entity being viewed', [
563      '@entity' => $entity->getEntityType()->getSingularLabel(),
564    ]);
565    $contexts['display_builder.entity'] = EntityContext::fromEntity($entity, (string) $label);
566
567    $cacheability = new CacheableMetadata();
568    $fake_build = [];
569
570    foreach ($sources as $source_data) {
571      $fake_build = $this->componentElementBuilder->buildSource($fake_build, 'content', [], $source_data, $contexts);
572    }
573    $build = $fake_build['#slots']['content'] ?? [];
574    $build['#cache'] = $fake_build['#cache'] ?? [];
575    // The render array is built based on decisions made by SourceStorage
576    // plugins, and therefore it needs to depend on the accumulated
577    // cacheability of those decisions.
578    $cacheability->applyTo($build);
579
580    return $build;
581  }
582
583  /**
584   * Returns the URL for the display builder from an instance id.
585   *
586   * @param string $instance_id
587   *   The builder instance ID.
588   *
589   * @return array
590   *   The url parameters for this instance id.
591   */
592  private static function getUrlParamsFromInstanceId(string $instance_id): array {
593    [, $entity, $bundle, $view_mode] = \explode('__', $instance_id);
594    $fieldable_entity_type = \Drupal::service('entity_type.manager')->getDefinition($entity);
595    $bundle_parameter_key = $fieldable_entity_type->getBundleEntityType() ?: 'bundle';
596
597    return [
598      $bundle_parameter_key => $bundle,
599      'view_mode_name' => $view_mode,
600      'entity' => $entity,
601    ];
602  }
603
604}