Completed
Pull Request — master (#19)
by Marco
02:29
created

SimpleCacheAdapter::get()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 2
crap 3
1
<?php
2
declare(strict_types = 1);
3
4
namespace Roave\DoctrineSimpleCache;
5
6
use Doctrine\Common\Cache\Cache as DoctrineCache;
7
use Doctrine\Common\Cache\ClearableCache;
8
use Doctrine\Common\Cache\MultiGetCache;
9
use Doctrine\Common\Cache\MultiPutCache;
10
use Psr\SimpleCache\CacheInterface as PsrCache;
11
use Roave\DoctrineSimpleCache\Exception\InvalidArgumentException;
12
13
final class SimpleCacheAdapter implements PsrCache
14
{
15
    /**
16
     * @var DoctrineCache|ClearableCache|MultiGetCache|MultiPutCache
17
     */
18
    private $doctrineCache;
19
20
    /**
21
     * @param DoctrineCache $doctrineCache
22
     * @throws \Roave\DoctrineSimpleCache\Exception\CacheException
23
     */
24 60
    public function __construct(DoctrineCache $doctrineCache)
25
    {
26 60
        $this->doctrineCache = $doctrineCache;
27
28 60
        if (!$this->doctrineCache instanceof ClearableCache) {
29 1
            throw Exception\CacheException::fromNonClearableCache($this->doctrineCache);
30
        }
31 59
        if (!$this->doctrineCache instanceof MultiGetCache) {
32 1
            throw Exception\CacheException::fromNonMultiGetCache($this->doctrineCache);
33
        }
34 58
        if (!$this->doctrineCache instanceof MultiPutCache) {
35 1
            throw Exception\CacheException::fromNonMultiPutCache($this->doctrineCache);
36
        }
37 57
    }
38
39
    /**
40
     * @param mixed $key
41
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
42
     */
43 54
    private function validateKey($key) : void
44
    {
45 54
        if (!is_string($key)) {
46 7
            throw Exception\InvalidArgumentException::fromInvalidType($key);
47
        }
48
49 47
        if ('' === $key) {
50 1
            throw Exception\InvalidArgumentException::fromEmptyKey();
51
        }
52
53 46
        if (preg_match('/[' . preg_quote('{}()/\@:', '/') . ']/', $key)) {
54 10
            throw Exception\InvalidArgumentException::fromInvalidKeyCharacters($key);
55
        }
56 36
    }
57
58
    /**
59
     * @param mixed $keys
60
     * @return array
61
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
62
     */
63 27
    private function filterValidateMultipleKeys($keys) : array
64
    {
65 27
        if ($keys instanceof \Traversable) {
66 2
            $keys = iterator_to_array($keys);
67
        }
68
69 27
        if (!is_array($keys)) {
70 1
            throw Exception\InvalidArgumentException::fromNonIterableKeys($keys);
71
        }
72
73 26
        array_map([$this, 'validateKey'], $keys);
74
75 8
        return $keys;
76
    }
77
78
    /**
79
     * {@inheritDoc}
80
     */
81 5
    public function get($key, $default = null)
82
    {
83 5
        $this->validateKey($key);
84
85 5
        $value = $this->doctrineCache->fetch($key);
0 ignored issues
show
Bug introduced by
The method fetch does only exist in Doctrine\Common\Cache\Cache, but not in Doctrine\Common\Cache\Cl...mon\Cache\MultiPutCache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
86 5
        if ($value === false) {
87 3
            // Doctrine cache returns `false` when cache doesn't contain, but also `false` if the value stored is
88
            // `false`, so check to see if the cache contains the key; if so, we probably meant to return `false`
89
            if ($this->doctrineCache->contains($key)) {
0 ignored issues
show
Bug introduced by
The method contains does only exist in Doctrine\Common\Cache\Cache, but not in Doctrine\Common\Cache\Cl...mon\Cache\MultiPutCache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
90 5
                return false;
91
            }
92
            return $default;
93
        }
94
95
        return $value;
96 14
    }
97
98 14
    /**
99
     * {@inheritDoc}
100 14
     */
101 1
    public function set($key, $value, $ttl = null) : bool
102
    {
103
        $this->validateKey($key);
104 13
105 1
        if ($ttl === null) {
106
            return $this->doctrineCache->save($key, $value);
0 ignored issues
show
Bug introduced by
The method save does only exist in Doctrine\Common\Cache\Cache, but not in Doctrine\Common\Cache\Cl...mon\Cache\MultiPutCache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
107
        }
108 13
109 10
        if ($ttl instanceof \DateInterval) {
110
            $ttl = $this->convertDateIntervalToInteger($ttl);
111
        }
112 3
113 1
        if (!is_integer($ttl)) {
114
            throw InvalidArgumentException::fromKeyAndInvalidTTL($key, $ttl);
115
        }
116 3
117
        if ($ttl <= 0) {
118
            return $this->delete($key);
119
        }
120
121
        return $this->doctrineCache->save($key, $value, $ttl);
122 5
    }
123
124 5
    /**
125 5
     * {@inheritDoc}
126
     */
127
    public function delete($key) : bool
128
    {
129
        $this->validateKey($key);
130
        return $this->doctrineCache->delete($key);
0 ignored issues
show
Bug introduced by
The method delete does only exist in Doctrine\Common\Cache\Cache, but not in Doctrine\Common\Cache\Cl...mon\Cache\MultiPutCache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
131 1
    }
132
133 1
    /**
134
     * {@inheritDoc}
135
     */
136
    public function clear() : bool
137
    {
138
        return $this->doctrineCache->deleteAll();
0 ignored issues
show
Bug introduced by
The method deleteAll does only exist in Doctrine\Common\Cache\ClearableCache, but not in Doctrine\Common\Cache\Ca...mon\Cache\MultiPutCache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
139
    }
140
141
    /**
142 24
     * @param array|\Traversable $keys
143
     * @param mixed $default
144 24
     * @return array
145 5
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
146
     */
147
    public function getMultiple($keys, $default = null)
148
    {
149
        $keys = $this->filterValidateMultipleKeys($keys);
150
        return array_merge(array_fill_keys($keys, $default), $this->doctrineCache->fetchMultiple($keys));
0 ignored issues
show
Bug introduced by
The method fetchMultiple does only exist in Doctrine\Common\Cache\MultiGetCache, but not in Doctrine\Common\Cache\Ca...mon\Cache\MultiPutCache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
Bug Best Practice introduced by
The return type of return array_merge(array...>fetchMultiple($keys)); (array) is incompatible with the return type declared by the interface Psr\SimpleCache\CacheInterface::getMultiple of type Psr\SimpleCache\iterable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
151
    }
152
153
    /**
154 15
     * @param array|\Traversable $values
155
     * @param null|int|\DateInterval $ttl
156 15
     * @return bool
157 1
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
158
     */
159
    public function setMultiple($values, $ttl = null) : bool
160 14
    {
161 14
        if (!$values instanceof \Traversable && !is_array($values)) {
162 14
            throw Exception\InvalidArgumentException::fromNonIterableKeys($values);
163 14
        }
164
165
        $validatedValues = [];
166 14
        foreach ($values as $k => $v) {
167 3
            $this->validateKey($k);
168
            $validatedValues[$k] = $v;
169
        }
170 12
171 1
        if ($ttl === null) {
172
            return $this->doctrineCache->saveMultiple($validatedValues);
0 ignored issues
show
Bug introduced by
The method saveMultiple does only exist in Doctrine\Common\Cache\MultiPutCache, but not in Doctrine\Common\Cache\Ca...mon\Cache\MultiGetCache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
173
        }
174 12
175 10
        if ($ttl instanceof \DateInterval) {
176
            $ttl = self::convertDateIntervalToInteger($ttl);
177
        }
178 2
179 1
        if (!is_integer($ttl)) {
180
            throw InvalidArgumentException::fromKeyAndInvalidTTL(key($validatedValues), $ttl);
181
        }
182 1
183
        if ($ttl <= 0) {
184
            return $this->deleteMultiple(array_keys($validatedValues));
185
        }
186
187
        return $this->doctrineCache->saveMultiple($validatedValues, $ttl);
188
    }
189
190 3
    /**
191
     * @param array|\Traversable $keys
192 3
     * @return bool
193
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
194 3
     */
195 3
    public function deleteMultiple($keys) : bool
196 3
    {
197 3
        $keys = $this->filterValidateMultipleKeys($keys);
198
199
        $success = true;
200
        foreach ($keys as $key) {
201 3
            if (!$this->delete($key)) {
202
                $success = false;
203
            }
204
        }
205
206
        return $success;
207 1
    }
208
209 1
    /**
210 1
     * {@inheritDoc}
211
     */
212
    public function has($key) : bool
213 2
    {
214
        $this->validateKey($key);
215
        return $this->doctrineCache->contains($key);
0 ignored issues
show
Bug introduced by
The method contains does only exist in Doctrine\Common\Cache\Cache, but not in Doctrine\Common\Cache\Cl...mon\Cache\MultiPutCache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
216 2
    }
217 2
218 2
    private function convertDateIntervalToInteger(\DateInterval $ttl) : int
219 2
    {
220
        // Timestamp has 2038 year limitation, but it's unlikely to set TTL that long.
221
        return (new \DateTime())
222
            ->setTimestamp(0)
223
            ->add($ttl)
224
            ->getTimestamp();
225
    }
226
}
227