Completed
Push — master ( 23b86d...e09bdb )
by Marco
9s
created

SimpleCacheAdapter::setMultiple()   C

Complexity

Conditions 8
Paths 15

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 14
cts 14
cp 1
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 14
nc 15
nop 2
crap 8
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
            return $default;
88
        }
89
90 5
        return $value;
91
    }
92
93
    /**
94
     * {@inheritDoc}
95
     */
96 14
    public function set($key, $value, $ttl = null) : bool
97
    {
98 14
        $this->validateKey($key);
99
100 14
        if ($ttl === null) {
101 1
            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...
102
        }
103
104 13
        if ($ttl instanceof \DateInterval) {
105 1
            $ttl = $this->convertDateIntervalToInteger($ttl);
106
        }
107
108 13
        if (!is_integer($ttl)) {
109 10
            throw InvalidArgumentException::fromKeyAndInvalidTTL($key, $ttl);
110
        }
111
112 3
        if ($ttl <= 0) {
113 1
            return $this->delete($key);
114
        }
115
116 3
        return $this->doctrineCache->save($key, $value, $ttl);
117
    }
118
119
    /**
120
     * {@inheritDoc}
121
     */
122 5
    public function delete($key) : bool
123
    {
124 5
        $this->validateKey($key);
125 5
        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...
126
    }
127
128
    /**
129
     * {@inheritDoc}
130
     */
131 1
    public function clear() : bool
132
    {
133 1
        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...
134
    }
135
136
    /**
137
     * @param array|\Traversable $keys
138
     * @param mixed $default
139
     * @return array
140
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
141
     */
142 24
    public function getMultiple($keys, $default = null)
143
    {
144 24
        $keys = $this->filterValidateMultipleKeys($keys);
145 5
        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...
146
    }
147
148
    /**
149
     * @param array|\Traversable $values
150
     * @param null|int|\DateInterval $ttl
151
     * @return bool
152
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
153
     */
154 15
    public function setMultiple($values, $ttl = null) : bool
155
    {
156 15
        if (!$values instanceof \Traversable && !is_array($values)) {
157 1
            throw Exception\InvalidArgumentException::fromNonIterableKeys($values);
158
        }
159
160 14
        foreach ($values as $k => $v) {
161 14
            $this->validateKey($k);
162
        }
163
164 14
        if ($ttl === null) {
165 3
            return $this->doctrineCache->saveMultiple($values);
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...
Bug introduced by
It seems like $values defined by parameter $values on line 154 can also be of type object<Traversable>; however, Doctrine\Common\Cache\Mu...utCache::saveMultiple() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
166
        }
167
168 12
        if ($ttl instanceof \DateInterval) {
169 1
            $ttl = self::convertDateIntervalToInteger($ttl);
170
        }
171
172 12
        if (!is_integer($ttl)) {
173 10
            throw InvalidArgumentException::fromKeyAndInvalidTTL(key($values), $ttl);
174
        }
175
176 2
        if ($ttl <= 0) {
177 1
            return $this->deleteMultiple(array_keys($values));
178
        }
179
180 1
        return $this->doctrineCache->saveMultiple($values, $ttl);
0 ignored issues
show
Bug introduced by
It seems like $values defined by parameter $values on line 154 can also be of type object<Traversable>; however, Doctrine\Common\Cache\Mu...utCache::saveMultiple() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
181
    }
182
183
    /**
184
     * @param array|\Traversable $keys
185
     * @return bool
186
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
187
     */
188 3
    public function deleteMultiple($keys) : bool
189
    {
190 3
        $keys = $this->filterValidateMultipleKeys($keys);
191
192 3
        $success = true;
193 3
        foreach ($keys as $key) {
194 3
            if (!$this->delete($key)) {
195 3
                $success = false;
196
            }
197
        }
198
199 3
        return $success;
200
    }
201
202
    /**
203
     * {@inheritDoc}
204
     */
205 1
    public function has($key) : bool
206
    {
207 1
        $this->validateKey($key);
208 1
        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...
209
    }
210
211 2
    private function convertDateIntervalToInteger(\DateInterval $ttl) : int
212
    {
213
        // Timestamp has 2038 year limitation, but it's unlikely to set TTL that long.
214 2
        return (new \DateTime())
215 2
            ->setTimestamp(0)
216 2
            ->add($ttl)
217 2
            ->getTimestamp();
218
    }
219
}
220