1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace RunOpenCode\ExchangeRate\Repository; |
4
|
|
|
|
5
|
|
|
use RunOpenCode\ExchangeRate\Contract\RateInterface; |
6
|
|
|
use RunOpenCode\ExchangeRate\Contract\RepositoryInterface; |
7
|
|
|
use RunOpenCode\ExchangeRate\Exception\ExchangeRateException; |
8
|
|
|
use RunOpenCode\ExchangeRate\Utils\RateFilter; |
9
|
|
|
use RunOpenCode\ExchangeRateBundle\Model\Rate; |
10
|
|
|
|
11
|
|
|
class FileRepository implements RepositoryInterface |
12
|
|
|
{ |
13
|
|
|
const RATE_KEY_FORMAT = '%currency_code%_%date%_%rate_type%'; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* File where all rates are persisted. |
17
|
|
|
* |
18
|
|
|
* @var string |
19
|
|
|
*/ |
20
|
|
|
protected $pathToFile; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Collection of loaded rates. |
24
|
|
|
* |
25
|
|
|
* @var array |
26
|
|
|
*/ |
27
|
|
|
protected $rates; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Collection of latest rates (to speed up search process). |
31
|
|
|
* |
32
|
|
|
* @var array |
33
|
|
|
*/ |
34
|
|
|
protected $latest; |
35
|
|
|
|
36
|
|
|
public function __construct($pathToFile) |
37
|
|
|
{ |
38
|
|
|
$this->pathToFile = $pathToFile; |
39
|
|
|
|
40
|
|
|
if (!file_exists($this->pathToFile)) { |
41
|
|
|
touch($this->pathToFile); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
if (!is_readable($this->pathToFile)) { |
45
|
|
|
throw new \RuntimeException(sprintf('File on path "%s" for storing rates must be readable.', $this->pathToFile)); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
if (!is_writable($this->pathToFile)) { |
49
|
|
|
throw new \RuntimeException(sprintf('File on path "%s" for storing rates must be writeable.', $this->pathToFile)); |
50
|
|
|
} |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* {@inheritdoc} |
55
|
|
|
*/ |
56
|
|
|
public function save(array $rates) |
57
|
|
|
{ |
58
|
|
|
if (!$this->rates) { |
|
|
|
|
59
|
|
|
$this->load(); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* @var RateInterface $rate |
64
|
|
|
*/ |
65
|
|
|
foreach ($rates as $rate) { |
66
|
|
|
$this->rates[sprintf('%s_%s_%s', $rate->getCurrencyCode(), $rate->getDate()->format('Y-m-d'), $rate->getRateType())] = $rate; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
usort($this->rates, function($rate1, $rate2) { |
70
|
|
|
/** |
71
|
|
|
* @var RateInterface $rate1 |
72
|
|
|
* @var RateInterface $rate2 |
73
|
|
|
*/ |
74
|
|
|
return ($rate1->getDate() > $rate2->getDate()) ? -1 : 1; |
75
|
|
|
}); |
76
|
|
|
|
77
|
|
|
$data = ''; |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @var RateInterface $rate |
81
|
|
|
*/ |
82
|
|
|
foreach ($this->rates as $rate) { |
83
|
|
|
$data .= json_encode(array( |
84
|
|
|
'sourceName' => $rate->getSourceName(), |
85
|
|
|
'value' => $rate->getValue(), |
86
|
|
|
'currencyCode' => $rate->getCurrencyCode(), |
87
|
|
|
'rateType' => $rate->getRateType(), |
88
|
|
|
'date' => $rate->getDate()->format('Y-m-d H:i:s'), |
89
|
|
|
'baseCurrencyCode' => $rate->getBaseCurrencyCode() |
90
|
|
|
)) . "\n"; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
file_put_contents($this->pathToFile, $data, LOCK_EX); |
94
|
|
|
|
95
|
|
|
$this->load(); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* {@inheritdoc} |
100
|
|
|
*/ |
101
|
|
|
public function delete(array $rates) |
102
|
|
|
{ |
103
|
|
|
if (!$this->rates) { |
|
|
|
|
104
|
|
|
$this->load(); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* @var RateInterface $rate |
109
|
|
|
*/ |
110
|
|
|
foreach ($rates as $rate) { |
111
|
|
|
unset($this->rates[$this->getRateKey($rate)]); |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
$this->save(array()); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* {@inheritdoc} |
119
|
|
|
*/ |
120
|
|
View Code Duplication |
public function has($currencyCode, \DateTime $date = null, $rateType = 'default') |
|
|
|
|
121
|
|
|
{ |
122
|
|
|
if (!$this->rates) { |
|
|
|
|
123
|
|
|
$this->load(); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
if (is_null($date)) { |
127
|
|
|
$date = new \DateTime('now'); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
return array_key_exists(str_replace(array( |
131
|
|
|
$currencyCode, |
132
|
|
|
$date->format('Y-m-d'), |
133
|
|
|
$rateType |
134
|
|
|
), array( |
135
|
|
|
'%currency_code%', |
136
|
|
|
'%date%', |
137
|
|
|
'%rate_type%' |
138
|
|
|
), self::RATE_KEY_FORMAT), $this->rates); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* {@inheritdoc} |
143
|
|
|
*/ |
144
|
|
View Code Duplication |
public function get($currencyCode, \DateTime $date = null, $rateType = 'default') |
|
|
|
|
145
|
|
|
{ |
146
|
|
|
if (!$this->rates) { |
|
|
|
|
147
|
|
|
$this->load(); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
if (is_null($date)) { |
151
|
|
|
$date = new \DateTime('now'); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
return $this->rates[str_replace(array( |
155
|
|
|
$currencyCode, |
156
|
|
|
$date->format('Y-m-d'), |
157
|
|
|
$rateType |
158
|
|
|
), array( |
159
|
|
|
'%currency_code%', |
160
|
|
|
'%date%', |
161
|
|
|
'%rate_type%' |
162
|
|
|
), self::RATE_KEY_FORMAT)]; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* {@inheritdoc} |
167
|
|
|
*/ |
168
|
|
|
public function latest($currencyCode, $rateType = 'default') |
169
|
|
|
{ |
170
|
|
|
if (!$this->rates) { |
|
|
|
|
171
|
|
|
$this->load(); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* @var RateInterface $rate |
176
|
|
|
*/ |
177
|
|
|
foreach ($this->rates as $rate) { |
178
|
|
|
|
179
|
|
|
if ($rate->getCurrencyCode() == $currencyCode && $rate->getRateType() == $rateType) { |
180
|
|
|
return $rate; |
181
|
|
|
} |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
throw new ExchangeRateException(sprintf('Could not fetch latest rate for rate currency code "%s" and rate type "%s".', $currencyCode, $rateType)); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* {@inheritdoc} |
189
|
|
|
*/ |
190
|
|
|
public function all(array $criteria = array()) |
191
|
|
|
{ |
192
|
|
|
if (!$this->rates) { |
|
|
|
|
193
|
|
|
$this->load(); |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
if (count($criteria) == 0) { |
197
|
|
|
return $this->rates; |
198
|
|
|
} else { |
199
|
|
|
$result = array(); |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* @var RateInterface $rate |
203
|
|
|
*/ |
204
|
|
|
foreach ($this->rates as $rate) { |
205
|
|
|
|
206
|
|
|
if (RateFilter::matches($rate, $criteria)) { |
207
|
|
|
$result[] = $rate; |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
return $result; |
212
|
|
|
} |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Load rates from file. |
217
|
|
|
*/ |
218
|
|
|
protected function load() |
219
|
|
|
{ |
220
|
|
|
$this->rates = array(); |
221
|
|
|
$this->latest = array(); |
222
|
|
|
|
223
|
|
|
$handle = fopen($this->pathToFile, 'r'); |
224
|
|
|
|
225
|
|
|
if ($handle) { |
226
|
|
|
|
227
|
|
|
while (($line = fgets($handle)) !== false) { |
228
|
|
|
$data = json_decode($line, true); |
229
|
|
|
|
230
|
|
|
$rate = new Rate( |
231
|
|
|
$data['sourceName'], |
232
|
|
|
$data['value'], |
233
|
|
|
$data['currencyCode'], |
234
|
|
|
$data['rateType'], |
235
|
|
|
\DateTime::createFromFormat('Y-m-d H:i:s', $data['date']), |
236
|
|
|
$data['baseCurrencyCode'] |
237
|
|
|
); |
238
|
|
|
|
239
|
|
|
$this->rates[$this->getRateKey($rate)] = $rate; |
240
|
|
|
|
241
|
|
|
$latestKey = sprintf('%s_%s', $rate->getCurrencyCode(), $rate->getRateType()); |
242
|
|
|
|
243
|
|
|
if (!isset($this->latest[$latestKey]) || ($this->latest[$latestKey]->getDate() < $rate->getDate())) { |
244
|
|
|
$this->latest[$latestKey] = $rate; |
245
|
|
|
} |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
fclose($handle); |
249
|
|
|
|
250
|
|
|
} else { |
251
|
|
|
throw new \RuntimeException(sprintf('Error opening file on path "%s".', $this->pathToFile)); |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
protected function getRateKey(RateInterface $rate) |
256
|
|
|
{ |
257
|
|
|
return str_replace(array( |
258
|
|
|
$rate->getCurrencyCode(), |
259
|
|
|
$rate->getDate()->format('Y-m-d'), |
260
|
|
|
$rate->getRateType() |
261
|
|
|
), array( |
262
|
|
|
'%currency_code%', |
263
|
|
|
'%date%', |
264
|
|
|
'%rate_type%' |
265
|
|
|
), self::RATE_KEY_FORMAT); |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.