Skip to content

Commit cc06174

Browse files
committed
Refactoring content cache functionality (#159)
1 parent ef5bfde commit cc06174

7 files changed

Lines changed: 325 additions & 224 deletions

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"psr/event-dispatcher-implementation": "1.0.0",
2323
"psr/log": "^1.1",
2424
"yiisoft/arrays": "^1.0",
25+
"yiisoft/cache": "^1.0",
2526
"yiisoft/files": "^1.0",
2627
"yiisoft/html": "^1.2",
2728
"yiisoft/json": "^1.0"

src/BaseView.php

Lines changed: 1 addition & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@
1313
use Yiisoft\View\Event\AfterRenderEventInterface;
1414
use Yiisoft\View\Exception\ViewNotFoundException;
1515

16-
use function count;
1716
use function dirname;
1817

1918
/**
2019
* @internal Base class for {@see View} and {@see WebView}.
2120
*/
22-
abstract class BaseView implements DynamicContentAwareInterface
21+
abstract class BaseView
2322
{
2423
protected EventDispatcherInterface $eventDispatcher;
2524

@@ -83,16 +82,6 @@ abstract class BaseView implements DynamicContentAwareInterface
8382
*/
8483
private array $viewFiles = [];
8584

86-
/**
87-
* @var DynamicContentAwareInterface[] A list of currently active dynamic content class instances.
88-
*/
89-
private array $cacheStack = [];
90-
91-
/**
92-
* @var array A list of placeholders for embedding dynamic contents.
93-
*/
94-
private array $dynamicPlaceholders = [];
95-
9685
public function __construct(string $basePath, EventDispatcherInterface $eventDispatcher, LoggerInterface $logger)
9786
{
9887
$this->basePath = $basePath;
@@ -433,110 +422,6 @@ public function localize(string $file, ?string $language = null, ?string $source
433422
return is_file($desiredFile) ? $desiredFile : $file;
434423
}
435424

436-
/**
437-
* Renders dynamic content returned by the given PHP statements.
438-
*
439-
* This method is mainly used together with content caching (fragment caching and page caching) when some portions
440-
* of the content (called *dynamic content*) should not be cached. The dynamic content must be returned by some PHP
441-
* statements. You can optionally pass additional parameters that will be available as variables in the PHP
442-
* statement:.
443-
*
444-
* ```php
445-
* <?= $this->renderDynamic('return foo($myVar);', [
446-
* 'myVar' => $model->getMyComplexVar(),
447-
* ]) ?>
448-
* ```
449-
*
450-
* @param string $statements the PHP statements for generating the dynamic content.
451-
* @param array $parameters the parameters (name-value pairs) that will be extracted and made
452-
* available in the $statement context. The parameters will be stored in the cache and be reused
453-
* each time $statement is executed. You should make sure, that these are safely serializable.
454-
*
455-
* @return string the placeholder of the dynamic content, or the dynamic content if there is no active content
456-
* cache currently.
457-
*/
458-
public function renderDynamic(string $statements, array $parameters = []): string
459-
{
460-
if (!empty($parameters)) {
461-
$statements = 'extract(unserialize(\'' .
462-
str_replace(['\\', '\''], ['\\\\', '\\\''], serialize($parameters)) .
463-
'\'));' . $statements;
464-
}
465-
466-
if (!empty($this->cacheStack)) {
467-
$n = count($this->dynamicPlaceholders);
468-
$placeholder = "<![CDATA[YII-DYNAMIC-$n-{$this->getPlaceholderSignature()}]]>";
469-
$this->addDynamicPlaceholder($placeholder, $statements);
470-
471-
return $placeholder;
472-
}
473-
474-
return $this->evaluateDynamicContent($statements);
475-
}
476-
477-
/**
478-
* Evaluates the given PHP statements.
479-
*
480-
* This method is mainly used internally to implement dynamic content feature.
481-
*
482-
* @param string $statements The PHP statements to be evaluated.
483-
*
484-
* @return mixed The return value of the PHP statements.
485-
*/
486-
public function evaluateDynamicContent(string $statements)
487-
{
488-
return eval($statements);
489-
}
490-
491-
/**
492-
* Returns a list of currently active dynamic content class instances.
493-
*
494-
* @return DynamicContentAwareInterface[] Class instances supporting dynamic contents.
495-
*/
496-
public function getDynamicContents(): array
497-
{
498-
return $this->cacheStack;
499-
}
500-
501-
/**
502-
* Adds a class instance supporting dynamic contents to the end of a list of currently active dynamic content class
503-
* instances.
504-
*
505-
* @param DynamicContentAwareInterface $instance Class instance supporting dynamic contents.
506-
*/
507-
public function pushDynamicContent(DynamicContentAwareInterface $instance): void
508-
{
509-
$this->cacheStack[] = $instance;
510-
}
511-
512-
/**
513-
* Removes a last class instance supporting dynamic contents from a list of currently active dynamic content class
514-
* instances.
515-
*/
516-
public function popDynamicContent(): void
517-
{
518-
array_pop($this->cacheStack);
519-
}
520-
521-
public function getDynamicPlaceholders(): array
522-
{
523-
return $this->dynamicPlaceholders;
524-
}
525-
526-
public function setDynamicPlaceholders(array $placeholders): void
527-
{
528-
$this->dynamicPlaceholders = $placeholders;
529-
}
530-
531-
public function addDynamicPlaceholder(string $name, string $statements): void
532-
{
533-
foreach ($this->cacheStack as $cache) {
534-
$cache->addDynamicPlaceholder($name, $statements);
535-
}
536-
537-
$this->dynamicPlaceholders[$name] = $statements;
538-
}
539-
540425
/**
541426
* This method is invoked right before {@see renderFile()} renders a view file.
542427
*

src/Cache/CachedContent.php

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\View\Cache;
6+
7+
use DateInterval;
8+
use InvalidArgumentException;
9+
use Yiisoft\Cache\CacheInterface;
10+
use Yiisoft\Cache\CacheKeyNormalizer;
11+
use Yiisoft\Cache\Dependency\Dependency;
12+
13+
use function array_merge;
14+
use function get_class;
15+
use function gettype;
16+
use function is_string;
17+
use function is_object;
18+
use function sprintf;
19+
use function strtr;
20+
21+
/**
22+
* CacheContent caches content, supports the use of dynamic content {@see DynamicContent} inside cached content.
23+
*/
24+
final class CachedContent
25+
{
26+
private string $id;
27+
private CacheInterface $cache;
28+
private CacheKeyNormalizer $cacheKeyNormalizer;
29+
30+
/**
31+
* @var array<string, DynamicContent>
32+
*/
33+
private array $dynamicContents = [];
34+
35+
/**
36+
* @var string[]
37+
*/
38+
private array $variations = [];
39+
40+
/**
41+
* @param string $id The unique identifier of the cached content.
42+
* @param CacheInterface $cache The cache instance.
43+
* @param DynamicContent[] $dynamicContents The dynamic content instances.
44+
* @param string[] $variations List of string factors that would cause the variation of the content being cached.
45+
*/
46+
public function __construct(string $id, CacheInterface $cache, array $dynamicContents = [], array $variations = [])
47+
{
48+
$this->id = $id;
49+
$this->cache = $cache;
50+
$this->cacheKeyNormalizer = new CacheKeyNormalizer();
51+
$this->setDynamicContents($dynamicContents);
52+
$this->setVariations($variations);
53+
}
54+
55+
/**
56+
* Caches, replaces placeholders with actual dynamic content, and returns the full actual content.
57+
*
58+
* @param string $content The content of the item to cache store.
59+
* @param DateInterval|int|null $ttl The TTL of the cached content.
60+
* @param Dependency|null $dependency The dependency of the cached content.
61+
* @param float $beta The value for calculating the range that is used for "Probably early expiration".
62+
*
63+
* @see CacheInterface::getOrSet()
64+
*
65+
* @return string The rendered cached content.
66+
*/
67+
public function cache(string $content, $ttl = 60, Dependency $dependency = null, float $beta = 1.0): string
68+
{
69+
return $this->replaceDynamicPlaceholders(
70+
$this->cache->getOrSet($this->cacheKey(), static fn (): string => $content, $ttl, $dependency, $beta),
71+
);
72+
}
73+
74+
/**
75+
* Returns cached content with placeholders replaced with actual dynamic content.
76+
*
77+
* @return string|null The cached content. Null is returned if valid content is not found in the cache.
78+
*/
79+
public function get(): ?string
80+
{
81+
$content = $this->cache->psr()->get($this->cacheKey());
82+
83+
if ($content === null) {
84+
return null;
85+
}
86+
87+
return $this->replaceDynamicPlaceholders($content);
88+
}
89+
90+
/**
91+
* Generates a unique key used for storing the content in cache.
92+
*
93+
* @return string A valid cache key.
94+
*/
95+
private function cacheKey(): string
96+
{
97+
return $this->cacheKeyNormalizer->normalize(array_merge([__CLASS__, $this->id], $this->variations));
98+
}
99+
100+
/**
101+
* Replaces placeholders with actual dynamic content.
102+
*
103+
* @param string $content The content to be replaced.
104+
*
105+
* @return string The content with replaced placeholders.
106+
*/
107+
private function replaceDynamicPlaceholders(string $content): string
108+
{
109+
$dynamicContents = [];
110+
111+
foreach ($this->dynamicContents as $dynamicContent) {
112+
$dynamicContents[$dynamicContent->placeholder()] = $dynamicContent->content();
113+
}
114+
115+
if (!empty($dynamicContents)) {
116+
$content = strtr($content, $dynamicContents);
117+
}
118+
119+
return $content;
120+
}
121+
122+
/**
123+
* Sets dynamic content instances.
124+
*
125+
* @param array $dynamicContents The dynamic content instances to set.
126+
*/
127+
private function setDynamicContents(array $dynamicContents): void
128+
{
129+
foreach ($dynamicContents as $dynamicContent) {
130+
if (!($dynamicContent instanceof DynamicContent)) {
131+
throw new InvalidArgumentException(sprintf(
132+
'Invalid dynamic content "%s" specified. It must be a "%s" instance.',
133+
is_object($dynamicContent) ? get_class($dynamicContent) : gettype($dynamicContent),
134+
DynamicContent::class,
135+
));
136+
}
137+
138+
$this->dynamicContents[$dynamicContent->id()] = $dynamicContent;
139+
}
140+
}
141+
142+
/**
143+
* Sets variations.
144+
*
145+
* @param array $variations The variations to set.
146+
*/
147+
private function setVariations(array $variations): void
148+
{
149+
foreach ($variations as $variation) {
150+
if (!is_string($variation)) {
151+
throw new InvalidArgumentException(sprintf(
152+
'Invalid variation "%s" specified. It must be a string type.',
153+
is_object($variation) ? get_class($variation) : gettype($variation),
154+
));
155+
}
156+
157+
$this->variations[] = $variation;
158+
}
159+
}
160+
}

src/Cache/DynamicContent.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\View\Cache;
6+
7+
/**
8+
* DynamicContent generates data for dynamic content that is used for cached content {@see CachedContent}.
9+
*/
10+
final class DynamicContent
11+
{
12+
private string $id;
13+
private array $parameters;
14+
15+
/**
16+
* @var callable
17+
*/
18+
private $contentGenerator;
19+
20+
/**
21+
* @param string $id The unique identifier of the dynamic content.
22+
* @param callable $contentGenerator PHP callable with the signature: `function (array $parameters = []): string;`.
23+
* @param array $parameters The parameters (name-value pairs) that will be passed in the $contentGenerator context.
24+
*/
25+
public function __construct(string $id, callable $contentGenerator, array $parameters = [])
26+
{
27+
$this->id = $id;
28+
$this->contentGenerator = $contentGenerator;
29+
$this->parameters = $parameters;
30+
}
31+
32+
/**
33+
* Returns the he unique identifier of the dynamic content.
34+
*
35+
* @return string The unique identifier of the dynamic content.
36+
*/
37+
public function id(): string
38+
{
39+
return $this->id;
40+
}
41+
42+
/**
43+
* Generates the dynamic content.
44+
*
45+
* @return string The generated dynamic content.
46+
*/
47+
public function content(): string
48+
{
49+
return ($this->contentGenerator)($this->parameters);
50+
}
51+
52+
/**
53+
* Returns the placeholder of the dynamic content.
54+
*
55+
* @return string The placeholder of the dynamic content.
56+
*/
57+
public function placeholder(): string
58+
{
59+
return "<![CDATA[YII-DYNAMIC-$this->id]]>";
60+
}
61+
}

0 commit comments

Comments
 (0)