Commit 8372e3d1 authored by Stephen Lucero's avatar Stephen Lucero
Browse files

Issue #3307170 by slucero, mariohernandez: Cache tags aren't persisted for...

Issue #3307170 by slucero, mariohernandez: Cache tags aren't persisted for placed patterns and blocks
parent 0f4f52bd
Loading
Loading
Loading
Loading
+11 −6
Original line number Diff line number Diff line
@@ -173,6 +173,12 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface {
    /** @var \Drupal\patternkit\Entity\PatternInterface $pattern */
    $pattern = $element['#pattern'];

    // Capture base bubbleable metadata.
    $metadata = BubbleableMetadata::createFromRenderArray($element);

    // Ensure the metadata from the pattern is present.
    $metadata->addCacheableDependency($pattern);

    // Fail early if a pattern was unable to be loaded.
    if (is_null($pattern)) {
      return [
@@ -184,14 +190,10 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface {
    $pattern->context = $element['#context'];

    try {
      $bubbleableMetadata = new BubbleableMetadata();
      $this->fieldProcessorPluginManager->processSchemaValues($pattern, $pattern->config, $pattern->context, $bubbleableMetadata);
      $this->fieldProcessorPluginManager->processSchemaValues($pattern, $pattern->config, $pattern->context, $metadata);

      $library_plugin = $this->getPatternLibraryPlugin($pattern);
      $elements = $library_plugin->render([$pattern]);

      // Apply all bubbleable metadata from preprocessing.
      $bubbleableMetadata->applyTo($elements);
    }
    catch (SchemaException $exception) {
      // Replace the pattern output with an error element for more sophisticated
@@ -207,6 +209,9 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface {
      ];
    }

    // Ensure all metadata persists to the result.
    $metadata->applyTo($elements);

    return $elements;
  }

@@ -225,7 +230,7 @@ class Pattern extends RenderElement implements ContainerFactoryPluginInterface {
    $pattern_plugin = $pattern->getLibraryPluginId();
    $library_plugin_id = !empty($pattern_plugin) ? $pattern_plugin : 'twig';

    /** @var \Drupal\patternkit\PatternLibraryPluginInterface */
    /** @var \Drupal\patternkit\PatternLibraryPluginInterface $plugin */
    $plugin = $this->libraryPluginManager->createInstance($library_plugin_id);
    return $plugin;
  }
+11 −0
Original line number Diff line number Diff line
@@ -403,4 +403,15 @@ class Pattern extends ContentEntityBase implements PatternInterface {
    return $assets;
  }

  /**
   * {@inheritdoc}
   */
  public function getCacheTags() {
    if (empty($this->cacheTags)) {
      $this->cacheTags[] = 'pattern:' . $this->getAssetId();
    }

    return parent::getCacheTags();
  }

}
+10 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Template\TwigEnvironment;
use Drupal\Core\Utility\Token;
use Drupal\patternkit\Entity\PatternInterface;
@@ -583,12 +584,20 @@ class PatternkitBlock extends BlockBase implements ContainerFactoryPluginInterfa
      $config = $this->serializer::decode(reset($data)['value']);
    }

    return [
    $element = [
      '#type' => 'pattern',
      '#pattern' => $pattern,
      '#config' => $config,
      '#context' => $context,
    ];

    // Capture cacheable metadata from dependencies.
    $metadata = new BubbleableMetadata();
    $metadata->addCacheableDependency($pattern);
    $metadata->addCacheableDependency($patternkit_block);
    $metadata->applyTo($element);

    return $element;
  }

  /**
+130 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\patternkit\Functional;

use Drupal\Core\Url;
use Drupal\node\Entity\Node;

/**
 * Block placement and cache testing for patternkit blocks.
 *
 * @group patternkit
 */
class BlockTest extends PatternkitBrowserTestBase {

  /**
   * {@inheritdoc}
   */
  static protected $modules = [
    'patternkit_example',
  ];

  /**
   * Tests that cache tags are properly set and bubbled up to the page cache.
   *
   * Verify that invalidation of these cache tags works:
   * - "patternkit_pattern:<pattern ID>"
   * - "patternkit_block:<block plugin ID>"
   */
  public function testBlockCacheTags(): void {
    // Place an example block in the layout.
    $pattern_name = '@patternkit/atoms/example/src/example';
    $block = $this->createPatternBlock($pattern_name, [
      'text' => 'Pattern block title',
      'formatted_text' => 'Pattern block body',
      'image_url' => '',
      'hidden' => 'Hidden text',
    ]);
    $this->placePatternBlockInLayout(Node::load(1), $block);

    $config = $block->getConfiguration();
    $block_id = $config['patternkit_block_id'];
    /** @var \Drupal\patternkit\Entity\PatternInterface $pattern */
    $pattern = $this->patternStorage->loadRevision($config['pattern']);
    $pattern_id = $pattern->id();

    // The page cache only works for anonymous users.
    $this->drupalLogout();

    // Enable page caching.
    $config = $this->config('system.performance');
    $config->set('cache.page.max_age', 300);
    $config->save();

    // Prime the page cache.
    $this->drupalGet('node/1');
    $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');

    // Verify a cache hit, but also the presence of the correct cache tags in
    // the page cache.
    $this->drupalGet('node/1');
    $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
    $cid_parts = [
      Url::fromUri('entity:node/1', ['absolute' => TRUE])
        ->toString(),
      '',
    ];
    $cid = implode(':', $cid_parts);
    $cache_entry = \Drupal::cache('page')->get($cid);
    $expected_cache_tags = [
      "pattern:$pattern_name",
      "patternkit_pattern:$pattern_id",
      "patternkit_block:$block_id",
    ];
    foreach ($expected_cache_tags as $tag) {
      $this->assertContains($tag, $cache_entry->tags);
    }

    // The pattern entity is modified; verify a cache miss.
    $pattern->setVersion(\Drupal::VERSION);
    $pattern->save();
    $this->drupalGet('node/1');
    $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');

    // Now we should have a cache hit again.
    $this->drupalGet('node/1');
    $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');

    // Place another pattern block; verify a cache miss.
    $new_pattern_name = '@patternkit/atoms/example_ref/src/example_ref';
    $new_block = $this->createPatternBlock($new_pattern_name, [
      'text' => 'Pattern block title',
      'nested_reference' => [
        'formatted_text' => 'Pattern block body',
        'image_url' => '',
        'hidden' => 'Hidden text',
      ],
    ]);
    $this->placePatternBlockInLayout(Node::load(1), $new_block);

    $config = $block->getConfiguration();
    $new_block_id = $config['patternkit_block_id'];
    /** @var \Drupal\patternkit\Entity\PatternInterface $pattern */
    $new_pattern = $this->patternStorage->loadRevision($config['pattern']);
    $new_pattern_id = $new_pattern->id();

    $this->drupalGet('node/1');
    $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');

    // Verify a cache hit, but also the presence of the correct cache tags.
    $this->drupalGet('node/1');
    $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
    $cache_entry = \Drupal::cache('page')->get($cid);
    $expected_cache_tags = [
      "pattern:$pattern_name",
      "patternkit_pattern:$pattern_id",
      "patternkit_block:$block_id",
      "pattern:$new_pattern_name",
      "patternkit_pattern:$new_pattern_id",
      "patternkit_block:$new_block_id",
    ];
    foreach ($expected_cache_tags as $tag) {
      $this->assertContains($tag, $cache_entry->tags);
    }

    // Now we should have a cache hit again.
    $this->drupalGet('node/1');
    $this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
  }

}
+19 −20
Original line number Diff line number Diff line
@@ -22,6 +22,20 @@ class PatternDiscoveryTest extends KernelTestBase {
    'text',
  ];

  /**
   * An array containing expected definitions to be discovered.
   *
   * @var array|\string[][]
   */
  protected array $expectedDefinitions = [
    '@patternkit' => [
      '@patternkit/atoms/example/src/example',
      '@patternkit/atoms/example_filtered/src/example_filtered',
      '@patternkit/atoms/example_filtered_multiple/src/example_filtered_multiple',
      '@patternkit/atoms/example_ref/src/example_ref',
    ],
  ];

  /**
   * The pattern discovery service being tested.
   *
@@ -42,31 +56,17 @@ class PatternDiscoveryTest extends KernelTestBase {
   * @covers ::getPatternsByNamespace
   */
  public function testGetPatternsByNamespace() {
    $expected_patterns = [
      '@patternkit/atoms/example/src/example',
      '@patternkit/atoms/example_filtered/src/example_filtered',
      '@patternkit/atoms/example_ref/src/example_ref',
      '@patternkit/atoms/example_filtered_multiple/src/example_filtered_multiple',
    ];

    $definitions = $this->discovery->getPatternsByNamespace('@patternkit');

    $this->assertIsArray($definitions);
    $this->assertCount(count($expected_patterns), $definitions);
    $this->assertEqualsCanonicalizing($expected_patterns, array_keys($definitions));
    $this->assertCount(count($this->expectedDefinitions['@patternkit']), $definitions);
    $this->assertEqualsCanonicalizing($this->expectedDefinitions['@patternkit'], array_keys($definitions));
  }

  /**
   * @covers ::getPatternDefinitions
   */
  public function testGetPatternDefinitions() {
    $expected_patterns = [
      '@patternkit/atoms/example/src/example',
      '@patternkit/atoms/example_filtered/src/example_filtered',
      '@patternkit/atoms/example_ref/src/example_ref',
      '@patternkit/atoms/example_filtered_multiple/src/example_filtered_multiple',
    ];

    $definitions = $this->discovery->getPatternDefinitions();

    $this->assertIsArray($definitions);
@@ -74,9 +74,8 @@ class PatternDiscoveryTest extends KernelTestBase {
    // With these enabled modules, there should only be one namespace defined.
    $this->assertEquals(['@patternkit'], array_keys($definitions));

    // Within the '@patternkit' namespace there should be a known list of
    // discovered definitions.
    $this->assertEqualsCanonicalizing($expected_patterns, array_keys($definitions['@patternkit']));
    // Within the @patternkit namespace we should know the definitions found.
    $this->assertEqualsCanonicalizing($this->expectedDefinitions['@patternkit'], array_keys($definitions['@patternkit']));
  }

  /**
@@ -89,7 +88,7 @@ class PatternDiscoveryTest extends KernelTestBase {
    $cacheEntry = $cache->get($collector::PERSISTENT_CACHE_ID);
    $this->assertFalse($cacheEntry, 'Expected the pattern discovery cache to be empty initially.');

    $definitions = $this->discovery->getPatternDefinitions();
    $this->discovery->getPatternDefinitions();

    // Trigger end of request destruction to persist data for testing.
    $collector->destruct();
Loading