Completed
Pull Request — master (#452)
by
unknown
02:45
created

Cache::setTtl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace Gaufrette\Adapter;
4
5
use Gaufrette\File;
6
use Gaufrette\Adapter;
7
use Gaufrette\Adapter\InMemory as InMemoryAdapter;
8
9
/**
10
 * Cache adapter.
11
 *
12
 * @author  Antoine Hérault <[email protected]>
13
 */
14
class Cache implements Adapter,
0 ignored issues
show
Coding Style introduced by
The first item in a multi-line implements list must be on the line following the implements keyword
Loading history...
15
                       MetadataSupporter
0 ignored issues
show
Coding Style introduced by
Expected 4 spaces before interface name; 23 found
Loading history...
16
{
17
    /**
18
     * @var Adapter
19
     */
20
    protected $source;
21
22
    /**
23
     * @var Adapter
24
     */
25
    protected $cache;
26
27
    /**
28
     * @var int
29
     */
30
    protected $ttl;
31
32
    /**
33
     * @var Adapter
34
     */
35
    protected $serializeCache;
36
37
    /**
38
     * @param Adapter $source         The source adapter that must be cached
39
     * @param Adapter $cache          The adapter used to cache the source
40
     * @param int     $ttl            Time to live of a cached file
41
     * @param Adapter $serializeCache The adapter used to cache serializations
42
     */
43
    public function __construct(Adapter $source, Adapter $cache, $ttl = 0, Adapter $serializeCache = null)
44
    {
45
        $this->source = $source;
46
        $this->cache = $cache;
47
        $this->ttl = $ttl;
48
49
        if (!$serializeCache) {
50
            $serializeCache = new InMemoryAdapter();
51
        }
52
        $this->serializeCache = $serializeCache;
53
    }
54
55
    /**
56
     * Returns the time to live of the cache.
57
     *
58
     * @return int $ttl
59
     */
60
    public function getTtl()
61
    {
62
        return $this->ttl;
63
    }
64
65
    /**
66
     * Defines the time to live of the cache.
67
     *
68
     * @param int $ttl
69
     */
70
    public function setTtl($ttl)
71
    {
72
        $this->ttl = $ttl;
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function read($key)
79
    {
80
        if ($this->needsReload($key)) {
81
            $contents = $this->source->read($key);
82
            $this->cache->write($key, $contents);
0 ignored issues
show
Bug introduced by
It seems like $contents defined by $this->source->read($key) on line 81 can also be of type boolean; however, Gaufrette\Adapter::write() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
83
        } else {
84
            $contents = $this->cache->read($key);
85
        }
86
87
        return $contents;
88
    }
89
90
    /**
91
     * {@inheritdoc}
92
     */
93
    public function rename($key, $new)
94
    {
95
        return $this->source->rename($key, $new) && $this->cache->rename($key, $new);
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function write($key, $content, array $metadata = null)
102
    {
103
        $bytesSource = $this->source->write($key, $content);
104
105
        if (false === $bytesSource) {
106
            return false;
107
        }
108
109
        $bytesCache = $this->cache->write($key, $content);
110
111
        if ($bytesSource !== $bytesCache) {
112
            return false;
113
        }
114
115
        return $bytesSource;
116
    }
117
118
    /**
119
     * {@inheritdoc}
120
     */
121
    public function exists($key)
122
    {
123
        if ($this->needsReload($key)) {
124
            return $this->source->exists($key);
125
        }
126
127
        return $this->cache->exists($key);
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function mtime($key)
134
    {
135
        return $this->source->mtime($key);
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function keys()
142
    {
143
        $cacheFile = 'keys.cache';
144
        if ($this->needsRebuild($cacheFile)) {
145
            $keys = $this->source->keys();
146
            sort($keys);
147
            $this->serializeCache->write($cacheFile, serialize($keys));
148
        } else {
149
            $keys = unserialize($this->serializeCache->read($cacheFile));
150
        }
151
152
        return $keys;
153
    }
154
155
    /**
156
     * {@inheritdoc}
157
     */
158
    public function delete($key)
159
    {
160
        return $this->source->delete($key) && $this->cache->delete($key);
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166
    public function isDirectory($key)
167
    {
168
        return $this->source->isDirectory($key);
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174
    public function setMetadata($key, $metadata)
175
    {
176
        if ($this->source instanceof MetadataSupporter) {
177
            $this->source->setMetadata($key, $metadata);
178
        }
179
180
        if ($this->cache instanceof MetadataSupporter) {
181
            $this->cache->setMetadata($key, $metadata);
182
        }
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188
    public function getMetadata($key)
189
    {
190
        if ($this->source instanceof MetadataSupporter) {
191
            return $this->source->getMetadata($key);
192
        }
193
194
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Gaufrette\Adapter\MetadataSupporter::getMetadata of type array.

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...
195
    }
196
197
    /**
198
     * Indicates whether the cache for the specified key needs to be reloaded.
199
     *
200
     * @param string $key
201
     */
202
    public function needsReload($key)
203
    {
204
        $needsReload = true;
205
206
        if ($this->cache->exists($key)) {
207
            try {
208
                $dateCache = $this->cache->mtime($key);
209
                $needsReload = false;
210
211
                if (time() - $this->ttl >= $dateCache) {
212
                    $dateSource = $this->source->mtime($key);
213
                    $needsReload = $dateCache < $dateSource;
214
                    if (!$needsReload) {
215
                        // we're not reloading from source now, but touch file mod time
216
                        // so that we only check the source once per $ttl interval
217
                        $this->cache->write($key, $this->cache->read($key));
0 ignored issues
show
Bug introduced by
It seems like $this->cache->read($key) targeting Gaufrette\Adapter::read() can also be of type boolean; however, Gaufrette\Adapter::write() does only seem to accept string, maybe add an additional type check?

This check looks at variables that 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...
218
                    }
219
                }
220
            } catch (\RuntimeException $e) {
221
            }
222
        }
223
224
        return $needsReload;
225
    }
226
227
    /**
228
     * Indicates whether the serialized cache file needs to be rebuild.
229
     *
230
     * @param string $cacheFile
231
     */
232
    public function needsRebuild($cacheFile)
233
    {
234
        $needsRebuild = true;
235
236
        if ($this->serializeCache->exists($cacheFile)) {
237
            try {
238
                $needsRebuild = time() - $this->ttl >= $this->serializeCache->mtime($cacheFile);
239
            } catch (\RuntimeException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
240
            }
241
        }
242
243
        return $needsRebuild;
244
    }
245
}
246