Commit dc53be5b authored by alexpott's avatar alexpott

Issue #2483433 by damiankloip, Wim Leers, catch, dawehner: Optimize...

Issue #2483433 by damiankloip, Wim Leers, catch, dawehner: Optimize CacheableMetadata::merge() + BubbleableMetadata::merge()
parent 068edfc1
......@@ -32,6 +32,7 @@
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Routing\GeneratorNotInitializedException;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AnonymousUserSession;
......@@ -571,10 +572,10 @@ function drupal_js_defaults($data = NULL) {
* The merged #attached array.
*
* @deprecated To be removed in Drupal 8.0.x. Use
* \Drupal\Core\Render\Renderer::mergeAttachments() instead.
* \Drupal\Core\Render\BubbleableMetadata::mergeAttachments() instead.
*/
function drupal_merge_attached(array $a, array $b) {
return \Drupal::service('renderer')->mergeAttachments($a, $b);
return BubbleableMetadata::mergeAttachments($a, $b);
}
/**
......
......@@ -8,6 +8,7 @@
namespace Drupal\Core\Ajax;
use Drupal\Core\Asset\AttachedAssets;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\Renderer;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
......@@ -79,7 +80,7 @@ public function addCommand(CommandInterface $command, $prepend = FALSE) {
'library' => $assets->getLibraries(),
'drupalSettings' => $assets->getSettings(),
];
$attachments = $this->getRenderer()->mergeAttachments($this->attachments, $attachments);
$attachments = BubbleableMetadata::mergeAttachments($this->attachments, $attachments);
$this->setAttachments($attachments);
}
......
......@@ -140,9 +140,38 @@ public function setCacheMaxAge($max_age) {
*/
public function merge(CacheableMetadata $other) {
$result = new static();
$result->contexts = Cache::mergeContexts($this->contexts, $other->contexts);
$result->tags = Cache::mergeTags($this->tags, $other->tags);
$result->maxAge = Cache::mergeMaxAges($this->maxAge, $other->maxAge);
// This is called many times per request, so avoid merging unless absolutely
// necessary.
if (empty($this->contexts)) {
$result->contexts = $other->contexts;
}
elseif (empty($other->contexts)) {
$result->contexts = $this->contexts;
}
else {
$result->contexts = Cache::mergeContexts($this->contexts, $other->contexts);
}
if (empty($this->tags)) {
$result->tags = $other->tags;
}
elseif (empty($other->tags)) {
$result->tags = $this->tags;
}
else {
$result->tags = Cache::mergeTags($this->tags, $other->tags);
}
if ($this->maxAge === Cache::PERMANENT) {
$result->maxAge = $other->maxAge;
}
elseif ($other->maxAge === Cache::PERMANENT) {
$result->maxAge = $this->maxAge;
}
else {
$result->maxAge = Cache::mergeMaxAges($this->maxAge, $other->maxAge);
}
return $result;
}
......
......@@ -35,9 +35,21 @@ class BubbleableMetadata extends CacheableMetadata {
*/
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) {
$result->attached = \Drupal::service('renderer')->mergeAttachments($this->attached, $other->attached);
if (empty($this->attached)) {
$result->attached = $other->attached;
}
elseif (empty($other->attached)) {
$result->attached = $this->attached;
}
else {
$result->attached = static::mergeAttachments($this->attached, $other->attached);
}
}
return $result;
}
......@@ -142,4 +154,76 @@ public function setAssets(array $assets) {
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 behave
// idempotently.
if (!empty($a['drupalSettings']) && !empty($b['drupalSettings'])) {
$drupalSettings = NestedArray::mergeDeepArray(array($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;
}
}
......@@ -349,48 +349,4 @@ public function mergeBubbleableMetadata(array $a, array $b);
*/
public function addCacheableDependency(array &$elements, $dependency);
/**
* 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 function mergeAttachments(array $a, array $b);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment