Completed
Push — 10.x ( c22aa1 )
by Tim
12:07
created

GenericCacheAdapter   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 258
c 0
b 0
f 0
wmc 20
lcom 1
cbo 4
ccs 0
cts 84
cp 0
rs 10

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A resolveReference() 0 11 2
A cacheKey() 0 4 1
A isCached() 0 11 2
A notCached() 0 4 1
A addReference() 0 4 1
A toCache() 0 23 4
A fromCache() 0 4 1
A flushCache() 0 5 1
A removeCache() 0 5 1
A raiseCounter() 0 18 2
A mergeAttributesRecursive() 0 18 3
1
<?php
2
3
/**
4
 * TechDivision\Import\Cache\GenericCacheAdapter
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2019 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Cache;
22
23
use Psr\Cache\CacheItemPoolInterface;
24
use TechDivision\Import\ConfigurationInterface;
25
use TechDivision\Import\Utils\CacheKeyUtilInterface;
26
27
/**
28
 * Generic cache adapter implementation.
29
 *
30
 * @author    Tim Wagner <[email protected]>
31
 * @copyright 2019 TechDivision GmbH <[email protected]>
32
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
33
 * @link      https://github.com/techdivision/import
34
 * @link      http://www.techdivision.com
35
 */
36
class GenericCacheAdapter implements CacheAdapterInterface
37
{
38
39
    /**
40
     * The serial of the actual import process.
41
     *
42
     * @var string
43
     */
44
    protected $serial;
45
46
    /**
47
     * The cache for the query results.
48
     *
49
     * @var \Psr\Cache\CacheItemPoolInterface
50
     */
51
    protected $cache;
52
53
    /**
54
     * References that links to another cache entry.
55
     *
56
     * @var array
57
     */
58
    protected $references = array();
59
60
    /**
61
     * The cache key utility instance.
62
     *
63
     * @var \TechDivision\Import\Utils\CacheKeyUtilInterface
64
     */
65
    protected $cacheKeyUtil;
66
67
    /**
68
     * Initialize the cache handler with the passed cache and configuration instances.
69
     * .
70
     * @param \Psr\Cache\CacheItemPoolInterface                $cache         The cache instance
71
     * @param \TechDivision\Import\Utils\CacheKeyUtilInterface $cacheKeyUtil  The cache key utility instance
72
     * @param \TechDivision\Import\ConfigurationInterface      $configuration The configuration instance
73
     */
74
    public function __construct(
75
        CacheItemPoolInterface $cache,
76
        CacheKeyUtilInterface $cacheKeyUtil,
77
        ConfigurationInterface $configuration
78
    ) {
79
80
        // load the serial of the actual import process
81
        $this->serial = $configuration->getSerial();
82
83
        // set the cache and the cache key utility instance
84
        $this->cache = $cache;
85
        $this->cacheKeyUtil = $cacheKeyUtil;
86
    }
87
88
    /**
89
     * Resolve's the cache key.
90
     *
91
     * @param string $from The cache key to resolve
92
     *
93
     * @return string The resolved reference
94
     */
95
    protected function resolveReference($from)
96
    {
97
98
        // query whether or not a reference exists
99
        if (isset($this->references[$from])) {
100
            return $this->references[$from];
101
        }
102
103
        // return the passed reference
104
        return $from;
105
    }
106
107
    /**
108
     * Creates a unique cache key from the passed data.
109
     *
110
     * @param mixed $data The date to create the cache key from
111
     *
112
     * @return string The generated cache key
113
     */
114
    public function cacheKey($data)
115
    {
116
        return $this->cacheKeyUtil->cacheKey($data);
117
    }
118
119
    /**
120
     * Query whether or not a cache value for the passed cache key is available.
121
     *
122
     * @param string $key The cache key to query for
123
     *
124
     * @return boolean TRUE if the a value is available, else FALSE
125
     */
126
    public function isCached($key)
127
    {
128
129
        // query whether or not the item has been cached, and if yes if the cache is valid
130
        if ($this->cache->hasItem($resolvedKey = $this->resolveReference($this->cacheKey($key)))) {
131
            return $this->cache->getItem($resolvedKey)->isHit();
132
        }
133
134
        // return FALSE in all other cases
135
        return false;
136
    }
137
138
    /**
139
     * Inversion of the isCached() method.
140
     *
141
     * @param string $key The cache key to query for
142
     *
143
     * @return boolean TRUE if the value is not available, else FALSE
144
     */
145
    public function notCached($key)
146
    {
147
        return !$this->isCached($key);
148
    }
149
150
    /**
151
     * Add's a cache reference from one key to another.
152
     *
153
     * @param string $from The key to reference from
154
     * @param string $to   The key to reference to
155
     *
156
     * @return void
157
     */
158
    public function addReference($from, $to)
159
    {
160
        $this->references[$this->cacheKey($from)] = $this->cacheKey($to);
161
    }
162
163
    /**
164
     * Add the passed item to the cache.
165
     *
166
     * @param string  $key        The cache key to use
167
     * @param mixed   $value      The value that has to be cached
168
     * @param array   $references An array with references to add
169
     * @param boolean $override   Flag that allows to override an exising cache entry
170
     * @param integer $time       The TTL in seconds for the passed item
171
     *
172
     * @return void
173
     */
174
    public function toCache($key, $value, array $references = array(), $override = false, $time = null)
175
    {
176
177
        // create the unique cache key
178
        $uniqueKey = $this->cacheKey($key);
179
180
        // query whether or not the key has already been used
181
        if ($this->isCached($uniqueKey) && $override === false) {
182
            throw new \Exception(sprintf('Try to override data with key %s', $uniqueKey));
183
        }
184
185
        // initialize the cache item
186
        $cacheItem = $this->cache->getItem($uniqueKey);
187
        $cacheItem->set($value)->expiresAfter($time)->setTags(array($this->serial));
188
189
        // set the attribute in the registry
190
        $this->cache->save($cacheItem);
191
192
        // also register the references if given
193
        foreach ($references as $from => $to) {
194
            $this->addReference($from, $to);
195
        }
196
    }
197
198
    /**
199
     * Returns a new cache item for the passed key
200
     *
201
     * @param string $key The cache key to return the item for
202
     *
203
     * @return mixed The value for the passed key
204
     */
205
    public function fromCache($key)
206
    {
207
        return $this->cache->getItem($this->resolveReference($this->cacheKey($key)))->get();
208
    }
209
210
    /**
211
     * Flush the cache and remove the references.
212
     *
213
     * @return void
214
     */
215
    public function flushCache()
216
    {
217
        $this->cache->invalidateTag($this->serial);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Cache\CacheItemPoolInterface as the method invalidateTag() does only exist in the following implementations of said interface: Cache\Adapter\Common\AbstractCachePool, Cache\Adapter\PHPArray\ArrayCachePool.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
218
        $this->references = array();
219
    }
220
221
    /**
222
     * Remove the item with the passed key and all its references from the cache.
223
     *
224
     * @param string $key The key of the cache item to Remove
225
     *
226
     * @return void
227
     */
228
    public function removeCache($key)
229
    {
230
        $this->cache->deleteItem($this->resolveReference($uniqueKey = $this->cacheKey($key)));
231
        unset($this->references[$uniqueKey]);
232
    }
233
234
    /**
235
     * Raises the value for the attribute with the passed key by one.
236
     *
237
     * @param mixed $key         The key of the attribute to raise the value for
238
     * @param mixed $counterName The name of the counter to raise
239
     *
240
     * @return integer The counter's new value
241
     */
242
    public function raiseCounter($key, $counterName)
243
    {
244
245
        // initialize the counter
246
        $counter = 0;
247
248
        // raise/initialize the value
249
        if ($this->isCached($key)) {
250
            $value = $this->fromCache($key);
251
            $counter = $value[$counterName];
252
        }
253
254
        // set the counter value back to the cache item/cache
255
        $this->toCache($key, array($counterName => ++$counter), array(), true);
256
257
        // return the new value
258
        return $counter;
259
    }
260
261
    /**
262
     * This method merges the passed attributes with an array that
263
     * has already been added under the passed key.
264
     *
265
     * If no value will be found under the passed key, the attributes
266
     * will simply be registered.
267
     *
268
     * @param mixed $key        The key of the attributes that has to be merged with the passed ones
269
     * @param array $attributes The attributes that has to be merged with the exising ones
270
     *
271
     * @return void
272
     * @throws \Exception Is thrown, if the already registered value is no array
273
     * @link http://php.net/array_replace_recursive
274
     */
275
    public function mergeAttributesRecursive($key, array $attributes)
276
    {
277
278
        // if the key not exists, simply add the new attributes
279
        if ($this->notCached($key)) {
280
            $this->toCache($key, $attributes);
281
            return;
282
        }
283
284
        // if the key exists and the value is an array, merge it with the passed array
285
        if (is_array($value = $this->fromCache($key))) {
286
            $this->toCache($key, array_replace_recursive($value, $attributes), array(), true);
287
            return;
288
        }
289
290
        // throw an exception if the key exists, but the found value is not of type array
291
        throw new \Exception(sprintf('Can\'t merge attributes, because value for key %s already exists, but is not of type array', $key));
292
    }
293
}
294