Completed
Push — 6.7 ( fb8957...f97c26 )
by André
18:17
created

CacheServiceDecorator::handleTransactionItems()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
c 0
b 0
f 0
cc 6
nc 3
nop 1
rs 8.8817
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\Item\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 $deferredClearKeys = [];
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 \Stash\Interfaces\ItemInterface[] $items
106
     * @return \Stash\Interfaces\ItemInterface[]
107
     */
108
    private function handleTransactionItems(array $items)
109
    {
110
        if ($this->transactionDepth === 0) {
111
            return $items;
112
        }
113
114
        // If in transaction we set callback for save()/clear(), & for isMiss() detect if key is a deferred miss
115
        foreach ($items as $item) {
116
            /* @var TransactionItem $item */
117
            $item->setClearCallback(function ($key) {
118
                $this->clearOrDefer([$key]);
119
            });
120
            $item->setIsClearedCallback(function ($key) {
121
                // Due to keys in Stash being hierarchical we need to check if key or prefix of key has been cleared
122
                foreach ($this->deferredClearKeys as $clearedKey) {
123
                    if ($key === $clearedKey || stripos($key, $clearedKey) === 0) {
124
                        return true;
125
                    }
126
                }
127
128
                return false;
129
            });
130
        }
131
132
        return $items;
133
    }
134
135
    /**
136
     * Remove slashes from start and end of keys, and for content replace it with _ to avoid issues for Stash.
137
     *
138
     * @param string $key
139
     * @return string
140
     */
141
    private function washKey($key)
142
    {
143
        return str_replace('/', '_', trim($key, '/'));
144
    }
145
146
    /**
147
     * Clears the cache for the key, or if none is specified clears the entire cache. The key can be either
148
     * a series of string arguments, or an array.
149
     *
150
     * @internal
151
     * @param array|null|string $key , $key, $key...
152
     * @return bool
153
     */
154
    public function clear(...$key)
155
    {
156
        // Make washed string key out of the arguments
157
        if (empty($key)) {
158
            $key = self::SPI_CACHE_KEY_PREFIX;
159
        } elseif (!isset($key[1]) && is_array($key[0])) {
160
            $key = self::SPI_CACHE_KEY_PREFIX . '/' . implode('/', array_map([$this, 'washKey'], $key[0]));
161 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...
162
            $key = self::SPI_CACHE_KEY_PREFIX . '/' . implode('/', array_map([$this, 'washKey'], $key));
163
        }
164
165
        return $this->clearOrDefer([$key]);
166
    }
167
168
    private function clearOrDefer(array $keys)
169
    {
170
        // Store for later if in transaction
171
        if ($this->transactionDepth > 0) {
172
            $this->deferredClearKeys = array_merge($this->deferredClearKeys, $keys);
173
174
            return true;
175
        }
176
177
        return $this->executeClear($keys);
178
    }
179
180
    private function executeClear(array $keys)
181
    {
182
        // Detect full cache clear, if so ignore everything else
183
        if (in_array(self::SPI_CACHE_KEY_PREFIX, $keys, true)) {
184
            $item = $this->cachePool->getItem(self::SPI_CACHE_KEY_PREFIX);
185
186
            return $item->clear();
187
        }
188
189
        return $this->cachePool->deleteItems($keys);
190
    }
191
192
    /**
193
     * {@inheritdoc}.
194
     */
195
    public function beginTransaction()
196
    {
197
        if ($this->transactionDepth === 0) {
198
            // Wrap Item(s) in order to also handle calls to $item->save() and $item->clear()
199
            $this->cachePool->setItemClass(TransactionItem::class);
200
        }
201
        ++$this->transactionDepth;
202
    }
203
204
    /**
205
     * {@inheritdoc}.
206
     */
207
    public function commitTransaction()
208
    {
209
        if ($this->transactionDepth === 0) {
210
            // ignore, might have been a rollback
211
            return;
212
        }
213
214
        --$this->transactionDepth;
215
216
        // Cache commit time, it's now time to share the changes with the pool
217
        if ($this->transactionDepth === 0) {
218
            $this->cachePool->setItemClass(CacheItem::class);
219
            if (!empty($this->deferredClearKeys)) {
220
                $this->executeClear($this->deferredClearKeys);
221
                $this->deferredClearKeys = [];
222
            }
223
        }
224
    }
225
226
    /**
227
     * {@inheritdoc}.
228
     */
229
    public function rollbackTransaction()
230
    {
231
        // A rollback in SQL will by default set transaction level to 0 & wipe transaction changes, so we do the same.
232
        $this->cachePool->setItemClass(CacheItem::class);
233
        $this->transactionDepth = 0;
234
        $this->deferredClearKeys = [];
235
    }
236
}
237