Completed
Push — master ( 6a43ea...7edb4c )
by Nikola
45:00
created

FileRepository::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 4
Bugs 2 Features 0
Metric Value
c 4
b 2
f 0
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 9.4286
cc 1
eloc 4
nc 1
nop 1
crap 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\ExchangeRate\Utils\RateFilterUtil;
9
use RunOpenCode\ExchangeRate\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 2
    public function __construct($pathToFile)
37
    {
38 2
        $this->pathToFile = $pathToFile;
39 2
        $this->initStorage();
40 2
        $this->load();
41 2
    }
42
43
    /**
44
     * {@inheritdoc}
45
     */
46 2
    public function save(array $rates)
47
    {
48
        /**
49
         * @var RateInterface $rate
50
         */
51 2
        foreach ($rates as $rate) {
52 2
            $this->rates[$this->getRateKey($rate)] = $rate;
53 1
        }
54
55 2
        usort($this->rates, function(RateInterface $rate1, RateInterface $rate2) {
56
            return ($rate1->getDate() > $rate2->getDate()) ? -1 : 1;
57 2
        });
58
59 2
        $data = '';
60
61
        /**
62
         * @var RateInterface $rate
63
         */
64 2
        foreach ($this->rates as $rate) {
65 2
            $data .= json_encode(array(
66 2
                    'sourceName' => $rate->getSourceName(),
67 2
                    'value' => $rate->getValue(),
68 2
                    'currencyCode' => $rate->getCurrencyCode(),
69 2
                    'rateType' => $rate->getRateType(),
70 2
                    'date' => $rate->getDate()->format('Y-m-d H:i:s'),
71 2
                    'baseCurrencyCode' => $rate->getBaseCurrencyCode(),
72 2
                    'createdAt' => $rate->getCreatedAt()->format('Y-m-d H:i:s'),
73 2
                    'modifiedAt' => $rate->getModifiedAt()->format('Y-m-d H:i:s')
74 2
                )) . "\n";
75 1
        }
76
77 2
        file_put_contents($this->pathToFile, $data, LOCK_EX);
78
79 2
        $this->load();
80 2
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function delete(array $rates)
86
    {
87
        /**
88
         * @var RateInterface $rate
89
         */
90
        foreach ($rates as $rate) {
91
            unset($this->rates[$this->getRateKey($rate)]);
92
        }
93
94
        $this->save(array());
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100 2
    public function has($currencyCode, \DateTime $date = null, $rateType = 'default')
101
    {
102 2
        if ($date === null) {
103 2
            $date = new \DateTime('now');
104 1
        }
105
106 2
        return array_key_exists(
107 1
            str_replace(
108 2
                array('%currency_code%', '%date%', '%rate_type%'),
109 2
                array($currencyCode, $date->format('Y-m-d'), $rateType),
110 1
                self::RATE_KEY_FORMAT
111 1
            ),
112 2
            $this->rates
113 1
        );
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function get($currencyCode, \DateTime $date = null, $rateType = 'default')
120
    {
121
        if ($date === null) {
122
            $date = new \DateTime('now');
123
        }
124
125
        if ($this->has($currencyCode, $date, $rateType)) {
126
            return $this->rates[
127
                str_replace(
128
                    array('%currency_code%', '%date%', '%rate_type%'),
129
                    array($currencyCode, $date->format('Y-m-d'), $rateType),
130
                    self::RATE_KEY_FORMAT
131
                )
132
            ];
133
        }
134
135
        throw new ExchangeRateException(sprintf('Could not fetch rate for rate currency code "%s" and rate type "%s" on date "%s".', $currencyCode, $rateType, $date->format('Y-m-d')));
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function latest($currencyCode, $rateType = 'default')
142
    {
143
        /**
144
         * @var RateInterface $rate
145
         */
146
        foreach ($this->rates as $rate) {
147
148
            if ($rate->getCurrencyCode() === $currencyCode && $rate->getRateType() === $rateType) {
149
                return $rate;
150
            }
151
        }
152
153
        throw new ExchangeRateException(sprintf('Could not fetch latest rate for rate currency code "%s" and rate type "%s".', $currencyCode, $rateType));
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function all(array $criteria = array())
160
    {
161
        if (count($criteria) == 0) {
162
            return $this->rates;
163
        } else {
164
            $result = array();
165
166
            /**
167
             * @var RateInterface $rate
168
             */
169
            foreach ($this->rates as $rate) {
170
171
                if (RateFilterUtil::matches($rate, $criteria)) {
172
                    $result[] = $rate;
173
                }
174
            }
175
176
            return $result;
177
        }
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183
    public function count()
184
    {
185
        return count($this->rates);
186
    }
187
188
    /**
189
     * Load rates from file.
190
     *
191
     * @return RateInterface[]
192
     */
193 2
    protected function load()
194
    {
195 2
        $this->rates = array();
196 2
        $this->latest = array();
197
198 2
        $handle = fopen($this->pathToFile, 'r');
199
200 2
        if ($handle) {
201
202 2
            while (($line = fgets($handle)) !== false) {
203 2
                $data = json_decode($line, true);
204
205 2
                $rate = new Rate(
206 2
                    $data['sourceName'],
207 2
                    $data['value'],
208 2
                    $data['currencyCode'],
209 2
                    $data['rateType'],
210 2
                    \DateTime::createFromFormat('Y-m-d H:i:s', $data['date']),
211 2
                    $data['baseCurrencyCode'],
212 2
                    \DateTime::createFromFormat('Y-m-d H:i:s', $data['createdAt']),
213 2
                    \DateTime::createFromFormat('Y-m-d H:i:s', $data['modifiedAt'])
214 1
                );
215
216 2
                $this->rates[$this->getRateKey($rate)] = $rate;
217
218 2
                $latestKey = sprintf('%s_%s', $rate->getCurrencyCode(), $rate->getRateType());
219
220 2
                if (!isset($this->latest[$latestKey]) || ($this->latest[$latestKey]->getDate() < $rate->getDate())) {
221 2
                    $this->latest[$latestKey] = $rate;
222 1
                }
223 1
            }
224
225 2
            fclose($handle);
226
227 1
        } else {
228
            throw new \RuntimeException(sprintf('Error opening file on path "%s".', $this->pathToFile));
229
        }
230
231 2
        return $this->rates;
232
    }
233
234 2
    protected function getRateKey(RateInterface $rate)
235
    {
236 2
        return str_replace(
237 2
            array('%currency_code%', '%date%', '%rate_type%'),
238 2
            array($rate->getCurrencyCode(), $rate->getDate()->format('Y-m-d'), $rate->getRateType()),
239 1
            self::RATE_KEY_FORMAT
240 1
        );
241
    }
242
243 2
    protected function initStorage()
244
    {
245
        /** @noinspection MkdirRaceConditionInspection */
246 2
        if (!file_exists(dirname($this->pathToFile)) && !mkdir(dirname($this->pathToFile), 0777, true)) {
247
            throw new \RuntimeException(sprintf('Could not create storage file on path "%s".', $this->pathToFile));
248
        }
249
250 2
        if (!file_exists($this->pathToFile) && !(touch($this->pathToFile) && chmod($this->pathToFile, 0777))) {
251
            throw new \RuntimeException(sprintf('Could not create storage file on path "%s".', $this->pathToFile));
252
        }
253
254 2
        if (!is_readable($this->pathToFile)) {
255
            throw new \RuntimeException(sprintf('File on path "%s" for storing rates must be readable.', $this->pathToFile));
256
        }
257
258 2
        if (!is_writable($this->pathToFile)) {
259
            throw new \RuntimeException(sprintf('File on path "%s" for storing rates must be writeable.', $this->pathToFile));
260
        }
261 2
    }
262
}
263