Completed
Push — 6.7_race_cond_testing ( 56a653 )
by André
18:41 queued 11s
created

CacheServiceDecorator::executeClear()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
c 0
b 0
f 0
cc 2
nc 2
nop 1
rs 9.9
1
<?php
2
3
/**
4
 * File containing the CacheServiceDecorator class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Persistence\Cache;
10
11
use eZ\Publish\Core\Persistence\Cache\Adapter\TransactionAwareAdapterInterface;
12
use eZ\Publish\Core\Persistence\Cache\Adapter\TransactionItem;
13
use Stash\Interfaces\PoolInterface;
14
use Tedivm\StashBundle\Service\CacheItem;
15
16
/**
17
 * Class CacheServiceDecorator.
18
 *
19
 * Wraps the Cache Service for Spi cache to apply key prefix for the cache
20
 */
21
class CacheServiceDecorator implements TransactionAwareAdapterInterface
22
{
23
    const SPI_CACHE_KEY_PREFIX = 'ez_spi';
24
25
    /**
26
     * @var \Stash\Interfaces\PoolInterface
27
     */
28
    protected $cachePool;
29
30
    /** @var int */
31
    protected $transactionDepth = 0;
32
33
    /** @var array */
34
    protected $deferredClear = [];
35
36
    /**
37
     * Constructs the cache service decorator.
38
     *
39
     * @param \Stash\Interfaces\PoolInterface $cachePool
40
     */
41
    public function __construct(PoolInterface $cachePool)
42
    {
43
        $this->cachePool = $cachePool;
44
    }
45
46
    /**
47
     * Prepend key with prefix and support array format Stash supported before.
48
     *
49
     * {@see \Psr\Cache\CacheItemPoolInterface}
50
     *
51
     * @internal param array|string $key , $key, $key...
52
     *
53
     * @return \Stash\Interfaces\ItemInterface
54
     */
55
    public function getItem()
56
    {
57
        $args = func_get_args();
58
59
        if (empty($args)) {
60
            return $this->handleTransactionItems(
61
                [$this->cachePool->getItem(self::SPI_CACHE_KEY_PREFIX)]
62
            )[0];
63
        }
64
65
        //  Upstream seems to no longer support array, so we flatten it
66
        if (!isset($args[1]) && is_array($args[0])) {
67
            $key = implode('/', array_map([$this, 'washKey'], $args[0]));
68 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
69
            $key = '' . implode('/', array_map([$this, 'washKey'], $args));
70
        }
71
72
        $key = $key === '' ? self::SPI_CACHE_KEY_PREFIX : self::SPI_CACHE_KEY_PREFIX . '/' . $key;
73
74
        return $this->handleTransactionItems(
75
            [$this->cachePool->getItem($key)]
76
        )[0];
77
    }
78
79
    /**
80
     * Prepend keys with prefix.
81
     *
82
     * {@see \Psr\Cache\CacheItemPoolInterface}
83
     *
84
     * @param array $keys
85
     * @return \Stash\Interfaces\ItemInterface[]
86
     */
87
    public function getItems(array $keys = [])
88
    {
89
        $prefix = self::SPI_CACHE_KEY_PREFIX;
90
        $keys = array_map(
91
            function ($key) use ($prefix) {
92
                $key = $this->washKey($key);
93
94
                return $key === '' ? $prefix : $prefix . '/' . $key;
95
            },
96
            $keys
97
        );
98
99
        return $this->handleTransactionItems(
100
            $this->cachePool->getItems($keys)
101
        );
102
    }
103
104
    /**
105
     * @param Item[] $items
106
     *
107
     * @return Item[]
108
     */
109
    private function handleTransactionItems(array $items)
110
    {
111
        if ($this->transactionDepth === 0) {
112
            return $items;
113
        }
114
115
        // If in transaction we set callback for save()/clear(), & for isMiss() detect if key is a deferred miss
116
        foreach ($items as $item) {
117
            /* @var TransactionItem $item */
118
            $item->setClearCallback(function ($key) {
119
                $this->rawClear([$key]);
120
            });
121
            $item->setIsClearedCallback(function ($key) {
122
                // Due to keys in Stash being hierarchical we need to check if key or prefix of key has been cleared
123
                foreach ($this->deferredClear as $clearedKey) {
124
                    if ($key === $clearedKey || stripos($key, $clearedKey) === 0) {
125
                        return true;
126
                    }
127
                }
128
129
                return false;
130
            });
131
        }
132
133
        return $items;
134
    }
135
136
    /**
137
     * Remove slashes from start and end of keys, and for content replace it with _ to avoid issues for Stash.
138
     *
139
     * @param string $key
140
     * @return string
141
     */
142
    private function washKey($key)
143
    {
144
        return str_replace('/', '_', trim($key, '/'));
145
    }
146
147
    /**
148
     * Clears the cache for the key, or if none is specified clears the entire cache. The key can be either
149
     * a series of string arguments, or an array.
150
     *
151
     * @internal
152
     * @param array|null|string $key , $key, $key...
153
     * @return bool
154
     */
155
    public function clear(...$key)
156
    {
157
        // Make washed string key out of the arguments
158
        if (empty($key)) {
159
            $key = self::SPI_CACHE_KEY_PREFIX;
160
        } elseif (!isset($key[1]) && is_array($key[0])) {
161
            $key = self::SPI_CACHE_KEY_PREFIX . '/' . implode('/', array_map([$this, 'washKey'], $key[0]));
162 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
163
            $key = self::SPI_CACHE_KEY_PREFIX . '/' . implode('/', array_map([$this, 'washKey'], $key));
164
        }
165
166
        return $this->rawClear([$key]);
167
    }
168
169
    private function rawClear(array $keys)
170
    {
171
        // Store for later if in transaction
172
        if ($this->transactionDepth) {
173
            $this->deferredClear = array_merge($this->deferredClear, $keys);
174
175
            return true;
176
        }
177
178
        return $this->executeClear($keys);
179
    }
180
181
    private function executeClear(array $keys)
182
    {
183
        // Detect full cache clear, if so ignore everything else
184
        if (in_array(self::SPI_CACHE_KEY_PREFIX, $keys, true)) {
185
            $item = $this->cachePool->getItem(self::SPI_CACHE_KEY_PREFIX);
186
187
            return $item->clear();
188
        }
189
190
        return $this->cachePool->deleteItems($keys);
191
    }
192
193
    /**
194
     * {@inheritdoc}.
195
     */
196
    public function beginTransaction()
197
    {
198
        if ($this->transactionDepth === 0) {
199
            // Wrap Item(s) in order to also handle calls to $item->save() and $item->clear()
200
            $this->cachePool->setItemClass(TransactionItem::class);
201
        }
202
        ++$this->transactionDepth;
203
    }
204
205
    /**
206
     * {@inheritdoc}.
207
     */
208
    public function commitTransaction()
209
    {
210
        if ($this->transactionDepth === 0) {
211
            // ignore, might have been a rollback
212
            return;
213
        }
214
215
        --$this->transactionDepth;
216
217
        // Cache commit time, it's now time to share the changes with the pool
218
        if ($this->transactionDepth === 0) {
219
            $this->cachePool->setItemClass(CacheItem::class);
220
            if (!empty($this->deferredClear)) {
221
                $this->executeClear($this->deferredClear);
222
                $this->deferredClear = [];
223
            }
224
        }
225
    }
226
227
    /**
228
     * {@inheritdoc}.
229
     */
230
    public function rollbackTransaction()
231
    {
232
        // A rollback in SQL will by default set transaction level to 0 & wipe transaction changes, so we do the same.
233
        $this->cachePool->setItemClass(CacheItem::class);
234
        $this->transactionDepth = 0;
235
        $this->deferredClear = [];
236
    }
237
}
238