9 июн. 2019 г.
Всем привет! Сегодня расскажу про получение курсов валют. Сама по себе задача не сложная и ничего необычного здесь нет. Самый простой способ получить актуальный курс валюты - использовать какой-нибудь сторонний сервис для этого. Например, Fixer.io. Обращаемся к нему по Api, получаем данные ничего необычного. Обычно в подобных сервисах есть бесплатный пакет, который позволяет использовать сервис в небольшом проекте. В данном случае это 1000 запросов в месяц.
Однако у такого подхода может быть несколько недостатков.
Можно попробовать получить курсы валют напрямую из официальных источников. Курсы валют публикуются на сайтах ECB, ЦБ РФ, The Bank of England и т.д. Соответственно, публикуется курс заданой валюты относительно других валют.
К примеру, на сайте ЦБ РФ публикуется курс рубля относительно Eur, Usd и т.д. На сайте ... - курс евро относительно доллара, норвежской кроны и т.д.
Данные публикуются в различных форматах, где-то это просто html таблица, где-то XML, где-то JSON, где-то CSV. Далее приведены ссылки на получение данных:
Для остальных валют мне не требовалось извлекать курсы. Их можно попробовать извлечь с соответствующих ресурсов. Либо, например, взять курс EUR относитлельно конкретной валюты и высчитать курс этой самой валюты относительно EUR.
Для реализации парсера имеет смысл использовать такую структуру: создаем Retriever и Mapper, для каждой валюты. Retriever непосредственно извлекает курсы валют с сайта. Mapper приводит к общему формату.
public function retrieve(): array { $url = 'https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/usd.xml'; $result = $this->curlHelper->sendRequest($url, Request::METHOD_GET); $xml = simplexml_load_string($result); $json = json_encode($xml); $array = json_decode($json, true); return $array; }
public function map(array $data): array { $results = []; $rows = $data['DataSet']['Series']['Obs']; foreach ($rows as $row) { $attributes = $row['@attributes']; $results[] = [ 'date' => $attributes['TIME_PERIOD'], 'rate' => $attributes['OBS_VALUE'], ]; } return $results; }
Также используется Enum со списком валют и Factory, которая возвращает для каждой валюты Retriever и Mapper.
public function createMapper(string $currency): MapperInterface { $className = 'App\\Currency\\'.$currency.'\\Mapper'; if (!class_exists($className)) { throw new \Exception('Wrong currency: '.$currency); } return new $className(); }
public function createRetriever(string $currency): RetrieverInterface { $curlHelper = new CurlHelper(); $className = 'App\\Currency\\'.$currency.'\\Retriever'; if (!class_exists($className)) { throw new \Exception('Wrong currency: '.$currency); } return new $className($curlHelper); }
Остается еще одна небольшая проблема. Как правило, публикуются курсы валют только для рабочих дней. Для выходных остаются пропуски. При этом логично, что если есть курс на пятницу, а потом сразу на понедельник, то в субботу и воскресенье курс будет пятничным. Соответственно, напишем код, который будет дополнять курсы валют для выходных и праздников.
public function addMissingDates(array $data, string $endDate): array { $currentDate = $data[0]['date']; $currentRate = $data[0]['rate']; $dataMap = $this->getDataMap($data); $results = []; for (;;) { $results[] = [ 'date' => $currentDate, 'rate' => $currentRate, ]; if ($currentDate == $endDate) { break; } $currentDate = (new \DateTime($currentDate))->modify('+1 day')->format('Y-m-d'); if (isset($dataMap[$currentDate])) { $currentRate = $dataMap[$currentDate]; } } return $results; }
В результате мы можем получать курсы валют самостоятельно, из официальных источников.
Напоследок подготовил небольшой пример, в котором реализованы основные идеи из статьи. Пример сделан с использованием Symfony 4. Код доступен на github. Для запуска нужно клонировать репозиторий установить зависимости и запустить одну команду:
php bin/console currencies:update-all-rates
Данные курсов валют сохраняются в директорию currencies_data
.
На этом пока все. Спасибо за внимание!