Browse Source

Add 'cache_metadata' Twig filter

3.1.x
Chi 4 years ago
parent
commit
c6ce6aeea9
  1. 3
      composer.json
  2. 9
      docs/cheat-sheet.md
  3. 52
      src/CacheMetadataExtractor.php
  4. 14
      src/TwigTweakExtension.php
  5. 9
      src/UrlExtractor.php
  6. 91
      tests/src/Kernel/CacheMetadataExtractorTest.php
  7. 3
      twig_tweak.services.yml

3
composer.json

@ -13,7 +13,8 @@
"php": ">=7.3", "php": ">=7.3",
"ext-json": "*", "ext-json": "*",
"drupal/core": "^9.0", "drupal/core": "^9.0",
"twig/twig": "^2.12" "twig/twig": "^2.12",
"symfony/polyfill-php80": "^2.12"
}, },
"suggest": { "suggest": {
"symfony/var-dumper": "Better dump() function for debugging Twig variables" "symfony/var-dumper": "Better dump() function for debugging Twig variables"

9
docs/cheat-sheet.md

@ -289,7 +289,6 @@ For string arguments it works similar to core `file_url()` Twig function.
In order to generate absolute URL set "relative" parameter to `false`. In order to generate absolute URL set "relative" parameter to `false`.
```twig ```twig
{{ 'public://sea.jpg'|file_url(relative=false) }}
{{ 'public://sea.jpg'|file_url(false) }} {{ 'public://sea.jpg'|file_url(false) }}
``` ```
@ -318,6 +317,14 @@ That is typically needed when printing data from referenced entities.
{{ media|translation.title|view }} {{ media|translation.title|view }}
``` ```
## Cache metadata
When using raw values from entities or render arrays it is essential to
ensure that cache metadata are bubbled up.
```twig
<img src="{{ node.field_media|file_url }}" alt="Logo"/>
{{ content.field_media|cache_metadata }}
```
## PHP ## PHP
PHP filter is disabled by default. You can enable it in `settings.php` file as PHP filter is disabled by default. You can enable it in `settings.php` file as
follows: follows:

52
src/CacheMetadataExtractor.php

@ -0,0 +1,52 @@
<?php
namespace Drupal\twig_tweak;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Render\Element;
/**
* Cache metadata extractor service.
*/
class CacheMetadataExtractor {
/**
* Extracts cache metadata from object or render array.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|array $input
* The cacheable object or render array.
*
* @return array
* A render array with extracted cache metadata.
*/
public function extractCacheMetadata($input): array {
if ($input instanceof CacheableDependencyInterface) {
$cache_metadata = CacheableMetadata::createFromObject($input);
}
elseif (is_array($input)) {
$cache_metadata = self::extractFromArray($input);
}
else {
$message = sprintf('The input should be either instance of %s or array. %s was given.', CacheableDependencyInterface::class, \get_debug_type($input));
throw new \InvalidArgumentException($message);
}
$build = [];
$cache_metadata->applyTo($build);
return $build;
}
/**
* Extracts cache metadata from renders array.
*/
private function extractFromArray(array $build): CacheableMetadata {
$cache_metadata = CacheableMetadata::createFromRenderArray($build);
$keys = Element::children($build);
foreach (array_intersect_key($build, array_flip($keys)) as $item) {
$cache_metadata->addCacheableDependency(self::extractFromArray($item));
}
return $cache_metadata;
}
}

14
src/TwigTweakExtension.php

@ -113,6 +113,7 @@ class TwigTweakExtension extends AbstractExtension {
new TwigFilter('file_uri', [self::class, 'fileUriFilter']), new TwigFilter('file_uri', [self::class, 'fileUriFilter']),
new TwigFilter('file_url', [self::class, 'fileUrlFilter']), new TwigFilter('file_url', [self::class, 'fileUrlFilter']),
new TwigFilter('translation', [self::class, 'entityTranslation']), new TwigFilter('translation', [self::class, 'entityTranslation']),
new TwigFilter('cache_metadata', [self::class, 'CacheMetadata']),
]; ];
if (Settings::get('twig_tweak_enable_php_filter')) { if (Settings::get('twig_tweak_enable_php_filter')) {
@ -618,6 +619,19 @@ class TwigTweakExtension extends AbstractExtension {
return \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode); return \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode);
} }
/**
* Extracts cache metadata from object or render array.
*
* @param \Drupal\Core\Cache\CacheableDependencyInterface|array $input
* The cacheable object or render array.
*
* @return array
* A render array with extracted cache metadata.
*/
public static function cacheMetadata($input): array {
return \Drupal::service('twig_tweak.cache_metadata_extractor')->extractCacheMetadata($input);
}
/** /**
* Evaluates a string of PHP code. * Evaluates a string of PHP code.
* *

9
src/UrlExtractor.php

@ -5,8 +5,11 @@ namespace Drupal\twig_tweak;
use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface; use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem; use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\file\FileInterface; use Drupal\file\FileInterface;
use Drupal\link\LinkItemInterface;
use Drupal\link\Plugin\Field\FieldType\LinkItem;
use Drupal\media\MediaInterface; use Drupal\media\MediaInterface;
use Drupal\media\Plugin\media\Source\OEmbedInterface; use Drupal\media\Plugin\media\Source\OEmbedInterface;
@ -56,6 +59,12 @@ class UrlExtractor {
elseif ($input instanceof EntityReferenceItem) { elseif ($input instanceof EntityReferenceItem) {
return $this->getUrlFromEntity($input->entity, $relative); return $this->getUrlFromEntity($input->entity, $relative);
} }
elseif ($input instanceof LinkItemInterface) {
return $input->getUrl()->toString();
}
elseif ($input instanceof FieldItemList && $input->first() instanceof LinkItemInterface) {
return $input->first()->getUrl()->toString();
}
return NULL; return NULL;
} }

91
tests/src/Kernel/CacheMetadataExtractorTest.php

@ -0,0 +1,91 @@
<?php
namespace Drupal\Tests\twig_tweak\Kernel;
use Drupal\Core\Cache\CacheableMetadata;
/**
* A test for Cache Metadata Extractor service.
*
* @group twig_tweak
*/
final class CacheMetadataExtractorTest extends AbstractExtractorTestCase {
/**
* Test callback.
*/
public function testCacheMetadataExtractor(): void {
$extractor = $this->container->get('twig_tweak.cache_metadata_extractor');
// -- Object.
$input = new CacheableMetadata();
$input->setCacheMaxAge(5);
$input->setCacheContexts(['url', 'user.permissions']);
$input->setCacheTags(['node', 'node.view']);
$build = $extractor->extractCacheMetadata($input);
$expected_build['#cache'] = [
'contexts' => ['url', 'user.permissions'],
'tags' => ['node', 'node.view'],
'max-age' => 5,
];
self::assertSame($expected_build, $build);
// -- Render array.
$input = [
'foo' => [
'#cache' => [
'tags' => ['foo', 'foo.view'],
],
'bar' => [
0 => [
'#cache' => [
'tags' => ['bar-0'],
],
],
1 => [
'#cache' => [
'tags' => ['bar-1'],
],
],
'#cache' => [
'tags' => ['bar', 'bar.view'],
'contexts' => ['url.path'],
'max-age' => 10,
],
],
],
'#cache' => [
'contexts' => ['url', 'user.permissions'],
'tags' => ['node', 'node.view'],
'max-age' => 20,
],
];
$build = $extractor->extractCacheMetadata($input);
$expected_build = [
'#cache' => [
'contexts' => ['url', 'url.path', 'user.permissions'],
'tags' => [
'bar',
'bar-0',
'bar-1',
'bar.view',
'foo',
'foo.view',
'node',
'node.view',
],
'max-age' => 10,
],
];
self::assertSame($expected_build, $build);
// -- Wrong type.
self::expectErrorMessage('The input should be either instance of Drupal\Core\Cache\CacheableDependencyInterface or array. stdClass was given.');
/** @noinspection PhpParamsInspection */
$extractor->extractCacheMetadata(new \stdClass());
}
}

3
twig_tweak.services.yml

@ -39,3 +39,6 @@ services:
twig_tweak.uri_extractor: twig_tweak.uri_extractor:
class: Drupal\twig_tweak\UriExtractor class: Drupal\twig_tweak\UriExtractor
arguments: ['@entity_type.manager'] arguments: ['@entity_type.manager']
twig_tweak.cache_metadata_extractor:
class: Drupal\twig_tweak\CacheMetadataExtractor

Loading…
Cancel
Save