Completed
Push — master ( 17fbce...9c0ecb )
by Nikola
02:12
created

FileRepository::save()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 41
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 41
ccs 0
cts 25
cp 0
rs 8.439
cc 5
eloc 19
nc 8
nop 1
crap 30
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) {
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...
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) {
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...
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')
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...
121
    {
122
        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...
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')
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...
145
    {
146
        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...
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) {
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...
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) {
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...
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