Completed
Pull Request — master (#15)
by James
06:46
created

SimpleCacheAdapter::validateKey()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 8
cts 8
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 7
nc 4
nop 1
crap 4
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
12
final class SimpleCacheAdapter implements PsrCache
13
{
14
    /**
15
     * @var DoctrineCache|ClearableCache|MultiGetCache|MultiPutCache
16
     */
17
    private $doctrineCache;
18
19
    /**
20
     * @param DoctrineCache $doctrineCache
21
     * @throws \Roave\DoctrineSimpleCache\CacheException
22
     */
23 36
    public function __construct(DoctrineCache $doctrineCache)
24
    {
25 36
        $this->doctrineCache = $doctrineCache;
26
27 36
        if (!$this->doctrineCache instanceof ClearableCache) {
28 1
            throw CacheException::fromNonClearableCache($this->doctrineCache);
29
        }
30 35
        if (!$this->doctrineCache instanceof MultiGetCache) {
31 1
            throw CacheException::fromNonMultiGetCache($this->doctrineCache);
32
        }
33 34
        if (!$this->doctrineCache instanceof MultiPutCache) {
34 1
            throw CacheException::fromNonMultiPutCache($this->doctrineCache);
35
        }
36 33
    }
37
38
    /**
39
     * @param mixed $key
40
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
41
     */
42 30
    private function validateKey($key) : void
43
    {
44 30
        if (!is_string($key)) {
45 7
            throw Exception\InvalidArgumentException::fromInvalidType($key);
46
        }
47
48 23
        if ('' === $key) {
49 1
            throw Exception\InvalidArgumentException::fromEmptyKey();
50
        }
51
52 22
        if (preg_match('/[' . preg_quote('{}()/\@:', '/') . ']/', $key)) {
53 10
            throw Exception\InvalidArgumentException::fromInvalidKeyCharacters($key);
54
        }
55 12
    }
56
57
    /**
58
     * @param mixed $keys
59
     * @return array
60
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
61
     */
62 25
    private function filterValidateMultipleKeys($keys) : array
63
    {
64 25
        if ($keys instanceof \Traversable) {
65 2
            $keys = iterator_to_array($keys);
66
        }
67
68 25
        if (!is_array($keys)) {
69 1
            throw Exception\InvalidArgumentException::fromNonIterableKeys($keys);
70
        }
71
72 24
        array_map([$this, 'validateKey'], $keys);
73
74 6
        return $keys;
75
    }
76
77
    /**
78
     * {@inheritDoc}
79
     */
80 2
    public function get($key, $default = null)
81
    {
82 2
        $this->validateKey($key);
83
84 2
        $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...
85 2
        if ($value === false) {
86 1
            return $default;
87
        }
88
89 2
        return $value;
90
    }
91
92
    /**
93
     * {@inheritDoc}
94
     */
95 2
    public function set($key, $value, $ttl = null) : bool
96
    {
97 2
        $this->validateKey($key);
98 2
        return $this->doctrineCache->save($key, $value, $ttl);
0 ignored issues
show
Bug introduced by
It seems like $ttl defined by parameter $ttl on line 95 can also be of type null; however, Doctrine\Common\Cache\Cache::save() does only seem to accept integer, 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...
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...
99
    }
100
101
    /**
102
     * {@inheritDoc}
103
     */
104 3
    public function delete($key) : bool
105
    {
106 3
        $this->validateKey($key);
107 3
        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...
108
    }
109
110
    /**
111
     * {@inheritDoc}
112
     */
113 1
    public function clear() : bool
114
    {
115 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...
116
    }
117
118
    /**
119
     * @param array|\Traversable $keys
120
     * @param mixed $default
121
     * @return array
122
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
123
     */
124 23
    public function getMultiple($keys, $default = null)
125
    {
126 23
        $keys = $this->filterValidateMultipleKeys($keys);
127 4
        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...
128
    }
129
130
    /**
131
     * @param array|\Traversable $values
132
     * @param null|int|\DateInterval $ttl
133
     * @return bool
134
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
135
     */
136 3
    public function setMultiple($values, $ttl = null) : bool
137
    {
138 3
        if (!$values instanceof \Traversable && !is_array($values)) {
139 1
            throw Exception\InvalidArgumentException::fromNonIterableKeys($values);
140
        }
141
142 2
        foreach ($values as $k => $v) {
143 2
            $this->validateKey($k);
144
        }
145
146 2
        return $this->doctrineCache->saveMultiple($values, $ttl);
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 136 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...
Bug introduced by
It seems like $ttl defined by parameter $ttl on line 136 can also be of type null or object<DateInterval>; however, Doctrine\Common\Cache\Mu...utCache::saveMultiple() does only seem to accept integer, 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...
147
    }
148
149
    /**
150
     * @param array|\Traversable $keys
151
     * @return bool
152
     * @throws \Roave\DoctrineSimpleCache\Exception\InvalidArgumentException
153
     */
154 2
    public function deleteMultiple($keys) : bool
155
    {
156 2
        $keys = $this->filterValidateMultipleKeys($keys);
157
158 2
        $success = true;
159 2
        foreach ($keys as $key) {
160 2
            if (!$this->delete($key)) {
161 2
                $success = false;
162
            }
163
        }
164
165 2
        return $success;
166
    }
167
168
    /**
169
     * {@inheritDoc}
170
     */
171 1
    public function has($key) : bool
172
    {
173 1
        $this->validateKey($key);
174 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...
175
    }
176
}
177