annotate core/lib/Drupal/Core/Render/BubbleableMetadata.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 4c8ae668cc8c
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Render;
Chris@0 4
Chris@0 5 use Drupal\Component\Utility\NestedArray;
Chris@0 6 use Drupal\Core\Cache\CacheableMetadata;
Chris@0 7
Chris@0 8 /**
Chris@0 9 * Value object used for bubbleable rendering metadata.
Chris@0 10 *
Chris@0 11 * @see \Drupal\Core\Render\RendererInterface::render()
Chris@0 12 */
Chris@0 13 class BubbleableMetadata extends CacheableMetadata implements AttachmentsInterface {
Chris@0 14
Chris@0 15 use AttachmentsTrait;
Chris@0 16
Chris@0 17 /**
Chris@0 18 * Merges the values of another bubbleable metadata object with this one.
Chris@0 19 *
Chris@0 20 * @param \Drupal\Core\Cache\CacheableMetadata $other
Chris@0 21 * The other bubbleable metadata object.
Chris@0 22 *
Chris@0 23 * @return static
Chris@0 24 * A new bubbleable metadata object, with the merged data.
Chris@0 25 */
Chris@0 26 public function merge(CacheableMetadata $other) {
Chris@0 27 $result = parent::merge($other);
Chris@0 28
Chris@0 29 // This is called many times per request, so avoid merging unless absolutely
Chris@0 30 // necessary.
Chris@0 31 if ($other instanceof BubbleableMetadata) {
Chris@0 32 if (empty($this->attachments)) {
Chris@0 33 $result->attachments = $other->attachments;
Chris@0 34 }
Chris@0 35 elseif (empty($other->attachments)) {
Chris@0 36 $result->attachments = $this->attachments;
Chris@0 37 }
Chris@0 38 else {
Chris@0 39 $result->attachments = static::mergeAttachments($this->attachments, $other->attachments);
Chris@0 40 }
Chris@0 41 }
Chris@0 42
Chris@0 43 return $result;
Chris@0 44 }
Chris@0 45
Chris@0 46 /**
Chris@0 47 * Applies the values of this bubbleable metadata object to a render array.
Chris@0 48 *
Chris@0 49 * @param array &$build
Chris@0 50 * A render array.
Chris@0 51 */
Chris@0 52 public function applyTo(array &$build) {
Chris@0 53 parent::applyTo($build);
Chris@0 54 $build['#attached'] = $this->attachments;
Chris@0 55 }
Chris@0 56
Chris@0 57 /**
Chris@0 58 * Creates a bubbleable metadata object with values taken from a render array.
Chris@0 59 *
Chris@0 60 * @param array $build
Chris@0 61 * A render array.
Chris@0 62 *
Chris@0 63 * @return static
Chris@0 64 */
Chris@0 65 public static function createFromRenderArray(array $build) {
Chris@0 66 $meta = parent::createFromRenderArray($build);
Chris@0 67 $meta->attachments = (isset($build['#attached'])) ? $build['#attached'] : [];
Chris@0 68 return $meta;
Chris@0 69 }
Chris@0 70
Chris@0 71 /**
Chris@0 72 * Creates a bubbleable metadata object from a depended object.
Chris@0 73 *
Chris@0 74 * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $object
Chris@0 75 * The object whose cacheability metadata to retrieve. If it implements
Chris@0 76 * CacheableDependencyInterface, its cacheability metadata will be used,
Chris@0 77 * otherwise, the passed in object must be assumed to be uncacheable, so
Chris@0 78 * max-age 0 is set.
Chris@0 79 *
Chris@0 80 * @return static
Chris@0 81 */
Chris@0 82 public static function createFromObject($object) {
Chris@0 83 $meta = parent::createFromObject($object);
Chris@0 84
Chris@0 85 if ($object instanceof AttachmentsInterface) {
Chris@0 86 $meta->attachments = $object->getAttachments();
Chris@0 87 }
Chris@0 88
Chris@0 89 return $meta;
Chris@0 90 }
Chris@0 91
Chris@0 92 /**
Chris@0 93 * {@inheritdoc}
Chris@0 94 */
Chris@0 95 public function addCacheableDependency($other_object) {
Chris@0 96 parent::addCacheableDependency($other_object);
Chris@0 97
Chris@0 98 if ($other_object instanceof AttachmentsInterface) {
Chris@0 99 $this->addAttachments($other_object->getAttachments());
Chris@0 100 }
Chris@0 101
Chris@0 102 return $this;
Chris@0 103 }
Chris@0 104
Chris@0 105 /**
Chris@0 106 * Merges two attachments arrays (which live under the '#attached' key).
Chris@0 107 *
Chris@0 108 * The values under the 'drupalSettings' key are merged in a special way, to
Chris@0 109 * match the behavior of:
Chris@0 110 *
Chris@0 111 * @code
Chris@0 112 * jQuery.extend(true, {}, $settings_items[0], $settings_items[1], ...)
Chris@0 113 * @endcode
Chris@0 114 *
Chris@0 115 * This means integer indices are preserved just like string indices are,
Chris@0 116 * rather than re-indexed as is common in PHP array merging.
Chris@0 117 *
Chris@0 118 * Example:
Chris@0 119 * @code
Chris@0 120 * function module1_page_attachments(&$page) {
Chris@0 121 * $page['a']['#attached']['drupalSettings']['foo'] = ['a', 'b', 'c'];
Chris@0 122 * }
Chris@0 123 * function module2_page_attachments(&$page) {
Chris@0 124 * $page['#attached']['drupalSettings']['foo'] = ['d'];
Chris@0 125 * }
Chris@0 126 * // When the page is rendered after the above code, and the browser runs the
Chris@0 127 * // resulting <SCRIPT> tags, the value of drupalSettings.foo is
Chris@0 128 * // ['d', 'b', 'c'], not ['a', 'b', 'c', 'd'].
Chris@0 129 * @endcode
Chris@0 130 *
Chris@0 131 * By following jQuery.extend() merge logic rather than common PHP array merge
Chris@0 132 * logic, the following are ensured:
Chris@0 133 * - Attaching JavaScript settings is idempotent: attaching the same settings
Chris@0 134 * twice does not change the output sent to the browser.
Chris@0 135 * - If pieces of the page are rendered in separate PHP requests and the
Chris@0 136 * returned settings are merged by JavaScript, the resulting settings are
Chris@0 137 * the same as if rendered in one PHP request and merged by PHP.
Chris@0 138 *
Chris@0 139 * @param array $a
Chris@0 140 * An attachments array.
Chris@0 141 * @param array $b
Chris@0 142 * Another attachments array.
Chris@0 143 *
Chris@0 144 * @return array
Chris@0 145 * The merged attachments array.
Chris@0 146 */
Chris@0 147 public static function mergeAttachments(array $a, array $b) {
Chris@0 148 // If both #attached arrays contain drupalSettings, then merge them
Chris@0 149 // correctly; adding the same settings multiple times needs to behave
Chris@0 150 // idempotently.
Chris@0 151 if (!empty($a['drupalSettings']) && !empty($b['drupalSettings'])) {
Chris@0 152 $drupalSettings = NestedArray::mergeDeepArray([$a['drupalSettings'], $b['drupalSettings']], TRUE);
Chris@0 153 // No need for re-merging them.
Chris@0 154 unset($a['drupalSettings']);
Chris@0 155 unset($b['drupalSettings']);
Chris@0 156 }
Chris@0 157 // Optimize merging of placeholders: no need for deep merging.
Chris@0 158 if (!empty($a['placeholders']) && !empty($b['placeholders'])) {
Chris@0 159 $placeholders = $a['placeholders'] + $b['placeholders'];
Chris@0 160 // No need for re-merging them.
Chris@0 161 unset($a['placeholders']);
Chris@0 162 unset($b['placeholders']);
Chris@0 163 }
Chris@0 164 // Apply the normal merge.
Chris@0 165 $a = array_merge_recursive($a, $b);
Chris@0 166 if (isset($drupalSettings)) {
Chris@0 167 // Save the custom merge for the drupalSettings.
Chris@0 168 $a['drupalSettings'] = $drupalSettings;
Chris@0 169 }
Chris@0 170 if (isset($placeholders)) {
Chris@0 171 // Save the custom merge for the placeholders.
Chris@0 172 $a['placeholders'] = $placeholders;
Chris@0 173 }
Chris@0 174 return $a;
Chris@0 175 }
Chris@0 176
Chris@0 177 }