ExRatesTableFinder   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 214
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 28
lcom 1
cbo 3
dl 0
loc 214
rs 10
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 3
C getExRatesTable() 0 37 8
A setSoughtPubDate() 0 11 4
A getCachedXml() 0 12 2
A downloadXml() 0 20 4
A findFileInRatesDir() 0 18 2
A constructDirname() 0 9 2
A matchFilename() 0 11 3
1
<?php
2
3
namespace Ksdev\NBPCurrencyConverter;
4
5
class ExRatesTableFinder
6
{
7
    const NBP_XML_URL = 'http://www.nbp.pl/kursy/xml/';
8
    const MIN_PUB_DATE = '2002-01-02';
9
    const MAX_ONE_TIME_API_REQ = 7;
10
11
    /** @var \GuzzleHttp\Client */
12
    private $guzzle;
13
14
    /** @var ExRatesTableFactory */
15
    private $ratesTableFactory;
16
17
    /** @var string */
18
    private $cachePath;
19
20
    /** @var \DateTime */
21
    private $soughtPubDate;
22
23
    /**
24
     * @param \GuzzleHttp\Client $guzzle
25
     * @param ExRatesTableFactory $ratesTableFactory
26
     * @param string $cachePath Optional path to an existing folder where the cache files will be stored
27
     *
28
     * @throws \Exception
29
     */
30
    public function __construct(
31
        \GuzzleHttp\Client $guzzle,
32
        ExRatesTableFactory $ratesTableFactory,
33
        $cachePath = ''
34
    ) {
35
        $this->guzzle = $guzzle;
36
        $this->ratesTableFactory = $ratesTableFactory;
37
        if ($cachePath) {
38
            if (!is_dir($cachePath)) {
39
                throw new \Exception('Invalid cache path');
40
            }
41
            $this->cachePath = rtrim((string)$cachePath, '/') . '/';
42
        }
43
    }
44
45
    /**
46
     * Get the ExRatesTable instance
47
     *
48
     * @param \DateTime $pubDate Optional rates table publication date
49
     *
50
     * @return ExRatesTable
51
     *
52
     * @throws \Exception
53
     */
54
    public function getExRatesTable(\DateTime $pubDate = null)
55
    {
56
        $this->setSoughtPubDate($pubDate);
57
58
        $i = 0;
59
        do {
60
            // Limit the number of times the loop repeats
61
            if ($i === self::MAX_ONE_TIME_API_REQ) {
62
                throw new \Exception('Max requests to api limit has been reached');
63
            }
64
65
            // If user doesn't want a specific date, try to get the rates from the last working day
66
            if (!$pubDate) {
67
                $this->soughtPubDate = $this->soughtPubDate->sub(new \DateInterval('P1D'));
68
            }
69
70
            // Try to find the file in cache, otherwise download it
71
            if ($this->cachePath && ($cachedXml = $this->getCachedXml())) {
72
                $rawContent = $cachedXml;
73
            } else {
74
                $rawContent = $this->downloadXml();
75
            }
76
77
            // If a specific date is sought then break, otherwise continue
78
            if ($pubDate) {
79
                break;
80
            }
81
82
            $i++;
83
        } while (!$rawContent);
84
85
        if (!$rawContent) {
86
            throw new \Exception('Exchange rates file not found');
87
        }
88
89
        return $this->ratesTableFactory->getInstance($rawContent);
90
    }
91
92
    /**
93
     * Set the sought publication date necessary for finder operation
94
     *
95
     * @param \DateTime|null $pubDate
96
     *
97
     * @throws \Exception
98
     */
99
    private function setSoughtPubDate($pubDate)
100
    {
101
        if ($pubDate instanceof \DateTime) {
102
            if (!($pubDate >= new \DateTime(self::MIN_PUB_DATE) && $pubDate <= new \DateTime())) {
103
                throw new \Exception('Invalid publication date');
104
            }
105
        } else {
106
            $pubDate = new \DateTime();
107
        }
108
        $this->soughtPubDate = $pubDate;
109
    }
110
111
    /**
112
     * Get the raw xml content from a cache file
113
     *
114
     * @return string|int Content string or 0 if the file doesn't exist
115
     */
116
    private function getCachedXml()
117
    {
118
        $filesArray = scandir($this->cachePath);
119
        $filename = $this->matchFilename($filesArray);
120
121
        if ($filename) {
122
            $rawContent = file_get_contents($this->cachePath . $filename);
123
            return $rawContent;
124
        }
125
126
        return 0;
127
    }
128
129
    /**
130
     * Get the raw xml content from the NBP api
131
     *
132
     * @return string|int Content string or 0 if the file doesn't exist
133
     *
134
     * @throws \Exception
135
     */
136
    private function downloadXml()
137
    {
138
        $filename = $this->findFileInRatesDir();
139
        if ($filename) {
140
            $response = $this->guzzle->get(self::NBP_XML_URL . $filename);
141
            if ($response->getStatusCode() === 200) {
142
                $rawContent = (string)$response->getBody();
143
                if ($this->cachePath) {
144
                    file_put_contents($this->cachePath . $filename, $rawContent);
145
                }
146
                return $rawContent;
147
            } else {
148
                throw new \Exception(
149
                    "Invalid response status code: {$response->getStatusCode()} {$response->getReasonPhrase()}"
150
                );
151
            }
152
        } else {
153
            return 0;
154
        }
155
    }
156
157
    /**
158
     * Find the file related to the publication date
159
     *
160
     * @return string|int Filename or 0 if the file was not found
161
     *
162
     * @throws \Exception
163
     */
164
    private function findFileInRatesDir()
165
    {
166
        $dirname = $this->constructDirname();
167
168
        $response = $this->guzzle->get(self::NBP_XML_URL . $dirname);
169
        if ($response->getStatusCode() === 200) {
170
            $rawContent = (string)$response->getBody();
171
        } else {
172
            throw new \Exception(
173
                "Invalid response status code: {$response->getStatusCode()} {$response->getReasonPhrase()}"
174
            );
175
        }
176
177
        $filesArray = explode("\r\n", $rawContent);
178
        $filename = $this->matchFilename($filesArray);
179
180
        return $filename;
181
    }
182
183
    /**
184
     * Construct the name of directory containing the files
185
     *
186
     * @return string
187
     */
188
    private function constructDirname()
189
    {
190
        if ($this->soughtPubDate->format('Y') !== (new \DateTime())->format('Y')) {
191
            $dirname = "dir{$this->soughtPubDate->format('Y')}.txt";
192
        } else {
193
            $dirname = 'dir.txt';
194
        }
195
        return $dirname;
196
    }
197
198
    /**
199
     * Searches files array for a match to the publication date
200
     *
201
     * @todo Optimize to avoid unnecessary regex
202
     *
203
     * @param array $filesArray
204
     *
205
     * @return string|int Filename or 0 if the file was not found
206
     */
207
    private function matchFilename(array $filesArray)
208
    {
209
        foreach ($filesArray as $filename) {
210
            if (preg_match('/(a\d{3}z' . $this->soughtPubDate->format('ymd') . ')/', $filename, $matches)) {
211
                $filename = "{$matches[1]}.xml";
212
                return $filename;
213
            }
214
        }
215
216
        return 0;
217
    }
218
}
219