Completed
Push — master ( 1fe99f...b40b69 )
by Nikola
02:38
created

FileRepository::all()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 24
rs 8.5126
cc 5
eloc 11
nc 8
nop 1
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\ExchangeRateBundle\Model\Rate;
9
10
class FileRepository implements RepositoryInterface
11
{
12
    const RATE_KEY_FORMAT = '%currency_code%_%date%_%rate_type%';
13
14
    /**
15
     * File where all rates are persisted.
16
     *
17
     * @var string
18
     */
19
    protected $pathToFile;
20
21
    /**
22
     * Collection of loaded rates.
23
     *
24
     * @var array
25
     */
26
    protected $rates;
27
28
    /**
29
     * Collection of latest rates (to speed up search process).
30
     *
31
     * @var array
32
     */
33
    protected $latest;
34
35
    public function __construct($pathToFile)
36
    {
37
        $this->pathToFile = $pathToFile;
38
39
        if (!file_exists($this->pathToFile)) {
40
            touch($this->pathToFile);
41
        }
42
43
        if (!is_readable($this->pathToFile)) {
44
            throw new \RuntimeException(sprintf('File on path "%s" for storing rates must be readable.', $this->pathToFile));
45
        }
46
47
        if (!is_writable($this->pathToFile)) {
48
            throw new \RuntimeException(sprintf('File on path "%s" for storing rates must be writeable.', $this->pathToFile));
49
        }
50
    }
51
52
    /**
53
     * {@inheritdoc}
54
     */
55
    public function save(array $rates)
56
    {
57
        if (!$this->rates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
58
            $this->load();
59
        }
60
61
        /**
62
         * @var RateInterface $rate
63
         */
64
        foreach ($rates as $rate) {
65
            $this->rates[sprintf('%s_%s_%s', $rate->getCurrencyCode(), $rate->getDate()->format('Y-m-d'), $rate->getRateType())] = $rate;
66
        }
67
68
        usort($this->rates, function($rate1, $rate2) {
69
            /**
70
             * @var RateInterface $rate1
71
             * @var RateInterface $rate2
72
             */
73
            return ($rate1->getDate() > $rate2->getDate()) ? -1 : 1;
74
        });
75
76
        $data = '';
77
78
        /**
79
         * @var RateInterface $rate
80
         */
81
        foreach ($this->rates as $rate) {
82
            $data .= json_encode(array(
83
                    'sourceName' => $rate->getSourceName(),
84
                    'value' => $rate->getValue(),
85
                    'currencyCode' => $rate->getCurrencyCode(),
86
                    'rateType' => $rate->getRateType(),
87
                    'date' => $rate->getDate()->format('Y-m-d H:i:s'),
88
                    'baseCurrencyCode' => $rate->getBaseCurrencyCode()
89
                )) . "\n";
90
        }
91
92
        file_put_contents($this->pathToFile, $data, LOCK_EX);
93
94
        $this->load();
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100
    public function delete(array $rates)
101
    {
102
        if (!$this->rates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
103
            $this->load();
104
        }
105
106
        /**
107
         * @var RateInterface $rate
108
         */
109
        foreach ($rates as $rate) {
110
            unset($this->rates[$this->getRateKey($rate)]);
111
        }
112
113
        $this->save(array());
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 View Code Duplication
    public function has($currencyCode, \DateTime $date = null, $rateType = 'default')
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in 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...
120
    {
121
        if (!$this->rates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
122
            $this->load();
123
        }
124
125
        if (is_null($date)) {
126
            $date = new \DateTime('now');
127
        }
128
129
        return array_key_exists(str_replace(array(
130
            $currencyCode,
131
            $date->format('Y-m-d'),
132
            $rateType
133
        ), array(
134
            '%currency_code%',
135
            '%date%',
136
            '%rate_type%'
137
        ), self::RATE_KEY_FORMAT), $this->rates);
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143 View Code Duplication
    public function get($currencyCode, \DateTime $date = null, $rateType = 'default')
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in 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...
144
    {
145
        if (!$this->rates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
146
            $this->load();
147
        }
148
149
        if (is_null($date)) {
150
            $date = new \DateTime('now');
151
        }
152
153
        return $this->rates[str_replace(array(
154
            $currencyCode,
155
            $date->format('Y-m-d'),
156
            $rateType
157
        ), array(
158
            '%currency_code%',
159
            '%date%',
160
            '%rate_type%'
161
        ), self::RATE_KEY_FORMAT)];
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167
    public function latest($currencyCode, $rateType = 'default')
168
    {
169
        if (!$this->rates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
170
            $this->load();
171
        }
172
173
        /**
174
         * @var RateInterface $rate
175
         */
176
        foreach ($this->rates as $rate) {
177
178
            if ($rate->getCurrencyCode() == $currencyCode && $rate->getRateType() == $rateType) {
179
                return $rate;
180
            }
181
        }
182
183
        throw new ExchangeRateException(sprintf('Could not fetch latest rate for rate currency code "%s" and rate type "%s".', $currencyCode, $rateType));
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189
    public function all(array $criteria = array())
190
    {
191
        if (!$this->rates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->rates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
192
            $this->load();
193
        }
194
195
        if (count($criteria) == 0) {
196
            return $this->rates;
197
        } else {
198
            $result = array();
199
200
            /**
201
             * @var RateInterface $rate
202
             */
203
            foreach ($this->rates as $rate) {
204
205
                if ($this->matches($rate, $criteria)) {
206
                    $result[] = $rate;
207
                }
208
            }
209
210
            return $result;
211
        }
212
    }
213
214
    /**
215
     * Check if rate matches filter criteria.
216
     *
217
     * @param RateInterface $rate
218
     * @param array $criteria
219
     * @return bool
220
     */
221
    protected function matches(RateInterface $rate, array $criteria) {
222
223
        if (isset($criteria['currencyCode']) && $criteria['currencyCode'] != $rate->getCurrencyCode()) {
224
            return false;
225
        }
226
227
        if (isset($criteria['currencyCodes']) && !in_array($rate->getCurrencyCode(), $criteria['currencyCodes'])) {
228
            return false;
229
        }
230
231
        if (isset($criteria['dateFrom']) && $criteria['dateFrom'] <= $rate->getDate()) {
232
            return false;
233
        }
234
235
        if (isset($criteria['dateTo']) && $criteria['dateTo'] >= $rate->getDate()) {
236
            return false;
237
        }
238
239
        if (isset($criteria['onDate']) && $criteria['onDate']->format('Y-m-d') != $rate->getDate()->format('Y-m-d')) {
240
            return false;
241
        }
242
243
        if (isset($criteria['rateType']) && $criteria['rateType'] != $rate->getRateType()) {
244
            return false;
245
        }
246
247
        if (isset($criteria['rateTypes']) && !in_array($rate->getRateType(), $criteria['rateTypes'])) {
248
            return false;
249
        }
250
251
        if (isset($criteria['source']) && $criteria['source'] != $rate->getSourceName()) {
252
            return false;
253
        }
254
255
        if (isset($criteria['sources']) && !in_array($rate->getSourceName(), $criteria['sources'])) {
256
            return false;
257
        }
258
259
        return true;
260
    }
261
262
    /**
263
     * Load rates from file.
264
     */
265
    protected function load()
266
    {
267
        $this->rates = array();
268
        $this->latest = array();
269
270
        $handle = fopen($this->pathToFile, 'r');
271
272
        if ($handle) {
273
274
            while (($line = fgets($handle)) !== false) {
275
                $data = json_decode($line, true);
276
277
                $rate = new Rate(
278
                    $data['sourceName'],
279
                    $data['value'],
280
                    $data['currencyCode'],
281
                    $data['rateType'],
282
                    \DateTime::createFromFormat('Y-m-d H:i:s', $data['date']),
283
                    $data['baseCurrencyCode']
284
                );
285
286
                $this->rates[$this->getRateKey($rate)] = $rate;
287
288
                $latestKey = sprintf('%s_%s', $rate->getCurrencyCode(), $rate->getRateType());
289
290
                if (!isset($this->latest[$latestKey]) || ($this->latest[$latestKey]->getDate() < $rate->getDate())) {
291
                    $this->latest[$latestKey] = $rate;
292
                }
293
            }
294
295
            fclose($handle);
296
297
        } else {
298
            throw new \RuntimeException(sprintf('Error opening file on path "%s".', $this->pathToFile));
299
        }
300
    }
301
302
    protected function getRateKey(RateInterface $rate)
303
    {
304
        return str_replace(array(
305
            $rate->getCurrencyCode(),
306
            $rate->getDate()->format('Y-m-d'),
307
            $rate->getRateType()
308
        ), array(
309
            '%currency_code%',
310
            '%date%',
311
            '%rate_type%'
312
        ), self::RATE_KEY_FORMAT);
313
    }
314
}
315