File::get()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 33
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 33
rs 8.439
cc 6
eloc 18
nc 6
nop 2
1
<?php
2
3
namespace Sugarcrm\UpgradeSpec\Cache\Adapter;
4
5
use Psr\SimpleCache\CacheInterface;
6
use Sugarcrm\UpgradeSpec\Cache\Exception\InvalidArgumentException;
7
8
/**
9
 * PSR-16 compatible file based cache adapter.
10
 */
11
class File implements CacheInterface
12
{
13
    use AdapterTrait;
14
15
    /**
16
     * @var string
17
     */
18
    private $cachePath;
19
20
    /**
21
     * File constructor.
22
     *
23
     * @param $path
24
     * @param $ttl
25
     */
26
    public function __construct($path, $ttl = 3600)
27
    {
28
        $this->createDirectory($path);
29
30
        $this->cachePath = realpath($path);
31
        $this->ttl = $ttl;
32
33
        $this->cleanExpired();
34
    }
35
36
    /**
37
     * @param string $key
38
     * @param null   $default
39
     *
40
     * @return bool|mixed|null
41
     */
42
    public function get($key, $default = null)
43
    {
44
        $this->validateKey($key);
45
46
        $path = $this->getPath($key);
47
48
        $expire = @filemtime($path);
49
        if ($expire === false) {
50
            return $default; // file not found
51
        }
52
53
        if (time() >= $expire) {
54
            @unlink($path); // file expired
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
55
56
            return $default;
57
        }
58
59
        $data = @file_get_contents($path);
60
        if ($data === false) {
61
            return $default; // race condition: file not found
62
        }
63
64
        if ($data === 'b:0;') {
65
            return false; // because we can't otherwise distinguish a FALSE from unserialize()
66
        }
67
68
        $value = @unserialize($data);
69
        if ($value === false) {
70
            return $default; // unserialize() failed
71
        }
72
73
        return $value;
74
    }
75
76
    /**
77
     * @param string $key
78
     * @param mixed  $value
79
     * @param null   $ttl
80
     *
81
     * @return bool
82
     *
83
     * @throws InvalidArgumentException
84
     */
85
    public function set($key, $value, $ttl = null)
86
    {
87
        $this->validateKey($key);
88
89
        $path = $this->getPath($key);
90
        $this->createDirectory(dirname($path));
91
92
        $expire = $this->getExpire($ttl);
93
94
        $tempPath = $this->cachePath . DS . uniqid('', true);
95
        if (false === @file_put_contents($tempPath, serialize($value))) {
96
            return false;
97
        }
98
99
        if (@touch($tempPath, $expire) && @rename($tempPath, $path)) {
100
            return true;
101
        }
102
103
        @unlink($tempPath);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
104
105
        return false;
106
    }
107
108
    /**
109
     * @param string $key
110
     *
111
     * @return bool
112
     */
113
    public function delete($key)
114
    {
115
        $this->validateKey($key);
116
117
        if (!$this->has($key)) {
118
            return false;
119
        }
120
121
        @unlink($this->getPath($key));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
122
123
        return true;
124
    }
125
126
    /**
127
     * Remove all cache entries.
128
     */
129
    public function clear()
130
    {
131 View Code Duplication
        foreach ($this->getCacheFolderIterator() as $fileInfo) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
132
            if ($fileInfo->isDir()) {
133
                @rmdir($fileInfo->getRealPath());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
134
            }
135
136
            @unlink($fileInfo->getRealPath());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
137
        }
138
139
        @rmdir($this->cachePath);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
140
141
        return true;
142
    }
143
144
    /**
145
     * @param string $key
146
     *
147
     * @return bool
148
     *
149
     * @throws InvalidArgumentException
150
     */
151
    public function has($key)
152
    {
153
        $this->validateKey($key);
154
155
        return $this->get($key, $this) !== $this;
156
    }
157
158
    /**
159
     * Clean up expired cache entries.
160
     */
161
    private function cleanExpired()
162
    {
163
        $now = time();
164 View Code Duplication
        foreach ($this->getCacheFolderIterator() as $fileInfo) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
165
            if (!$fileInfo->isDir() && $now > filemtime($fileInfo->getRealPath())) {
166
                @unlink($fileInfo->getRealPath());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
167
            }
168
        }
169
    }
170
171
    /**
172
     * Creates writable directory in given path.
173
     *
174
     * @param $directoryPath
175
     *
176
     * @throws InvalidArgumentException
177
     */
178
    private function createDirectory($directoryPath)
179
    {
180
        if (!file_exists($directoryPath)) {
181
            @mkdir($directoryPath, 0777, true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
182
        }
183
184
        $directoryPath = realpath($directoryPath);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $directoryPath. This often makes code more readable.
Loading history...
185
        if ($directoryPath === false) {
186
            throw new InvalidArgumentException(sprintf('Cache path does not exist: %s', $directoryPath));
187
        }
188
189
        if (!is_writable($directoryPath . DS)) {
190
            throw new InvalidArgumentException(sprintf('Cache path is not writable: %s', $directoryPath));
191
        }
192
    }
193
194
    /**
195
     * For a given cache key, obtain the absolute file path.
196
     *
197
     * @param string $key
198
     *
199
     * @return string absolute path to cache file
200
     */
201
    private function getPath($key)
202
    {
203
        $hash = hash('sha256', $key);
204
205
        return implode(DS, [$this->cachePath, mb_strtoupper($hash[0]), mb_strtoupper($hash[1]), mb_substr($hash, 2)]);
206
    }
207
208
    /**
209
     * @return \RecursiveIteratorIterator
210
     */
211
    private function getCacheFolderIterator()
212
    {
213
        return new \RecursiveIteratorIterator(
214
            new \RecursiveDirectoryIterator($this->cachePath, \RecursiveDirectoryIterator::SKIP_DOTS),
215
            \RecursiveIteratorIterator::CHILD_FIRST
216
        );
217
    }
218
}
219