Skip to content

Commit 236b79a

Browse files
committed
TASK: Make metadata collection and cache flush work
1 parent 845d598 commit 236b79a

7 files changed

Lines changed: 210 additions & 377 deletions

File tree

Lines changed: 16 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
<?php
22
namespace Flowpack\FullPageCache\Aspects;
33

4+
use Neos\Cache\Frontend\StringFrontend;
45
use Neos\Flow\Annotations as Flow;
56
use Neos\Flow\Aop\JoinPointInterface;
7+
use Neos\Utility\ObjectAccess;
68

79
/**
810
* @Flow\Aspect
@@ -20,47 +22,10 @@ class ContentCacheAspect
2022
private $shortestLifetime = null;
2123

2224
/**
23-
* @Flow\Before("method(Neos\Fusion\Core\Cache\ContentCache->createCacheSegment())")
25+
* @Flow\Inject
26+
* @var StringFrontend
2427
*/
25-
public function grabCachedSegment(JoinPointInterface $joinPoint)
26-
{
27-
$tags = $joinPoint->getMethodArgument('tags');
28-
$lifetime = $joinPoint->getMethodArgument('lifetime');
29-
30-
foreach ($tags as $tag) {
31-
$this->cacheTags[$tag] = true;
32-
}
33-
34-
if ($lifetime !== null && $lifetime < $this->shortestLifetime) {
35-
$this->shortestLifetime = $lifetime;
36-
}
37-
}
38-
39-
/**
40-
* @Flow\Before("method(Neos\Fusion\Core\Cache\ContentCache->createDynamicCachedSegment())")
41-
*/
42-
public function grabDynamicCachedSegment(JoinPointInterface $joinPoint)
43-
{
44-
$tags = $joinPoint->getMethodArgument('tags');
45-
$lifetime = $joinPoint->getMethodArgument('lifetime');
46-
47-
foreach ($tags as $tag) {
48-
$this->cacheTags[$tag] = true;
49-
}
50-
51-
if ($lifetime === null) {
52-
return;
53-
}
54-
55-
if ($this->shortestLifetime === null) {
56-
$this->shortestLifetime = $lifetime;
57-
return;
58-
}
59-
60-
if ($lifetime < $this->shortestLifetime) {
61-
$this->shortestLifetime = $lifetime;
62-
}
63-
}
28+
protected $cacheFrontend;
6429

6530
/**
6631
* @Flow\Before("method(Neos\Fusion\Core\Cache\ContentCache->createUncachedSegment())")
@@ -71,19 +36,20 @@ public function grabUncachedSegment(JoinPointInterface $joinPoint)
7136
}
7237

7338
/**
74-
* @return array
39+
* @Flow\Before("method(Neos\Neos\Fusion\Cache\ContentCacheFlusher->shutdownObject())")
40+
* @param JoinPointInterface $joinPoint
41+
*
42+
* @throws \Neos\Utility\Exception\PropertyNotAccessibleException
7543
*/
76-
public function getAllCacheTags(): array
44+
public function interceptNodeCacheFlush(JoinPointInterface $joinPoint)
7745
{
78-
return $this->sanitizeTags(array_keys($this->cacheTags));
79-
}
46+
$object = $joinPoint->getProxy();
8047

81-
/**
82-
* @return int|null
83-
*/
84-
public function getShortestLifetime(): ?int
85-
{
86-
return $this->shortestLifetime;
48+
$tags = ObjectAccess::getProperty($object, 'tagsToFlush', true);
49+
foreach ($tags as $tag => $_) {
50+
$tag = $this->sanitizeTag($tag);
51+
$this->cacheFrontend->flushByTag($tag);
52+
}
8753
}
8854

