Issues (7)

src/services/CacheFlagService.php (3 issues)

1
<?php
2
3
namespace mmikkel\cacheflag\services;
4
5
use Craft;
6
use craft\base\Component;
7
use craft\base\ElementInterface;
0 ignored issues
show
The type craft\base\ElementInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use craft\db\Query;
9
use craft\db\Table;
10
use craft\elements\Asset;
11
use craft\elements\Category;
12
use craft\elements\Entry;
13
use craft\elements\GlobalSet;
14
use craft\elements\Tag;
15
use craft\helpers\Db;
16
use craft\helpers\StringHelper;
17
18
use yii\caching\TagDependency;
19
20
use mmikkel\cacheflag\events\FlaggedTemplateCachesEvent;
21
use mmikkel\cacheflag\records\Flags;
22
23
24
/**
25
 * @author    Mats Mikkel Rummelhoff
26
 * @package   CacheFlag
27
 * @since     1.0.0
28
 */
29
class CacheFlagService extends Component
30
{
31
32
    /**
33
     * @event Event The event that is triggered before flagged template caches are deleted.
34
     */
35
    const EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES = 'beforeInvalidateFlaggedCaches';
36
37
    /**
38
     * @event Event The event that is triggered after flagged template caches are invalidated.
39
     */
40
    const EVENT_AFTER_INVALIDATE_FLAGGED_CACHES = 'afterInvalidateFlaggedCaches';
41
42
    /**
43
     * @return array
44
     */
45
    public function getAllFlags(): array
46
    {
47
        return (new Query())
48
            ->select('*')
49
            ->from([Flags::tableName()])
50
            ->all();
51
    }
52
53
    /**
54
     * @param string|string[]|null $flags
55
     * @param string $sourceColumn
56
     * @param string $sourceValue
57
     * @throws \Throwable
58
     * @throws \yii\db\Exception
59
     */
60
    public function saveFlags(string|array|null $flags, string $sourceColumn, string $sourceValue): void
61
    {
62
63
        if (empty($flags)) {
64
            return;
65
        }
66
67
        if (is_array($flags)) {
0 ignored issues
show
The condition is_array($flags) is always true.
Loading history...
68
            $flags = implode(',', $flags);
69
        }
70
71
        $uid = (new Query())
72
            ->select(['uid'])
73
            ->from(Flags::tableName())
74
            ->where([$sourceColumn => $sourceValue])
75
            ->scalar();
76
77
        $isNew = !$uid;
78
        if ($isNew) {
79
            $uid = StringHelper::UUID();
80
        }
81
82
        switch ($sourceColumn) {
83
            case 'sectionId':
84
                $sourceKey = 'section';
85
                $sourceValue = Db::uidById(Table::SECTIONS, (int)$sourceValue);
86
                break;
87
            case 'categoryGroupId':
88
                $sourceKey = 'categoryGroup';
89
                $sourceValue = Db::uidById(Table::CATEGORYGROUPS, (int)$sourceValue);
90
                break;
91
            case 'tagGroupId':
92
                $sourceKey = 'tagGroup';
93
                $sourceValue = Db::uidById(Table::TAGGROUPS, (int)$sourceValue);
94
                break;
95
            case 'userGroupId':
96
                $sourceKey = 'userGroup';
97
                $sourceValue = Db::uidById(Table::USERGROUPS, (int)$sourceValue);
98
                break;
99
            case 'volumeId':
100
                $sourceKey = 'volume';
101
                $sourceValue = Db::uidById(Table::VOLUMES, (int)$sourceValue);
102
                break;
103
            case 'globalSetId':
104
                $sourceKey = 'globalSet';
105
                $sourceValue = Db::uidById(Table::GLOBALSETS, (int)$sourceValue);
106
                break;
107
            case 'elementType':
108
                $sourceKey = 'elementType';
109
                break;
110
            default:
111
                return;
112
        }
113
114
        if (!$sourceValue) {
115
            return;
116
        }
117
118
        // Save it to the project config
119
        $path = "cacheFlags.{$uid}";
120
        Craft::$app->getProjectConfig()->set($path, [
121
            'source' => "$sourceKey:$sourceValue",
122
            'flags' => $flags,
123
        ]);
124
    }
125
126
    /**
127
     * @param string $sourceColumn
128
     * @param string $sourceValue
129
     * @return false|void
130
     */
131
    public function deleteFlagsBySource(string $sourceColumn, string $sourceValue)
132
    {
133
134
        $uid = (new Query())
135
            ->select(['uid'])
136
            ->from(Flags::tableName())
137
            ->where([$sourceColumn => $sourceValue])
138
            ->scalar();
139
140
        if (!$uid) {
141
            return false;
142
        }
143
144
        // Remove it from the project config
145
        $path = "cacheFlags.{$uid}";
146
        Craft::$app->getProjectConfig()->remove($path);
147
    }
148
149
    /**
150
     * Invalidate all flagged template caches
151
     */
152
    public function invalidateAllFlaggedCaches()
153
    {
154
        TagDependency::invalidate(Craft::$app->getCache(), 'cacheflag');
155
    }
156
157
    /**
158
     * @param ElementInterface $element
159
     * @return bool
160
     */
161
    public function invalidateFlaggedCachesByElement(ElementInterface $element): bool
162
    {
163
        // Collect all flags for this element
164
        $query = (new Query())
165
            ->select(['flags'])
166
            ->from(Flags::tableName());
167
168
        $elementType = \get_class($element);
169
        $dynamicFlags = ["element:$element->id", "element:$element->uid"];
170
171
        switch ($elementType) {
172
            case 'craft\elements\Asset':
173
                /** @var Asset $element */
174
                $query->orWhere([
175
                    'volumeId' => $element->volumeId,
176
                ]);
177
                $dynamicFlags[] = "asset:$element->id";
178
                $dynamicFlags[] = "asset:$element->uid";
179
                break;
180
            case 'craft\elements\Category':
181
                /** @var Category $element */
182
                $query->orWhere([
183
                    'categoryGroupId' => $element->groupId,
184
                ]);
185
                $dynamicFlags[] = "category:$element->id";
186
                $dynamicFlags[] = "category:$element->uid";
187
                break;
188
            case 'craft\elements\Entry':
189
                /** @var Entry $element */
190
                $query->orWhere([
191
                    'sectionId' => $element->sectionId,
192
                ]);
193
                $dynamicFlags[] = "entry:$element->id";
194
                $dynamicFlags[] = "entry:$element->uid";
195
                break;
196
            case 'craft\elements\GlobalSet':
197
                /** @var GlobalSet $element */
198
                $query->orWhere([
199
                    'globalSetId' => $element->id,
200
                ]);
201
                $dynamicFlags[] = "globalSet:$element->id";
202
                $dynamicFlags[] = "globalSet:$element->uid";
203
                break;
204
            case 'craft\elements\Tag':
205
                /** @var Tag $element */
206
                $query->orWhere([
207
                    'tagGroupId' => $element->groupId,
208
                ]);
209
                $dynamicFlags[] = "tag:$element->id";
210
                $dynamicFlags[] = "tag:$element->uid";
211
                break;
212
            case 'craft\elements\User':
213
                /** @var User $element */
214
                foreach ($element->getGroups() as $userGroup) {
215
                    $query->orWhere([
216
                        'userGroupId' => $userGroup->id,
217
                    ]);
218
                }
219
                $dynamicFlags[] = "user:$element->id";
220
                $dynamicFlags[] = "user:$element->uid";
221
                break;
222
        }
223
224
        $query->orWhere([
225
            'elementType' => $elementType,
226
        ]);
227
228
        $flags = \array_unique(\array_merge($query->column(), $dynamicFlags));
229
230
        return $this->invalidateFlaggedCachesByFlags($flags);
231
    }
232
233
    /**
234
     * @param string|string[]|null $flags
235
     * @return bool
236
     */
237
    public function invalidateFlaggedCachesByFlags(string|array|null $flags): bool
238
    {
239
240
        if (empty($flags)) {
241
            return false;
242
        }
243
244
        if (is_array($flags)) {
0 ignored issues
show
The condition is_array($flags) is always true.
Loading history...
245
            $flags = $this->implodeFlagsArray($flags);
246
        } else {
247
            $flags = preg_replace('/\s+/', '', $flags);
248
        }
249
250
        $flags = array_values(\array_unique(\explode(',', $flags)));
251
252
        if (empty($flags)) {
253
            return false;
254
        }
255
256
        // Fire a `beforeInvalidateFlaggedCaches` event
257
        if ($this->hasEventHandlers(self::EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES)) {
258
            $this->trigger(self::EVENT_BEFORE_INVALIDATE_FLAGGED_CACHES, new FlaggedTemplateCachesEvent([
259
                'flags' => $flags,
260
            ]));
261
        }
262
263
        $flagTags = array_map(function (string $flag) {
264
            return "cacheflag::$flag";
265
        }, $flags);
266
267
        TagDependency::invalidate(Craft::$app->getCache(), $flagTags);
268
269
        // Fire a 'afterInvalidateFlaggedCaches' event
270
        if ($this->hasEventHandlers(self::EVENT_AFTER_INVALIDATE_FLAGGED_CACHES)) {
271
            $this->trigger(self::EVENT_AFTER_INVALIDATE_FLAGGED_CACHES, new FlaggedTemplateCachesEvent([
272
                'flags' => $flags,
273
            ]));
274
        }
275
276
        return true;
277
    }
278
279
    /**
280
     * @param array $flagsArray
281
     * @return string
282
     */
283
    protected function implodeFlagsArray(array $flagsArray): string
284
    {
285
286
        $flags = '';
287
288
        foreach ($flagsArray as $item) {
289
            if (is_array($item)) {
290
                $flags .= "{$this->implodeFlagsArray($item)},";
291
            } else {
292
                $flags .= preg_replace('/\s+/', '', $item) . ',';
293
            }
294
        }
295
296
        return substr($flags, 0, 0 - strlen(','));
297
    }
298
}
299