1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* PhPsst. |
4
|
|
|
* |
5
|
|
|
* @copyright Copyright (c) 2016 Felix Sandström |
6
|
|
|
* @license MIT |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace PhPsst\Storage; |
10
|
|
|
|
11
|
|
|
use PhPsst\Password; |
12
|
|
|
use PhPsst\PhPsstException; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
*/ |
16
|
|
|
class FileStorage implements StorageInterface |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* @var string |
20
|
|
|
*/ |
21
|
|
|
protected $dir; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var int |
25
|
|
|
*/ |
26
|
|
|
protected $gcProbability; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @const string |
30
|
|
|
*/ |
31
|
|
|
const FILE_SUFFIX = '.phpsst'; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* FileStorage constructor. |
35
|
|
|
* @param string $dir |
36
|
|
|
* @param int $gcProbability |
37
|
|
|
*/ |
38
|
9 |
|
public function __construct($dir, $gcProbability) |
39
|
|
|
{ |
40
|
9 |
|
$dir = rtrim($dir, '/') . '/'; |
41
|
9 |
|
if (empty($dir) || !is_dir($dir)) { |
42
|
1 |
|
throw new \RuntimeException('Invalid directory path'); |
43
|
|
|
} |
44
|
|
|
|
45
|
8 |
|
if ($gcProbability < 0) { |
46
|
|
|
throw new \LogicException('Invalid value for gcProbability'); |
47
|
|
|
} |
48
|
|
|
|
49
|
8 |
|
$this->dir = $dir; |
50
|
8 |
|
$this->gcProbability = $gcProbability; |
51
|
8 |
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @param Password $password |
55
|
|
|
*/ |
56
|
8 |
View Code Duplication |
public function insert(Password $password) |
|
|
|
|
57
|
|
|
{ |
58
|
8 |
|
if (file_exists($this->getFileName($password))) { |
59
|
1 |
|
throw new PhPsstException('The ID already exists', PhPsstException::ID_IS_ALREADY_TAKEN); |
60
|
|
|
} |
61
|
|
|
|
62
|
8 |
|
$this->writeFile($password); |
63
|
7 |
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @param Password $password |
67
|
|
|
*/ |
68
|
2 |
View Code Duplication |
public function update(Password $password) |
|
|
|
|
69
|
|
|
{ |
70
|
2 |
|
if (!file_exists($this->getFileName($password))) { |
71
|
1 |
|
throw new PhPsstException('No such ID exists', PhPsstException::NO_PASSWORD_WITH_ID_FOUND); |
72
|
|
|
} |
73
|
|
|
|
74
|
1 |
|
$this->writeFile($password); |
75
|
1 |
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @param $key |
79
|
|
|
* @return Password|null |
80
|
|
|
*/ |
81
|
3 |
|
public function get($key) |
82
|
|
|
{ |
83
|
3 |
|
$password = null; |
84
|
3 |
|
if (!file_exists($this->getFileNameFromKey($key))) { |
85
|
1 |
|
throw new PhPsstException('No such ID exists', PhPsstException::NO_PASSWORD_WITH_ID_FOUND); |
86
|
|
|
} |
87
|
|
|
|
88
|
3 |
|
if (($jsonData = json_decode(file_get_contents($this->getFileNameFromKey($key))))) { |
89
|
3 |
|
if (!empty($jsonData->id) |
90
|
3 |
|
&& !empty($jsonData->password) |
91
|
3 |
|
&& !empty($jsonData->ttl) |
92
|
3 |
|
&& !empty($jsonData->views) |
93
|
3 |
|
) { |
94
|
3 |
|
$password = new Password($jsonData->id, $jsonData->password, $jsonData->ttl, $jsonData->views); |
95
|
3 |
|
if ($jsonData->ttlTime < time()) { |
96
|
1 |
|
$this->delete($password); |
97
|
1 |
|
throw new PhPsstException('No such ID exists', PhPsstException::NO_PASSWORD_WITH_ID_FOUND); |
98
|
|
|
} |
99
|
3 |
|
} |
100
|
3 |
|
} |
101
|
|
|
|
102
|
3 |
|
return $password; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @param Password $password |
107
|
|
|
*/ |
108
|
3 |
|
public function delete(Password $password) |
109
|
|
|
{ |
110
|
3 |
|
unlink($this->getFileName($password)); |
111
|
3 |
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
*/ |
115
|
6 |
|
protected function garbageCollection() |
116
|
|
|
{ |
117
|
6 |
|
if (rand(1, $this->gcProbability) !== 1) { |
118
|
1 |
|
return; |
119
|
|
|
} |
120
|
|
|
|
121
|
5 |
|
$files = array_diff(scandir($this->dir), array('.', '..')); |
122
|
5 |
|
foreach ($files as $file) { |
123
|
5 |
|
if (($jsonData = json_decode(file_get_contents($this->dir . $file)))) { |
124
|
5 |
|
if ($jsonData->ttlTime < time()) { |
125
|
2 |
|
unlink($this->dir . $file); |
126
|
2 |
|
} |
127
|
5 |
|
} |
128
|
5 |
|
} |
129
|
5 |
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* @param Password $password |
133
|
|
|
*/ |
134
|
7 |
|
protected function writeFile(Password $password) |
135
|
|
|
{ |
136
|
7 |
|
$jsonData = json_encode([ |
137
|
7 |
|
'id' => $password->getId(), |
138
|
7 |
|
'password' => $password->getPassword(), |
139
|
7 |
|
'ttl' => $password->getTtl(), |
140
|
7 |
|
'ttlTime' => time() + $password->getTtl(), |
141
|
7 |
|
'views' => $password->getViews(), |
142
|
7 |
|
]); |
143
|
|
|
|
144
|
7 |
|
$fileName = $this->getFileName($password); |
145
|
7 |
|
if (!is_writable(dirname($fileName)) || !file_put_contents($fileName, $jsonData)) { |
146
|
1 |
|
throw new \RuntimeException('Can not write file'); |
147
|
|
|
} |
148
|
|
|
|
149
|
6 |
|
$this->garbageCollection(); |
150
|
6 |
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* @param Password $password |
154
|
|
|
* @return string |
155
|
|
|
*/ |
156
|
4 |
|
protected function getFileName(Password $password) |
157
|
|
|
{ |
158
|
4 |
|
return $this->getFileNameFromKey($password->getId()); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* @param string $key |
163
|
|
|
* @return string |
164
|
|
|
*/ |
165
|
4 |
|
protected function getFileNameFromKey($key) |
166
|
|
|
{ |
167
|
4 |
|
return $this->dir . $key . self::FILE_SUFFIX; |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
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.