8955
/**
@@ -104,19 +70,4 @@ protected function sanitizeTag($tag)
10470
{
10571
return strtr($tag, '.:', '_-');
10672
}
107-
108-
/**
109-
* Sanitizes multiple tags with sanitizeTag()
110-
*
111-
* @param array $tags Multiple tags
112-
* @return array The sanitized tags
113-
*/
114-
protected function sanitizeTags(array $tags)
115-
{
116-
foreach ($tags as $key => $value) {
117-
$tags[$key] = $this->sanitizeTag($value);
118-
}
119-
120-
return $tags;
121-
}
12273
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
namespace Flowpack\FullPageCache\Cache;
3+
4+
use Neos\Cache\Frontend\StringFrontend;
5+
use Neos\Flow\Annotations as Flow;
6+
use Neos\Flow\Property\Exception\InvalidDataTypeException;
7+
use Neos\Flow\Utility\Environment;
8+
use Psr\Log\LoggerInterface;
9+
10+
/**
11+
* A string frontend that stores cache metadata (tags, lifetime) for entries
12+
* Copied from MOC.Varnish
13+
*/
14+
class MetadataAwareStringFrontend extends StringFrontend
15+
{
16+
const SEPARATOR = '|';
17+
18+
/**
19+
* Store metadata of all loaded cache entries indexed by identifier
20+
*
21+
* @var array
22+
*/
23+
protected $metadata = [];
24+
25+
/**
26+
* @Flow\Inject
27+
* @var Environment
28+
*/
29+
protected $environment;
30+
31+
/**
32+
* @Flow\Inject
33+
* @var LoggerInterface
34+
*/
35+
protected $logger;
36+
37+
/**
38+
* Set a cache entry and store additional metadata (tags and lifetime)
39+
*
40+
* {@inheritdoc}
41+
*/
42+
public function set(string $entryIdentifier, $content, array $tags = [], int $lifetime = null)
43+
{
44+
$content = $this->insertMetadata($content, $entryIdentifier, $tags, $lifetime);
45+
parent::set($entryIdentifier, $content, $tags, $lifetime);
46+
}
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
public function get(string $entryIdentifier)
52+
{
53+
$content = parent::get($entryIdentifier);
54+
if ($content !== false) {
55+
$content = $this->extractMetadata($entryIdentifier, $content);
56+
}
57+
58+
return $content;
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
public function getByTag(string $tag): array
65+
{
66+
$entries = parent::getByTag($tag);
67+
foreach ($entries as $identifier => $content) {
68+
$entries[$identifier] = $this->extractMetadata($identifier, $content);
69+
}
70+
71+
return $entries;
72+
}
73+
74+
/**
75+
* Insert metadata into the content
76+
*
77+
* @param string $content
78+
* @param string $entryIdentifier The identifier metadata
79+
* @param array $tags The tags metadata
80+
* @param integer $lifetime The lifetime metadata
81+
* @return string The content including the serialized metadata
82+
* @throws InvalidDataTypeException
83+
*/
84+
protected function insertMetadata($content, $entryIdentifier, array $tags, $lifetime)
85+
{
86+
if (!is_string($content)) {
87+
throw new InvalidDataTypeException('Given data is of type "' . gettype($content) . '", but a string is expected for string cache.', 1433155737);
88+
}
89+
$metadata = [
90+
'identifier' => $entryIdentifier,
91+
'tags' => $tags,
92+
'lifetime' => $lifetime
93+
];
94+
$metadataJson = json_encode($metadata);
95+
$this->metadata[$entryIdentifier] = $metadata;
96+
97+
return $metadataJson . self::SEPARATOR . $content;
98+
}
99+
100+
/**
101+
* Extract metadata from the content and store it
102+
*
103+
* @param string $entryIdentifier The entry identifier
104+
* @param string $content The raw content including serialized metadata
105+
* @return string The content without metadata
106+
* @throws InvalidDataTypeException
107+
*/
108+
protected function extractMetadata($entryIdentifier, $content)
109+
{
110+
$separatorIndex = strpos($content, self::SEPARATOR);
111+
if ($separatorIndex === false) {
112+
$exception = new InvalidDataTypeException('Could not find cache metadata in entry with identifier ' . $entryIdentifier, 1433155925);
113+
if ($this->environment->getContext()->isProduction()) {
114+
$this->logger->error($exception->getMessage());
115+
} else {
116+
throw $exception;
117+
}
118+
}
119+
120+
$metadataJson = substr($content, 0, $separatorIndex);
121+
$metadata = json_decode($metadataJson, true);
122+
if ($metadata === null) {
123+
$exception = new InvalidDataTypeException('Invalid cache metadata in entry with identifier ' . $entryIdentifier, 1433155926);
124+
if ($this->environment->getContext()->isProduction()) {
125+
$this->logger->error($exception->getMessage());
126+
} else {
127+
throw $exception;
128+
}
129+
}
130+
131+
$this->metadata[$entryIdentifier] = $metadata;
132+
133+
return substr($content, $separatorIndex + 1);
134+
}
135+
136+
/**
137+
* @return array Metadata of all loaded entries (indexed by identifier)
138+
*/
139+
public function getAllMetadata()
140+
{
141+
return $this->metadata;
142+
}
143+
}

0 commit comments

Comments
 (0)