Passed
Push — master ( 6afa72...f96468 )
by M. Mikkel
04:08
created

TemplateCachesService::_cacheKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 2
dl 0
loc 9
rs 10
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: mmikkel
5
 * Date: 14/07/2018
6
 * Time: 11:55
7
 */
8
9
namespace mmikkel\cacheflag\services;
10
11
use Craft;
12
use craft\base\Component;
13
use craft\helpers\StringHelper;
14
use craft\services\TemplateCaches;
15
use yii\caching\TagDependency;
16
use DateTime;
17
18
/**
19
 * Class TemplateCachesService
20
 * @author    Mats Mikkel Rummelhoff
21
 * @package mmikkel\cacheflag\services
22
 * @since 1.0.0
23
 */
24
class TemplateCachesService extends Component
25
{
26
27
    /** @var bool */
28
    private $_collectElementTags = false;
29
30
    /**
31
     * @var string|null The current request's path
32
     * @see _path()
33
     */
34
    private $_path;
35
36
    // Public Methods
37
    // =========================================================================
38
    /**
39
     * Returns a cached template by its key.
40
     *
41
     * @param string $key The template cache key
42
     * @param string|string[]|null $flags The Cache Flag flags this cache would've been flagged with
43
     * @param bool $collectElementTags Whether to cache element queries or not
44
     * @param bool $global Whether the cache would have been stored globally.
45
     * @return string|null
46
     */
47
    public function getTemplateCache(string $key, $flags, bool $collectElementTags, bool $global)
48
    {
49
        // Make sure template caching is enabled
50
        if ($this->_isTemplateCachingEnabled() === false) {
51
            return null;
52
        }
53
54
        $this->_collectElementTags = $collectElementTags;
55
56
        $cacheKey = $this->_cacheKey($key, $global);
57
        $data = Craft::$app->getCache()->get($cacheKey);
58
59
        if ($data === false) {
60
            return null;
61
        }
62
63
        list($body, $tags) = $data;
64
65
        // Make sure the cache was tagged w/ the same flags
66
        $flagTags = $this->_getTagsForFlags($flags);
67
        $cachedFlagTags = \array_filter($tags, function (string $tag) {
68
            return \strpos($tag, 'cacheflag') === 0 || $tag === 'element';
69
        });
70
71
        if (\array_diff($flagTags, $cachedFlagTags) != \array_diff($cachedFlagTags, $flagTags)) {
72
            return null;
73
        }
74
75
        // If we're actively collecting element cache tags, add this cache's tags to the collection
76
        Craft::$app->getElements()->collectCacheTags($tags);
77
        return $body;
78
    }
79
80
    /**
81
     *
82
     */
83
    public function startTemplateCache()
84
    {
85
        // Make sure template caching is enabled
86
        if ($this->_isTemplateCachingEnabled() === false) {
87
            return;
88
        }
89
90
        if ($this->_collectElementTags) {
91
            Craft::$app->getElements()->startCollectingCacheTags();
92
        }
93
    }
94
95
    /**
96
     * Ends a template cache.
97
     *
98
     * @param string $key The template cache key.
99
     * @param string|null $flags The flags this cache should be flagged with.
100
     * @param bool $global Whether the cache should be stored globally.
101
     * @param string|null $duration How long the cache should be stored for. Should be a [relative time format](http://php.net/manual/en/datetime.formats.relative.php).
102
     * @param mixed|null $expiration When the cache should expire.
103
     * @param string $body The contents of the cache.
104
     * @throws \Throwable
105
     */
106
    public function endTemplateCache(string $key, $flags, bool $global, string $duration = null, /** @scrutinizer ignore-unused */ $expiration, string $body)
107
    {
108
109
        // Make sure template caching is enabled
110
        if ($this->_isTemplateCachingEnabled() === false) {
111
            return;
112
        }
113
114
        // If there are any transform generation URLs in the body, don't cache it.
115
        // stripslashes($body) in case the URL has been JS-encoded or something.
116
        if (StringHelper::contains(stripslashes($body), 'assets/generate-transform')) {
117
            return;
118
        }
119
120
        // Get flag tags
121
        $flagTags = $this->_getTagsForFlags($flags);
122
123
        if ($this->_collectElementTags) {
124
            // If we're collecting element tags, collect the flag tags too, and end the collection
125
            Craft::$app->getElements()->collectCacheTags($flagTags);
126
            $dep = Craft::$app->getElements()->stopCollectingCacheTags();
127
        } else {
128
            // If not, just tag it with the flags
129
            $dep = new TagDependency([
130
                'tags' => $flagTags,
131
            ]);
132
        }
133
134
        $cacheKey = $this->_cacheKey($key, $global);
135
136
        if ($duration !== null) {
137
            $duration = (new DateTime($duration))->getTimestamp() - time();
138
        }
139
140
        Craft::$app->getCache()->set($cacheKey, [$body, $dep->tags], $duration, $dep);
141
    }
142
143
    // Private Methods
144
    // =========================================================================
145
    /**
146
     * Returns whether template caching is enabled, based on the 'enableTemplateCaching' config setting.
147
     *
148
     * @return bool Whether template caching is enabled
149
     */
150
    private function _isTemplateCachingEnabled(): bool
151
    {
152
        return !!Craft::$app->getConfig()->getGeneral()->enableTemplateCaching;
153
    }
154
155
    /**
156
     * Defines a data cache key that should be used for a template cache.
157
     *
158
     * @param string $key
159
     * @param bool $global
160
     */
161
    private function _cacheKey(string $key, bool $global): string
162
    {
163
        $cacheKey = "template::$key::" . Craft::$app->getSites()->getCurrentSite()->id;
164
165
        if (!$global) {
166
            $cacheKey .= '::' . $this->_path();
167
        }
168
169
        return $cacheKey;
170
    }
171
172
    /**
173
     * Returns the current request path, including a "site:" or "cp:" prefix.
174
     *
175
     * @return string
176
     */
177
    private function _path(): string
178
    {
179
        if ($this->_path !== null) {
180
            return $this->_path;
181
        }
182
183
        if (Craft::$app->getRequest()->getIsCpRequest()) {
184
            $this->_path = 'cp:';
185
        } else {
186
            $this->_path = 'site:';
187
        }
188
189
        $this->_path .= Craft::$app->getRequest()->getPathInfo();
190
        if (Craft::$app->getDb()->getIsMysql()) {
191
            $this->_path = StringHelper::encodeMb4($this->_path);
192
        }
193
194
        if (($pageNum = Craft::$app->getRequest()->getPageNum()) != 1) {
195
            $this->_path .= '/' . Craft::$app->getConfig()->getGeneral()->getPageTrigger() . $pageNum;
196
        }
197
198
        return $this->_path;
199
    }
200
201
    /**
202
     * @param string|array|null $flags
203
     * @param string $delimiter
204
     * @return array
205
     */
206
    private function _getTagsForFlags($flags, string $delimiter = '|'): array
207
    {
208
        $tagsArray = ['cacheflag'];
209
        if (\is_array($flags)) {
210
            $flags = \implode(',', \array_map(function ($flag) {
211
                return \preg_replace('/\s+/', '', $flag);
212
            }, $flags));
213
        } else {
214
            $flags = \preg_replace('/\s+/', '', $flags);
215
        }
216
        $flags = \array_filter(\explode($delimiter, $flags));
217
        $tagsArray = \array_merge($tagsArray, \array_map(function (string $flag) {
218
            return "cacheflag::$flag";
219
        }, $flags));
220
        if ($this->_collectElementTags) {
221
            $tagsArray[] = 'element';
222
        }
223
        return \array_unique(($tagsArray));
224
    }
225
226
}
227