BubbleableMetadata.php

Same filename in other branches
  1. 8.9.x core/lib/Drupal/Core/Render/BubbleableMetadata.php
  2. 10 core/lib/Drupal/Core/Render/BubbleableMetadata.php
  3. 11.x core/lib/Drupal/Core/Render/BubbleableMetadata.php

Namespace

Drupal\Core\Render

File

core/lib/Drupal/Core/Render/BubbleableMetadata.php

View source
<?php

namespace Drupal\Core\Render;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\CacheableMetadata;

/**
 * Value object used for bubbleable rendering metadata.
 *
 * @see \Drupal\Core\Render\RendererInterface::render()
 */
class BubbleableMetadata extends CacheableMetadata implements AttachmentsInterface {
    use AttachmentsTrait;
    
    /**
     * Creates a new bubbleable metadata object by merging this one with another.
     *
     * @param \Drupal\Core\Cache\CacheableMetadata $other
     *   The other bubbleable metadata object.
     *
     * @return static
     *   A new bubbleable metadata object, with the merged data.
     */
    public function merge(CacheableMetadata $other) {
        $result = parent::merge($other);
        // This is called many times per request, so avoid merging unless absolutely
        // necessary.
        if ($other instanceof BubbleableMetadata) {
            if (empty($this->attachments)) {
                $result->attachments = $other->attachments;
            }
            elseif (empty($other->attachments)) {
                $result->attachments = $this->attachments;
            }
            else {
                $result->attachments = static::mergeAttachments($this->attachments, $other->attachments);
            }
        }
        return $result;
    }
    
    /**
     * Applies the values of this bubbleable metadata object to a render array.
     *
     * @param array &$build
     *   A render array.
     */
    public function applyTo(array &$build) {
        parent::applyTo($build);
        $build['#attached'] = $this->attachments;
    }
    
    /**
     * Creates a bubbleable metadata object with values taken from a render array.
     *
     * @param array $build
     *   A render array.
     *
     * @return static
     */
    public static function createFromRenderArray(array $build) {
        $meta = parent::createFromRenderArray($build);
        $meta->attachments = isset($build['#attached']) ? $build['#attached'] : [];
        return $meta;
    }
    
    /**
     * Creates a bubbleable metadata object from a depended object.
     *
     * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $object
     *   The object whose cacheability metadata to retrieve. If it implements
     *   CacheableDependencyInterface, its cacheability metadata will be used,
     *   otherwise, the passed in object must be assumed to be uncacheable, so
     *   max-age 0 is set.
     *
     * @return static
     */
    public static function createFromObject($object) {
        $meta = parent::createFromObject($object);
        if ($object instanceof AttachmentsInterface) {
            $meta->attachments = $object->getAttachments();
        }
        return $meta;
    }
    
    /**
     * {@inheritdoc}
     */
    public function addCacheableDependency($other_object) {
        parent::addCacheableDependency($other_object);
        if ($other_object instanceof AttachmentsInterface) {
            $this->addAttachments($other_object->getAttachments());
        }
        return $this;
    }
    
    /**
     * Merges two attachments arrays (which live under the '#attached' key).
     *
     * The values under the 'drupalSettings' key are merged in a special way, to
     * match the behavior of:
     *
     * @code
     *   jQuery.extend(true, {}, $settings_items[0], $settings_items[1], ...)
     * @endcode
     *
     * This means integer indices are preserved just like string indices are,
     * rather than re-indexed as is common in PHP array merging.
     *
     * Example:
     * @code
     * function module1_page_attachments(&$page) {
     *   $page['a']['#attached']['drupalSettings']['foo'] = ['a', 'b', 'c'];
     * }
     * function module2_page_attachments(&$page) {
     *   $page['#attached']['drupalSettings']['foo'] = ['d'];
     * }
     * // When the page is rendered after the above code, and the browser runs the
     * // resulting <SCRIPT> tags, the value of drupalSettings.foo is
     * // ['d', 'b', 'c'], not ['a', 'b', 'c', 'd'].
     * @endcode
     *
     * By following jQuery.extend() merge logic rather than common PHP array merge
     * logic, the following are ensured:
     * - Attaching JavaScript settings is idempotent: attaching the same settings
     *   twice does not change the output sent to the browser.
     * - If pieces of the page are rendered in separate PHP requests and the
     *   returned settings are merged by JavaScript, the resulting settings are
     *   the same as if rendered in one PHP request and merged by PHP.
     *
     * @param array $a
     *   An attachments array.
     * @param array $b
     *   Another attachments array.
     *
     * @return array
     *   The merged attachments array.
     */
    public static function mergeAttachments(array $a, array $b) {
        // If both #attached arrays contain drupalSettings, then merge them
        // correctly; adding the same settings multiple times needs to be
        // idempotent.
        if (!empty($a['drupalSettings']) && !empty($b['drupalSettings'])) {
            $drupalSettings = NestedArray::mergeDeepArray([
                $a['drupalSettings'],
                $b['drupalSettings'],
            ], TRUE);
            // No need for re-merging them.
            unset($a['drupalSettings']);
            unset($b['drupalSettings']);
        }
        // Optimize merging of placeholders: no need for deep merging.
        if (!empty($a['placeholders']) && !empty($b['placeholders'])) {
            $placeholders = $a['placeholders'] + $b['placeholders'];
            // No need for re-merging them.
            unset($a['placeholders']);
            unset($b['placeholders']);
        }
        // Apply the normal merge.
        $a = array_merge_recursive($a, $b);
        if (isset($drupalSettings)) {
            // Save the custom merge for the drupalSettings.
            $a['drupalSettings'] = $drupalSettings;
        }
        if (isset($placeholders)) {
            // Save the custom merge for the placeholders.
            $a['placeholders'] = $placeholders;
        }
        return $a;
    }

}

Classes

Title Deprecated Summary
BubbleableMetadata Value object used for bubbleable rendering metadata.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.