From 3229c92ae797dc49afde4c263869fe0976c912d4 Mon Sep 17 00:00:00 2001
From: liquetsoft <49859130+liquetsoft@users.noreply.github.com>
Date: Tue, 12 Jul 2022 11:31:34 +0200
Subject: [PATCH] Setup (#27)
---
.github/workflows/php.yml | 50 ++
.gitignore | 12 +
.php-cs-fixer.dist.php | 35 +
LICENSE | 21 +
Makefile | 31 +
README.md | 185 ++++++
composer.json | 45 ++
docker/docker-compose.yml | 12 +
docker/php/Dockerfile | 31 +
phpunit.xml.dist | 13 +
psalm.xml | 30 +
src/CbrfDaily.php | 619 ++++++++++++++++++
src/CbrfException.php | 12 +
src/CbrfSoapService.php | 129 ++++
src/DataHelper.php | 238 +++++++
src/Entity/Currency.php | 19 +
src/Entity/CurrencyEnum.php | 73 +++
src/Entity/CurrencyRate.php | 66 ++
src/Entity/DepoRate.php | 34 +
src/Entity/InternationalReserve.php | 34 +
src/Entity/InternationalReserveWeek.php | 34 +
src/Entity/KeyRate.php | 34 +
src/Entity/Mkr.php | 82 +++
src/Entity/OstatDepoRate.php | 42 ++
src/Entity/OstatRate.php | 46 ++
src/Entity/PreciousMetalRate.php | 47 ++
src/Entity/ReutersCurrencyRate.php | 73 +++
src/Entity/RuoniaBid.php | 50 ++
src/Entity/RuoniaIndex.php | 58 ++
src/Entity/Saldo.php | 34 +
src/Entity/SwapRate.php | 66 ++
tests/BaseTestCase.php | 169 +++++
tests/CbrfDailyTest.php | 822 ++++++++++++++++++++++++
tests/CbrfSoapServiceTest.php | 94 +++
tests/DataHelperTest.php | 588 +++++++++++++++++
35 files changed, 3928 insertions(+)
create mode 100644 .github/workflows/php.yml
create mode 100644 .gitignore
create mode 100644 .php-cs-fixer.dist.php
create mode 100644 LICENSE
create mode 100644 Makefile
create mode 100644 composer.json
create mode 100644 docker/docker-compose.yml
create mode 100644 docker/php/Dockerfile
create mode 100644 phpunit.xml.dist
create mode 100644 psalm.xml
create mode 100644 src/CbrfDaily.php
create mode 100644 src/CbrfException.php
create mode 100644 src/CbrfSoapService.php
create mode 100644 src/DataHelper.php
create mode 100644 src/Entity/Currency.php
create mode 100644 src/Entity/CurrencyEnum.php
create mode 100644 src/Entity/CurrencyRate.php
create mode 100644 src/Entity/DepoRate.php
create mode 100644 src/Entity/InternationalReserve.php
create mode 100644 src/Entity/InternationalReserveWeek.php
create mode 100644 src/Entity/KeyRate.php
create mode 100644 src/Entity/Mkr.php
create mode 100644 src/Entity/OstatDepoRate.php
create mode 100644 src/Entity/OstatRate.php
create mode 100644 src/Entity/PreciousMetalRate.php
create mode 100644 src/Entity/ReutersCurrencyRate.php
create mode 100644 src/Entity/RuoniaBid.php
create mode 100644 src/Entity/RuoniaIndex.php
create mode 100644 src/Entity/Saldo.php
create mode 100644 src/Entity/SwapRate.php
create mode 100644 tests/BaseTestCase.php
create mode 100644 tests/CbrfDailyTest.php
create mode 100644 tests/CbrfSoapServiceTest.php
create mode 100644 tests/DataHelperTest.php
diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
new file mode 100644
index 0000000..a333e8a
--- /dev/null
+++ b/.github/workflows/php.yml
@@ -0,0 +1,50 @@
+name: cbrf_service
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+jobs:
+ build:
+
+ name: CBRF service (PHP ${{ matrix.php-versions }})
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: true
+ matrix:
+ php-versions: ['7.4', '8.0', '8.1']
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-versions }}
+
+ - name: Check PHP Version
+ run: php -v
+
+ - name: Validate composer.json and composer.lock
+ run: composer validate
+
+ - name: Cache Composer packages
+ id: composer-cache
+ uses: actions/cache@v2
+ with:
+ path: vendor
+ key: ${{ runner.os }}-php-${{ matrix.php-versions }}-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-php-
+
+ - name: Install dependencies
+ if: steps.composer-cache.outputs.cache-hit != 'true'
+ run: composer install --prefer-dist --no-progress --no-suggest
+
+ - name: Run test suite
+ run: composer run-script test
+
+ - name: Run linters
+ run: composer run-script linter
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d4de4bd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+.idea
+vendor
+composer.lock
+.php_cs.cache
+.phpunit.result.cache
+.php-cs-fixer.cache
+
+tests/coverage
+docker/.env
+test.php
+
+.~lock.*
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..baf45b0
--- /dev/null
+++ b/.php-cs-fixer.dist.php
@@ -0,0 +1,35 @@
+in(__DIR__);
+
+$rules = [
+ '@Symfony' => true,
+ 'new_with_braces' => true,
+ 'concat_space' => ['spacing' => 'one'],
+ 'array_syntax' => ['syntax' => 'short'],
+ 'yoda_style' => false,
+ 'phpdoc_no_empty_return' => false,
+ 'no_superfluous_phpdoc_tags' => false,
+ 'single_line_throw' => false,
+ 'array_indentation' => true,
+ 'declare_strict_types' => true,
+ 'void_return' => true,
+ 'non_printable_character' => true,
+ 'modernize_types_casting' => true,
+ 'ordered_interfaces' => ['order' => 'alpha', 'direction' => 'ascend'],
+ 'date_time_immutable' => true,
+ 'native_constant_invocation' => true,
+ 'combine_nested_dirname' => true,
+ 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced', 'strict' => true],
+ 'php_unit_construct' => true,
+ 'php_unit_dedicate_assert' => true,
+ 'php_unit_expectation' => true,
+ 'php_unit_internal_class' => true,
+ 'php_unit_mock_short_will_return' => true,
+ 'php_unit_strict' => true,
+ 'strict_comparison' => true,
+];
+
+return (new Config())->setRules($rules)->setFinder($finder);
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..dabc729
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 liquetsoft
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..528bb04
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,31 @@
+#!/usr/bin/make
+
+user_id := $(shell id -u)
+docker_compose_bin := $(shell command -v docker-compose 2> /dev/null) --file "docker/docker-compose.yml"
+php_container_bin := $(docker_compose_bin) run --rm -u "$(user_id)" "php"
+php_composer_script := $(php_container_bin) composer run-script
+
+.DEFAULT_GOAL := build
+
+# --- [ Development tasks ] -------------------------------------------------------------------------------------------
+
+build: ## Build container and install composer libs
+ $(docker_compose_bin) build --force-rm
+
+install: ## Install all data
+ $(php_container_bin) composer update
+
+shell: ## Runs shell in container
+ $(php_container_bin) bash
+
+fixer: ## Run fixer to fix code style
+ $(php_composer_script) fixer
+
+linter: ## Run linter to check project
+ $(php_composer_script) linter
+
+test: ## Run tests
+ $(php_composer_script) test
+
+coverage: ## Run tests with coverage
+ $(php_composer_script) coverage
diff --git a/README.md b/README.md
index e69de29..bdd5b84 100644
--- a/README.md
+++ b/README.md
@@ -0,0 +1,185 @@
+Php курсы валют
+===============
+
+[![Latest Stable Version](https://poser.pugx.org/liquetsoft/cbrfservice/v/stable.png)](https://packagist.org/packages/liquetsoft/cbrfservice)
+[![Total Downloads](https://poser.pugx.org/liquetsoft/cbrfservice/downloads.png)](https://packagist.org/packages/liquetsoft/cbrfservice)
+[![License](https://poser.pugx.org/liquetsoft/cbrfservice/license.svg)](https://packagist.org/packages/liquetsoft/cbrfservice)
+[![Build Status](https://github.com/liquetsoft/cbrfservice/workflows/cbrf_service/badge.svg)](https://github.com/liquetsoft/cbrfservice/actions?query=workflow%3A%22cbrf_service%22)
+
+Php обертка для [сервиса Центробанка РФ](https://www.cbr.ru/development/DWS/).
+
+
+
+Установка
+---------
+
+Добавьте библиотеку в проект с помощью [Composer](https://getcomposer.org/doc/00-intro.md):
+
+```bash
+composer req liquetsoft/cbrfservice
+```
+
+
+
+Использование
+-------------
+
+```php
+//инициируем новый объект сервиса
+$cbrf = new \Liquetsoft\CbrfService\CbrfDaily();
+```
+
+```php
+//получаем курсы всех валют
+$rates = $cbrf->getCursOnDate(new \DateTimeImmutable());
+
+//получаем курс валюты по ее буквенному коду
+$rateEur = $cbrf->getCursOnDateByCharCode(new \DateTimeImmutable(), 'EUR');
+
+//получаем курс валюты по ее цифровому коду
+$rate978 = $cbrf->getCursOnDateByNumericCode(new \DateTimeImmutable(), 978);
+```
+
+```php
+//получаем словарь всех доступных валют
+$currencies = $cbrf->enumValutes();
+
+//получаем описание валюты из словаря по буквенному коду
+$enumEur = $cbrf->enumValuteByCharCode('EUR');
+
+//получаем описание валюты из словаря по цифровому коду
+$enum978 = $cbrf->enumValuteByNumericCode(978);
+
+//получаем динамику курса для указанной валюты за последний месяц
+$dynamic = $cbrf->getCursDynamic(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable(),
+ $enumEur
+);
+```
+
+```php
+//получаем динамику ключевой ставки за последний месяц
+$keyRate = $cbrf->keyRate(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+```php
+//получаем динамику цен на драгоценные металлы за последний месяц
+$metalsPrices = $cbrf->dragMetDynamic(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+```php
+//получаем валютный своп за последний месяц
+$swap = $cbrf->swapDynamic(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+```php
+//получаем динамику ставок привлечения средств по депозитным операциям за последний месяц
+$depo = $cbrf->depoDynamic(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+```php
+//получаем динамику сведений об остатках средств на корреспондентских счетах кредитных организаций
+$leftovers = $cbrf->ostatDynamic(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+```php
+//получаем депозиты банков в Банке России
+$leftovers = $cbrf->ostatDepo(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+```php
+//получаем международные резервы Российской Федерации, ежемесячные значения
+$mrrf = $cbrf->mrrf(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+```php
+//получаем международные резервы Российской Федерации, еженедельные значения
+$mrrf = $cbrf->mrrf7d(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+```php
+//получаем сальдо операций ЦБ РФ по предоставлению/абсорбированию ликвидности
+$saldo = $cbrf->saldo(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+```php
+//получаем индекс и срочную версию RUONIA
+$saldo = $cbrf->ruoniaSV(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+```php
+//получаем ставку RUONIA
+$saldo = $cbrf->ruonia(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+```php
+//получаем ставки межбанковского кредитного рынка
+$mkr = $cbrf->mkr(
+ new \DateTimeImmutable('-1 month'),
+ new \DateTimeImmutable()
+);
+```
+
+В случае, если необходимо передать сконфигурированный заранее транспорт, например для использования proxy:
+
+```php
+//инициируем новый объект SoapClient
+$client = new SoapClient(
+ \Liquetsoft\CbrfService\CbrfSoapService::DEFAULT_WSDL,
+ [
+ 'proxy_host' => 'localhost',
+ 'proxy_port' => 8080
+ ]
+);
+
+//инициируем новый объект сервиса
+$cbrf = new \Liquetsoft\CbrfService\CbrfDaily($client);
+```
+
+
+
+Обработка ошибок
+----------------
+
+Все ошибки, которые будут перехвачены при запросах, будут выброшены как исключение `\Liquetsoft\CbrfService\CbrfException`. Если `\SoapClient` будет сконфигурирован с отключенными исключениями, то обработка ошибок остается на стороне клиентского скрипта.
+
+
+
+Методы
+------
+
+Описание методов вы можете найти на [сайте банка России](https://www.cbr.ru/development/DWS/).
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..ea14a28
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,45 @@
+{
+ "name": "liquetsoft/cbrfservice",
+ "description": "Component for bank of Russia daily web service",
+ "type": "library",
+ "keywords": ["php", "banks", "currency rate", "soap", "service"],
+ "license": "MIT",
+ "require": {
+ "php": ">=7.4",
+ "ext-soap": "*",
+ "ext-SimpleXML": "*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.0",
+ "friendsofphp/php-cs-fixer": "^2.11|^3.0",
+ "sebastian/phpcpd": "^6.0",
+ "vimeo/psalm": "^4.0",
+ "psalm/plugin-phpunit": "^0.15.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "Liquetsoft\\CbrfService\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Liquetsoft\\CbrfService\\Tests\\": "tests/"
+ }
+ },
+ "scripts": {
+ "test": "vendor/bin/phpunit --configuration phpunit.xml.dist",
+ "coverage": "vendor/bin/phpunit --configuration phpunit.xml.dist --coverage-html=tests/coverage",
+ "fixer": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -vv --allow-risky=yes",
+ "linter": [
+ "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php -vv --allow-risky=yes --dry-run --stop-on-violation",
+ "vendor/bin/phpcpd ./ --exclude vendor --exclude tests",
+ "vendor/bin/psalm --show-info=true --php-version=$(php -r \"echo phpversion();\")"
+ ]
+ },
+ "repositories": [
+ {
+ "type": "git",
+ "url": "https://github.com/liquetsoft/cbrfservice"
+ }
+ ]
+}
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 0000000..22d8d59
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,12 @@
+version: '3.7'
+
+
+# container to run tests and other developer activities
+services:
+ php:
+ container_name: cbrf-service-php
+ image: cbrf-service-php
+ build:
+ context: php
+ volumes:
+ - ./../:/var/app:cached
diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile
new file mode 100644
index 0000000..7cbef9c
--- /dev/null
+++ b/docker/php/Dockerfile
@@ -0,0 +1,31 @@
+FROM php:8.1-cli-alpine
+
+
+RUN set -xe && apk update && apk add --no-cache \
+ libzip \
+ bash \
+ curl \
+ libmcrypt-dev \
+ libxml2-dev \
+ libzip-dev \
+ pcre-dev \
+ git \
+ autoconf \
+ g++ \
+ make
+
+
+RUN docker-php-ext-install zip soap \
+ && docker-php-source extract \
+ && pecl install xdebug \
+ && docker-php-ext-enable xdebug \
+ && docker-php-source delete \
+ && echo 'date.timezone=Europe/London' >> /usr/local/etc/php/conf.d/php-date.ini \
+ && echo 'xdebug.mode=coverage' >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
+
+
+RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --version=2.3.7 \
+ && mkdir -p /.composer && chmod -Rf 777 /.composer
+
+
+WORKDIR /var/app
\ No newline at end of file
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 0000000..aa1f2c8
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,13 @@
+
+
+
+
+ ./src
+
+
+
+
+ ./tests
+
+
+
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..9566bf6
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CbrfDaily.php b/src/CbrfDaily.php
new file mode 100644
index 0000000..3471cc0
--- /dev/null
+++ b/src/CbrfDaily.php
@@ -0,0 +1,619 @@
+soapClient = new CbrfSoapService($client);
+ }
+
+ /**
+ * Returns list of rates for all currencies for set date.
+ *
+ * @param DateTimeInterface $onDate
+ *
+ * @return CurrencyRate[]
+ *
+ * @throws CbrfException
+ */
+ public function getCursOnDate(DateTimeInterface $date): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'GetCursOnDate',
+ [
+ 'On_date' => $date->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $immutableDate = DataHelper::createImmutableDateTime($date);
+ $list = DataHelper::array('ValuteData.ValuteCursOnDate', $soapResult);
+ $callback = fn (array $item): CurrencyRate => new CurrencyRate($item, $immutableDate);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns rate for currency with set char code.
+ *
+ * @param DateTimeInterface $onDate
+ * @param string $charCode
+ *
+ * @return CurrencyRate|null
+ *
+ * @throws CbrfException
+ */
+ public function getCursOnDateByCharCode(DateTimeInterface $date, string $charCode): ?CurrencyRate
+ {
+ $list = $this->getCursOnDate($date);
+
+ $return = null;
+ foreach ($list as $item) {
+ if (strcasecmp($charCode, $item->getCharCode()) === 0) {
+ $return = $item;
+ break;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Returns rate for currency with set numeric code.
+ *
+ * @param DateTimeInterface $onDate
+ * @param int $numericCode
+ *
+ * @return CurrencyRate|null
+ *
+ * @throws CbrfException
+ */
+ public function getCursOnDateByNumericCode(DateTimeInterface $date, int $numericCode): ?CurrencyRate
+ {
+ $list = $this->getCursOnDate($date);
+
+ $return = null;
+ foreach ($list as $item) {
+ if ($item->getNumericCode() === $numericCode) {
+ $return = $item;
+ break;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * List of all currencies that allowed on cbrf service.
+ *
+ * @param bool $seld
+ *
+ * @return CurrencyEnum[]
+ *
+ * @throws CbrfException
+ */
+ public function enumValutes(bool $seld = false): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'EnumValutes',
+ [
+ 'Seld' => $seld,
+ ]
+ );
+
+ $list = DataHelper::array('ValuteData.EnumValutes', $soapResult);
+ $callback = fn (array $item): CurrencyEnum => new CurrencyEnum($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns enum for currency with set char code.
+ *
+ * @param string $charCode
+ * @param bool $seld
+ *
+ * @return CurrencyEnum|null
+ *
+ * @throws CbrfException
+ */
+ public function enumValuteByCharCode(string $charCode, bool $seld = false): ?CurrencyEnum
+ {
+ $list = $this->enumValutes($seld);
+
+ $return = null;
+ foreach ($list as $item) {
+ if (strcasecmp($charCode, $item->getCharCode()) === 0) {
+ $return = $item;
+ break;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Returns enum for currency with set numeric code.
+ *
+ * @param int $numericCode
+ * @param bool $seld
+ *
+ * @return CurrencyEnum|null
+ *
+ * @throws CbrfException
+ */
+ public function enumValuteByNumericCode(int $numericCode, bool $seld = false): ?CurrencyEnum
+ {
+ $list = $this->enumValutes($seld);
+
+ $return = null;
+ foreach ($list as $item) {
+ if ($item->getNumericCode() === $numericCode) {
+ $return = $item;
+ break;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Latest per day date and time of publication.
+ *
+ * @param string $format
+ *
+ * @return DateTimeInterface
+ *
+ * @throws CbrfException
+ */
+ public function getLatestDateTime(): DateTimeInterface
+ {
+ $soapResult = $this->soapClient->query('GetLatestDateTime');
+
+ return DataHelper::dateTime('GetLatestDateTimeResult', $soapResult);
+ }
+
+ /**
+ * Latest per day date and time of seld.
+ *
+ * @param string $format
+ *
+ * @return DateTimeInterface
+ *
+ * @throws CbrfException
+ */
+ public function getLatestDateTimeSeld(): DateTimeInterface
+ {
+ $soapResult = $this->soapClient->query('GetLatestDateTimeSeld');
+
+ return DataHelper::dateTime('GetLatestDateTimeSeldResult', $soapResult);
+ }
+
+ /**
+ * Latest per month date and time of publication.
+ *
+ * @param string $format
+ *
+ * @return DateTimeInterface
+ *
+ * @throws CbrfException
+ */
+ public function getLatestDate(): DateTimeInterface
+ {
+ $soapResult = $this->soapClient->query('GetLatestDate');
+
+ return DataHelper::dateTime('GetLatestDateResult', $soapResult);
+ }
+
+ /**
+ * Latest per month date and time of seld.
+ *
+ * @param string $format
+ *
+ * @return DateTimeInterface
+ *
+ * @throws CbrfException
+ */
+ public function getLatestDateSeld(): DateTimeInterface
+ {
+ $soapResult = $this->soapClient->query('GetLatestDateSeld');
+
+ return DataHelper::dateTime('GetLatestDateSeldResult', $soapResult);
+ }
+
+ /**
+ * Returns rate dynamic for set currency within set dates.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ * @param CurrencyEnum $currency
+ *
+ * @return CurrencyRate[]
+ */
+ public function getCursDynamic(DateTimeInterface $from, DateTimeInterface $to, CurrencyEnum $currency): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'GetCursDynamic',
+ [
+ 'FromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ValutaCode' => $currency->getInternalCode(),
+ ]
+ );
+
+ $result = [];
+ $list = DataHelper::array('ValuteData.ValuteCursDynamic', $soapResult);
+ foreach ($list as $item) {
+ $date = DataHelper::dateTime('CursDate', $item);
+ $item['Vname'] = $currency->getName();
+ $item['VchCode'] = $currency->getCharCode();
+ $item['Vcode'] = $currency->getNumericCode();
+ $result[] = new CurrencyRate($item, $date);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Returns key rate dynamic within set dates.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return KeyRate[]
+ */
+ public function keyRate(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'KeyRate',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('KeyRate.KR', $soapResult);
+ $callback = fn (array $item): KeyRate => new KeyRate($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns list of presious metals prices within set dates.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return PreciousMetalRate[]
+ */
+ public function dragMetDynamic(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'DragMetDynamic',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('DragMetall.DrgMet', $soapResult);
+ $callback = fn (array $item): PreciousMetalRate => new PreciousMetalRate($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns list of swap rates within set dates.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return SwapRate[]
+ */
+ public function swapDynamic(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'SwapDynamic',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('SwapDynamic.Swap', $soapResult);
+ $callback = fn (array $item): SwapRate => new SwapRate($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns list depo dynamic items within set dates.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return DepoRate[]
+ */
+ public function depoDynamic(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'DepoDynamic',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('DepoDynamic.Depo', $soapResult);
+ $callback = fn (array $item): DepoRate => new DepoRate($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns the dynamic of balances of funds items within set dates.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return OstatRate[]
+ */
+ public function ostatDynamic(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'OstatDynamic',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('OstatDynamic.Ostat', $soapResult);
+ $callback = fn (array $item): OstatRate => new OstatRate($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns the banks deposites at bank of Russia.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return OstatDepoRate[]
+ */
+ public function ostatDepo(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'OstatDepo',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('OD.odr', $soapResult);
+ $callback = fn (array $item): OstatDepoRate => new OstatDepoRate($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns international valute reseves of Russia for month.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return InternationalReserve[]
+ */
+ public function mrrf(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'mrrf',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('mmrf.mr', $soapResult);
+ $callback = fn (array $item): InternationalReserve => new InternationalReserve($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns international valute reseves of Russia for week.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return InternationalReserveWeek[]
+ */
+ public function mrrf7d(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'mrrf7D',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('mmrf7d.mr', $soapResult);
+ $callback = fn (array $item): InternationalReserveWeek => new InternationalReserveWeek($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns operations saldo.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return Saldo[]
+ */
+ public function saldo(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'Saldo',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('Saldo.So', $soapResult);
+ $callback = fn (array $item): Saldo => new Saldo($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns Ruonia index.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return RuoniaIndex[]
+ */
+ public function ruoniaSV(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'RuoniaSV',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('RuoniaSV.ra', $soapResult);
+ $callback = fn (array $item): RuoniaIndex => new RuoniaIndex($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns Ruonia bid.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return RuoniaBid[]
+ */
+ public function ruonia(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'Ruonia',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('Ruonia.ro', $soapResult);
+ $callback = fn (array $item): RuoniaBid => new RuoniaBid($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns inter banks credit market bids.
+ *
+ * @param DateTimeInterface $from
+ * @param DateTimeInterface $to
+ *
+ * @return Mkr[]
+ */
+ public function mkr(DateTimeInterface $from, DateTimeInterface $to): array
+ {
+ $soapResult = $this->soapClient->query(
+ 'MKR',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $list = DataHelper::array('mkr_base.MKR', $soapResult);
+ $callback = fn (array $item): Mkr => new Mkr($item);
+
+ return array_map($callback, $list);
+ }
+
+ /**
+ * Returns list of Reuters rates for all currencies for set date.
+ *
+ * @param DateTimeInterface $onDate
+ *
+ * @return ReutersCurrencyRate[]
+ *
+ * @throws CbrfException
+ */
+ public function getReutersCursOnDate(DateTimeInterface $date): array
+ {
+ $enumSoapResults = $this->soapClient->query(
+ 'EnumReutersValutes',
+ [
+ 'On_date' => $date->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ $enumCurrencies = [];
+ foreach ($enumSoapResults['ReutersValutesList']['EnumRValutes'] as $enumSoapResult) {
+ $enumCurrencies[$enumSoapResult['num_code']] = $enumSoapResult;
+ }
+
+ $soapValutesResults = $this->soapClient->query(
+ 'GetReutersCursOnDate',
+ [
+ 'On_date' => $date->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ]
+ );
+
+ foreach ($soapValutesResults['ReutersValutesData']['Currency'] as $soapValutesResult) {
+ $enumCurrencies[$soapValutesResult['num_code']] = array_merge($enumCurrencies[$soapValutesResult['num_code']], $soapValutesResult);
+ }
+
+ $results = [];
+ $immutableDate = DataHelper::createImmutableDateTime($date);
+
+ foreach ($enumCurrencies as $item) {
+ if (\is_array($item)) {
+ $results[] = new ReutersCurrencyRate($item, $immutableDate);
+ }
+ }
+
+ return $results;
+ }
+}
diff --git a/src/CbrfException.php b/src/CbrfException.php
new file mode 100644
index 0000000..c7c6105
--- /dev/null
+++ b/src/CbrfException.php
@@ -0,0 +1,12 @@
+client = $client;
+ } else {
+ $this->wsdl = $client;
+ }
+ }
+
+ /**
+ * Makes a soap call.
+ *
+ * @param string $method
+ * @param array $params
+ *
+ * @return array
+ *
+ * @throws CbrfException
+ */
+ public function query(string $method, array $params = []): array
+ {
+ try {
+ return $this->queryInternal($method, $params);
+ } catch (Throwable $e) {
+ $message = sprintf("Fail on '%s': '%s'.", $method, $e->getMessage());
+ throw new CbrfException($message, 0, $e);
+ }
+ }
+
+ /**
+ * Makes an internal soap call.
+ *
+ * @param string $method
+ * @param array $params
+ *
+ * @return array
+ */
+ private function queryInternal(string $method, array $params = []): array
+ {
+ // need to do this because every params list are nested to parameters object
+ if (!empty($params)) {
+ $params = [$params];
+ }
+
+ $soapCallResult = $this->getSoapClient()->__soapCall($method, $params);
+
+ $resName = $method . 'Result';
+ if (!empty($soapCallResult->$resName->any)) {
+ $xml = simplexml_load_string(
+ $soapCallResult->$resName->any,
+ 'SimpleXMLElement',
+ \LIBXML_NOCDATA
+ );
+ $parsedResult = $this->xml2array($xml);
+ } else {
+ $parsedResult = (array) $soapCallResult;
+ }
+
+ return $parsedResult;
+ }
+
+ /**
+ * Converts SimpleXMLElement to an associative array,.
+ *
+ * @param mixed $xmlObject
+ *
+ * @return array
+ */
+ private function xml2array($xmlObject): array
+ {
+ $out = [];
+
+ $xmlArray = (array) $xmlObject;
+ foreach ($xmlArray as $index => $node) {
+ if (\is_object($node) || \is_array($node)) {
+ $out[$index] = $this->xml2array($node);
+ } else {
+ $out[$index] = $node;
+ }
+ }
+
+ return $out;
+ }
+
+ /**
+ * Returns a SoapClient instance for soap requests.
+ *
+ * @return SoapClient
+ */
+ private function getSoapClient()
+ {
+ if ($this->client !== null) {
+ return $this->client;
+ }
+
+ $this->client = new SoapClient(
+ $this->wsdl,
+ [
+ 'exception' => true,
+ ]
+ );
+
+ return $this->client;
+ }
+}
diff --git a/src/DataHelper.php b/src/DataHelper.php
new file mode 100644
index 0000000..cab51de
--- /dev/null
+++ b/src/DataHelper.php
@@ -0,0 +1,238 @@
+format(\DATE_ATOM),
+ $date->getTimezone()
+ );
+ } else {
+ $immutableDate = new DateTimeImmutable($date);
+ }
+ } catch (Throwable $e) {
+ throw new CbrfException($e->getMessage(), 0, $e);
+ }
+
+ return $immutableDate;
+ }
+
+ /**
+ * Returns array from the set path.
+ *
+ * @param string $path
+ * @param mixed $data
+ *
+ * @return array
+ */
+ public static function array(string $path, $data): array
+ {
+ $item = self::get($path, $data);
+
+ if ($item === null) {
+ $item = [];
+ } elseif (!\is_array($item)) {
+ $message = sprintf("Can't find an array by '%s' path.", $path);
+ throw new CbrfException($message);
+ }
+
+ return $item;
+ }
+
+ /**
+ * Returns DateTimeInterface from the set path.
+ *
+ * @param string $path
+ * @param mixed $data
+ *
+ * @return DateTimeInterface
+ */
+ public static function dateTime(string $path, $data): DateTimeInterface
+ {
+ $item = self::get($path, $data);
+
+ if (!\is_string($item) || empty($item)) {
+ $message = sprintf("Can't find a date by '%s' path.", $path);
+ throw new CbrfException($message);
+ }
+
+ return self::createImmutableDateTime($item);
+ }
+
+ /**
+ * Returns string from the set path.
+ *
+ * @param string $path
+ * @param mixed $data
+ * @param string|null $default
+ *
+ * @return string
+ */
+ public static function string(string $path, $data, ?string $default = null): string
+ {
+ $item = self::get($path, $data);
+
+ if ($item === null) {
+ if ($default !== null) {
+ $return = $default;
+ } else {
+ $message = sprintf("Can't find a string by '%s' path.", $path);
+ throw new CbrfException($message);
+ }
+ } else {
+ $return = trim((string) $item);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Returns float from the set path.
+ *
+ * @param string $path
+ * @param mixed $data
+ * @param float|null $default
+ *
+ * @return float
+ */
+ public static function float(string $path, $data, ?float $default = null): float
+ {
+ $item = self::get($path, $data);
+
+ if ($item === null) {
+ if ($default !== null) {
+ $return = $default;
+ } else {
+ $message = sprintf("Can't find a float by '%s' path.", $path);
+ throw new CbrfException($message);
+ }
+ } else {
+ $return = (float) trim((string) $item);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Returns float from the set path or null is there is no data.
+ *
+ * @param string $path
+ * @param mixed $data
+ *
+ * @return float|null
+ */
+ public static function floatOrNull(string $path, $data): ?float
+ {
+ $item = self::get($path, $data);
+
+ if ($item === null) {
+ $return = null;
+ } else {
+ $return = (float) trim((string) $item);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Returns int from the set path.
+ *
+ * @param string $path
+ * @param mixed $data
+ * @param int|null $default
+ *
+ * @return int
+ */
+ public static function int(string $path, $data, ?int $default = null): int
+ {
+ $item = self::get($path, $data);
+
+ if ($item === null) {
+ if ($default !== null) {
+ $return = $default;
+ } else {
+ $message = sprintf("Can't find an int by '%s' path.", $path);
+ throw new CbrfException($message);
+ }
+ } else {
+ $return = (int) trim((string) $item);
+ }
+
+ return $return;
+ }
+
+ /**
+ * Returns char code from the set path.
+ *
+ * @param string $path
+ * @param mixed $data
+ * @param string|null $default
+ *
+ * @return string
+ */
+ public static function charCode(string $path, $data, ?string $default = null): string
+ {
+ $item = self::get($path, $data);
+
+ if ($item === null) {
+ if ($default !== null) {
+ $return = $default;
+ } else {
+ $message = sprintf("Can't find a char code by '%s' path.", $path);
+ throw new CbrfException($message);
+ }
+ } else {
+ $return = trim((string) $item);
+ }
+
+ return strtoupper($return);
+ }
+
+ /**
+ * Returns data from the set path.
+ *
+ * @param string $path
+ * @param mixed $data
+ *
+ * @return mixed
+ */
+ private static function get(string $path, $data)
+ {
+ $arPath = explode('.', trim($path, " \n\r\t\v\0."));
+
+ $item = $data;
+ foreach ($arPath as $chainItem) {
+ if (\is_array($item) && \array_key_exists($chainItem, $item)) {
+ $item = $item[$chainItem];
+ } elseif (\is_object($item) && property_exists($item, $chainItem)) {
+ $item = $item->$chainItem;
+ } else {
+ $item = null;
+ break;
+ }
+ }
+
+ return $item;
+ }
+}
diff --git a/src/Entity/Currency.php b/src/Entity/Currency.php
new file mode 100644
index 0000000..95c7cc6
--- /dev/null
+++ b/src/Entity/Currency.php
@@ -0,0 +1,19 @@
+internalCode = DataHelper::string('Vcode', $item, '');
+ $this->name = DataHelper::string('Vname', $item, '');
+ $this->engName = DataHelper::string('VEngname', $item, '');
+ $this->nom = DataHelper::int('Vnom', $item, 0);
+ $this->commonCode = DataHelper::string('VcommonCode', $item, '');
+ $this->numericCode = DataHelper::int('VnumCode', $item, 0);
+ $this->charCode = DataHelper::charCode('VcharCode', $item, '');
+ }
+
+ public function getInternalCode(): string
+ {
+ return $this->internalCode;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function getEngName(): string
+ {
+ return $this->engName;
+ }
+
+ public function getNom(): int
+ {
+ return $this->nom;
+ }
+
+ public function getCommonCode(): string
+ {
+ return $this->commonCode;
+ }
+
+ public function getNumericCode(): int
+ {
+ return $this->numericCode;
+ }
+
+ public function getCharCode(): string
+ {
+ return $this->charCode;
+ }
+}
diff --git a/src/Entity/CurrencyRate.php b/src/Entity/CurrencyRate.php
new file mode 100644
index 0000000..1a47336
--- /dev/null
+++ b/src/Entity/CurrencyRate.php
@@ -0,0 +1,66 @@
+charCode = DataHelper::charCode('VchCode', $item, '');
+ $this->name = DataHelper::string('Vname', $item, '');
+ $this->numericCode = DataHelper::int('Vcode', $item, 0);
+ $this->rate = DataHelper::float('Vcurs', $item, .0);
+ $this->nom = DataHelper::int('Vnom', $item, 0);
+ $this->date = $date;
+ }
+
+ public function getCharCode(): string
+ {
+ return $this->charCode;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function getNumericCode(): int
+ {
+ return $this->numericCode;
+ }
+
+ public function getRate(): float
+ {
+ return $this->rate;
+ }
+
+ public function getNom(): int
+ {
+ return $this->nom;
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+}
diff --git a/src/Entity/DepoRate.php b/src/Entity/DepoRate.php
new file mode 100644
index 0000000..d43e9ca
--- /dev/null
+++ b/src/Entity/DepoRate.php
@@ -0,0 +1,34 @@
+rate = DataHelper::float('Overnight', $item, .0);
+ $this->date = DataHelper::dateTime('DateDepo', $item);
+ }
+
+ public function getRate(): float
+ {
+ return $this->rate;
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+}
diff --git a/src/Entity/InternationalReserve.php b/src/Entity/InternationalReserve.php
new file mode 100644
index 0000000..0f3d854
--- /dev/null
+++ b/src/Entity/InternationalReserve.php
@@ -0,0 +1,34 @@
+date = DataHelper::dateTime('D0', $item);
+ $this->value = DataHelper::float('p1', $item, .0);
+ }
+
+ public function getValue(): float
+ {
+ return $this->value;
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+}
diff --git a/src/Entity/InternationalReserveWeek.php b/src/Entity/InternationalReserveWeek.php
new file mode 100644
index 0000000..98ebcd8
--- /dev/null
+++ b/src/Entity/InternationalReserveWeek.php
@@ -0,0 +1,34 @@
+date = DataHelper::dateTime('D0', $item);
+ $this->value = DataHelper::float('val', $item, .0);
+ }
+
+ public function getValue(): float
+ {
+ return $this->value;
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+}
diff --git a/src/Entity/KeyRate.php b/src/Entity/KeyRate.php
new file mode 100644
index 0000000..ad87af2
--- /dev/null
+++ b/src/Entity/KeyRate.php
@@ -0,0 +1,34 @@
+date = DataHelper::dateTime('DT', $item);
+ $this->rate = DataHelper::float('Rate', $item, .0);
+ }
+
+ public function getRate(): float
+ {
+ return $this->rate;
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+}
diff --git a/src/Entity/Mkr.php b/src/Entity/Mkr.php
new file mode 100644
index 0000000..55d66da
--- /dev/null
+++ b/src/Entity/Mkr.php
@@ -0,0 +1,82 @@
+date = DataHelper::dateTime('CDate', $item);
+ $this->p1 = DataHelper::int('p1', $item, 0);
+ $this->d1 = DataHelper::floatOrNull('d1', $item);
+ $this->d7 = DataHelper::floatOrNull('d7', $item);
+ $this->d30 = DataHelper::floatOrNull('d30', $item);
+ $this->d90 = DataHelper::floatOrNull('d90', $item);
+ $this->d180 = DataHelper::floatOrNull('d180', $item);
+ $this->d360 = DataHelper::floatOrNull('d360', $item);
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+
+ public function getP1(): int
+ {
+ return $this->p1;
+ }
+
+ public function getD1(): ?float
+ {
+ return $this->d1;
+ }
+
+ public function getD7(): ?float
+ {
+ return $this->d7;
+ }
+
+ public function getD30(): ?float
+ {
+ return $this->d30;
+ }
+
+ public function getD90(): ?float
+ {
+ return $this->d90;
+ }
+
+ public function getD180(): ?float
+ {
+ return $this->d180;
+ }
+
+ public function getD360(): ?float
+ {
+ return $this->d360;
+ }
+}
diff --git a/src/Entity/OstatDepoRate.php b/src/Entity/OstatDepoRate.php
new file mode 100644
index 0000000..756a986
--- /dev/null
+++ b/src/Entity/OstatDepoRate.php
@@ -0,0 +1,42 @@
+date = DataHelper::dateTime('D0', $item);
+ $this->days1to7 = DataHelper::float('D1_7', $item, .0);
+ $this->total = DataHelper::float('total', $item, .0);
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+
+ public function getDays1to7(): float
+ {
+ return $this->days1to7;
+ }
+
+ public function getTotal(): float
+ {
+ return $this->total;
+ }
+}
diff --git a/src/Entity/OstatRate.php b/src/Entity/OstatRate.php
new file mode 100644
index 0000000..2eeb6a7
--- /dev/null
+++ b/src/Entity/OstatRate.php
@@ -0,0 +1,46 @@
+>>>>>> origin/master
+ */
+class OstatRate
+{
+ private float $moscow = .0;
+
+ private float $russia = .0;
+
+ private DateTimeInterface $date;
+
+ public function __construct(array $item)
+ {
+ $this->date = DataHelper::dateTime('DateOst', $item);
+ $this->moscow = DataHelper::float('InMoscow', $item, .0);
+ $this->russia = DataHelper::float('InRuss', $item, .0);
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+
+ public function getMoscow(): float
+ {
+ return $this->moscow;
+ }
+
+ public function getRussia(): float
+ {
+ return $this->russia;
+ }
+}
diff --git a/src/Entity/PreciousMetalRate.php b/src/Entity/PreciousMetalRate.php
new file mode 100644
index 0000000..e349bda
--- /dev/null
+++ b/src/Entity/PreciousMetalRate.php
@@ -0,0 +1,47 @@
+date = DataHelper::dateTime('DateMet', $item);
+ $this->code = DataHelper::int('CodMet', $item, 0);
+ $this->price = DataHelper::float('price', $item, .0);
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+
+ public function getCode(): int
+ {
+ return $this->code;
+ }
+
+ public function getPrice(): float
+ {
+ return $this->price;
+ }
+}
diff --git a/src/Entity/ReutersCurrencyRate.php b/src/Entity/ReutersCurrencyRate.php
new file mode 100644
index 0000000..be038fa
--- /dev/null
+++ b/src/Entity/ReutersCurrencyRate.php
@@ -0,0 +1,73 @@
+chCode = strtoupper(trim($item['char_code'] ?? ''));
+ $this->nameRu = trim($item['Title_ru'] ?? '');
+ $this->nameEn = trim($item['Title_en'] ?? '');
+ $this->code = (int) ($item['num_code'] ?? 0);
+ $this->curs = (float) ($item['val'] ?? .0);
+ $this->dir = (int) ($item['dir'] ?? 0);
+ $this->date = $date;
+ }
+
+ public function getChCode(): string
+ {
+ return $this->chCode;
+ }
+
+ public function getNameRu(): string
+ {
+ return $this->nameRu;
+ }
+
+ public function getNameEn(): string
+ {
+ return $this->nameEn;
+ }
+
+ public function getCode(): int
+ {
+ return $this->code;
+ }
+
+ public function getCurs(): float
+ {
+ return $this->curs;
+ }
+
+ public function getDir(): int
+ {
+ return $this->dir;
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+}
diff --git a/src/Entity/RuoniaBid.php b/src/Entity/RuoniaBid.php
new file mode 100644
index 0000000..9df3d08
--- /dev/null
+++ b/src/Entity/RuoniaBid.php
@@ -0,0 +1,50 @@
+date = DataHelper::dateTime('D0', $item);
+ $this->bid = DataHelper::float('ruo', $item, .0);
+ $this->dealsVolume = DataHelper::float('vol', $item, .0);
+ $this->dateUpdate = DataHelper::dateTime('DateUpdate', $item);
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+
+ public function getBid(): float
+ {
+ return $this->bid;
+ }
+
+ public function getDealsVolume(): float
+ {
+ return $this->dealsVolume;
+ }
+
+ public function getDateUpdate(): DateTimeInterface
+ {
+ return $this->dateUpdate;
+ }
+}
diff --git a/src/Entity/RuoniaIndex.php b/src/Entity/RuoniaIndex.php
new file mode 100644
index 0000000..f05f9c3
--- /dev/null
+++ b/src/Entity/RuoniaIndex.php
@@ -0,0 +1,58 @@
+date = DataHelper::dateTime('DT', $item);
+ $this->index = DataHelper::float('RUONIA_Index', $item, .0);
+ $this->average1Month = DataHelper::float('RUONIA_AVG_1M', $item, .0);
+ $this->average3Month = DataHelper::float('RUONIA_AVG_3M', $item, .0);
+ $this->average6Month = DataHelper::float('RUONIA_AVG_6M', $item, .0);
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+
+ public function getIndex(): float
+ {
+ return $this->index;
+ }
+
+ public function getAverage1Month(): float
+ {
+ return $this->average1Month;
+ }
+
+ public function getAverage3Month(): float
+ {
+ return $this->average3Month;
+ }
+
+ public function getAverage6Month(): float
+ {
+ return $this->average6Month;
+ }
+}
diff --git a/src/Entity/Saldo.php b/src/Entity/Saldo.php
new file mode 100644
index 0000000..5cfe5b6
--- /dev/null
+++ b/src/Entity/Saldo.php
@@ -0,0 +1,34 @@
+date = DataHelper::dateTime('Dt', $item);
+ $this->value = DataHelper::float('DEADLINEBS', $item, .0);
+ }
+
+ public function getDate(): DateTimeInterface
+ {
+ return $this->date;
+ }
+
+ public function getValue(): float
+ {
+ return $this->value;
+ }
+}
diff --git a/src/Entity/SwapRate.php b/src/Entity/SwapRate.php
new file mode 100644
index 0000000..22f39fc
--- /dev/null
+++ b/src/Entity/SwapRate.php
@@ -0,0 +1,66 @@
+dateBuy = DataHelper::dateTime('DateBuy', $item);
+ $this->dateSell = DataHelper::dateTime('DateSell', $item);
+ $this->baseRate = DataHelper::float('BaseRate', $item, .0);
+ $this->tir = DataHelper::float('TIR', $item, .0);
+ $this->rate = DataHelper::float('Stavka', $item, .0);
+ $this->currency = DataHelper::int('Currency', $item, 0);
+ }
+
+ public function getDateBuy(): DateTimeInterface
+ {
+ return $this->dateBuy;
+ }
+
+ public function getDateSell(): DateTimeInterface
+ {
+ return $this->dateSell;
+ }
+
+ public function getBaseRate(): float
+ {
+ return $this->baseRate;
+ }
+
+ public function getTIR(): float
+ {
+ return $this->tir;
+ }
+
+ public function getRate(): float
+ {
+ return $this->rate;
+ }
+
+ public function getCurrency(): int
+ {
+ return $this->currency;
+ }
+}
diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php
new file mode 100644
index 0000000..62e941a
--- /dev/null
+++ b/tests/BaseTestCase.php
@@ -0,0 +1,169 @@
+assertSame(
+ $date1->getTimestamp(),
+ $date2->getTimestamp(),
+ "Date objects don't contain same dates"
+ );
+ }
+
+ /**
+ * @param string $method
+ * @param array|null $params
+ * @param mixed $result
+ *
+ * @return SoapClient
+ */
+ protected function createSoapCallMock(string $method, ?array $params, $result = null): SoapClient
+ {
+ /** @var MockObject&SoapClient */
+ $soapClient = $this->getMockBuilder(SoapClient::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ if ($params === null) {
+ $soapClient->expects($this->once())
+ ->method('__soapCall')
+ ->with(
+ $this->identicalTo($method)
+ )
+ ->willReturn($result)
+ ;
+ } else {
+ $soapClient->expects($this->once())
+ ->method('__soapCall')
+ ->with(
+ $this->identicalTo($method),
+ $this->identicalTo([$params])
+ )
+ ->willReturn($result)
+ ;
+ }
+
+ return $soapClient;
+ }
+
+ /**
+ * Creates full fixture.
+ *
+ * @param array $schema
+ *
+ * @return array
+ */
+ protected function createFixture(array $schema): array
+ {
+ $data = $this->createFixtureData($schema['schema'] ?? []);
+ $response = $this->createFixtureResponse($schema['path'] ?? '', $data);
+
+ return [$data, $response];
+ }
+
+ /**
+ * Cretaes array of fixtures by schema.
+ *
+ * @param array $schema
+ * @param int $count
+ *
+ * @return array>
+ */
+ protected function createFixtureData(array $schema, int $count = 4): array
+ {
+ $data = [];
+ for ($i = 0; $i < $count; ++$i) {
+ $datum = [];
+ foreach ($schema as $name => $type) {
+ switch ($type) {
+ case self::FIXTURE_TYPE_STRING:
+ $value = "{$name}_{$i}";
+ break;
+ case self::FIXTURE_TYPE_FLOAT:
+ $value = (float) (mt_rand(101, 999) / 100);
+ break;
+ case self::FIXTURE_TYPE_INT:
+ $value = $i * 10 + mt_rand(0, 9);
+ break;
+ case self::FIXTURE_TYPE_DATE:
+ $value = '2010-10-1' . mt_rand(0, 9);
+ break;
+ default:
+ $message = sprintf("Can't recognize field type '%s'.", $type);
+ throw new RuntimeException($message);
+ }
+ $datum[$name] = $value;
+ }
+ $data[] = $datum;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Returns fixture allowed for xml response.
+ *
+ * @param string $xmlPath
+ * @param array> $data
+ *
+ * @return object
+ */
+ protected function createFixtureResponse(string $xmlPath, array $data): object
+ {
+ $arPath = explode('.any.', $xmlPath);
+ if (\count($arPath) !== 2 || empty($arPath[0]) || empty($arPath[1])) {
+ $message = sprintf("Incorrect XML path '%s'.", $xmlPath);
+ throw new RuntimeException($message);
+ }
+ [$beforeAny, $afterAny] = $arPath;
+
+ $arAfterAny = explode('.', $afterAny);
+ $lastItem = array_pop($arAfterAny);
+ $any = '';
+ foreach ($arAfterAny as $nodeName) {
+ $any .= "<{$nodeName} xmlns=\"\">";
+ }
+ foreach ($data as $datum) {
+ $any .= "<{$lastItem} xmlns=\"\">";
+ foreach ($datum as $key => $value) {
+ $any .= "<{$key}>{$value}{$key}>";
+ }
+ $any .= "{$lastItem}>";
+ }
+ foreach (array_reverse($arAfterAny) as $nodeName) {
+ $any .= "{$nodeName}>";
+ }
+ $any .= '';
+
+ $arBeforeAny = explode('.', $beforeAny);
+ $latest = $soapResponse = new stdClass();
+ foreach ($arBeforeAny as $objectNode) {
+ $newNode = new stdClass();
+ $latest->{$objectNode} = $newNode;
+ $latest = $newNode;
+ }
+ $latest->any = $any;
+
+ return $soapResponse;
+ }
+}
diff --git a/tests/CbrfDailyTest.php b/tests/CbrfDailyTest.php
new file mode 100644
index 0000000..711944c
--- /dev/null
+++ b/tests/CbrfDailyTest.php
@@ -0,0 +1,822 @@
+ [
+ 'schema' => [
+ 'VchCode' => self::FIXTURE_TYPE_STRING,
+ 'Vname' => self::FIXTURE_TYPE_STRING,
+ 'Vcode' => self::FIXTURE_TYPE_INT,
+ 'Vcurs' => self::FIXTURE_TYPE_FLOAT,
+ 'Vnom' => self::FIXTURE_TYPE_INT,
+ ],
+ 'path' => 'GetCursOnDateResult.any.ValuteData.ValuteCursOnDate',
+ ],
+ 'EnumValutes' => [
+ 'schema' => [
+ 'Vcode' => self::FIXTURE_TYPE_STRING,
+ 'Vname' => self::FIXTURE_TYPE_STRING,
+ 'VEngname' => self::FIXTURE_TYPE_STRING,
+ 'Vnom' => self::FIXTURE_TYPE_INT,
+ 'VcommonCode' => self::FIXTURE_TYPE_STRING,
+ 'VnumCode' => self::FIXTURE_TYPE_INT,
+ 'VcharCode' => self::FIXTURE_TYPE_STRING,
+ ],
+ 'path' => 'EnumValutesResult.any.ValuteData.EnumValutes',
+ ],
+ 'CursDynamic' => [
+ 'schema' => [
+ 'CursDate' => self::FIXTURE_TYPE_DATE,
+ 'Vcurs' => self::FIXTURE_TYPE_FLOAT,
+ 'Vcode' => self::FIXTURE_TYPE_STRING,
+ 'Vnom' => self::FIXTURE_TYPE_INT,
+ ],
+ 'path' => 'GetCursDynamicResult.any.ValuteData.ValuteCursDynamic',
+ ],
+ 'KeyRate' => [
+ 'schema' => [
+ 'DT' => self::FIXTURE_TYPE_DATE,
+ 'Rate' => self::FIXTURE_TYPE_FLOAT,
+ ],
+ 'path' => 'KeyRateResult.any.KeyRate.KR',
+ ],
+ 'DragMetDynamic' => [
+ 'schema' => [
+ 'DateMet' => self::FIXTURE_TYPE_DATE,
+ 'price' => self::FIXTURE_TYPE_FLOAT,
+ 'CodMet' => self::FIXTURE_TYPE_INT,
+ ],
+ 'path' => 'DragMetDynamicResult.any.DragMetall.DrgMet',
+ ],
+ 'SwapDynamic' => [
+ 'schema' => [
+ 'DateBuy' => self::FIXTURE_TYPE_DATE,
+ 'DateSell' => self::FIXTURE_TYPE_DATE,
+ 'BaseRate' => self::FIXTURE_TYPE_FLOAT,
+ 'TIR' => self::FIXTURE_TYPE_FLOAT,
+ 'Stavka' => self::FIXTURE_TYPE_FLOAT,
+ 'Currency' => self::FIXTURE_TYPE_INT,
+ ],
+ 'path' => 'SwapDynamicResult.any.SwapDynamic.Swap',
+ ],
+ 'DepoDynamic' => [
+ 'schema' => [
+ 'Overnight' => self::FIXTURE_TYPE_FLOAT,
+ 'DateDepo' => self::FIXTURE_TYPE_DATE,
+ ],
+ 'path' => 'DepoDynamicResult.any.DepoDynamic.Depo',
+ ],
+ 'OstatDynamic' => [
+ 'schema' => [
+ 'DateOst' => self::FIXTURE_TYPE_DATE,
+ 'InMoscow' => self::FIXTURE_TYPE_FLOAT,
+ 'InRuss' => self::FIXTURE_TYPE_FLOAT,
+ ],
+ 'path' => 'OstatDynamicResult.any.OstatDynamic.Ostat',
+ ],
+ 'OstatDepo' => [
+ 'schema' => [
+ 'D0' => self::FIXTURE_TYPE_DATE,
+ 'D1_7' => self::FIXTURE_TYPE_FLOAT,
+ 'total' => self::FIXTURE_TYPE_FLOAT,
+ ],
+ 'path' => 'OstatDepoResult.any.OD.odr',
+ ],
+ 'Mrrf' => [
+ 'schema' => [
+ 'D0' => self::FIXTURE_TYPE_DATE,
+ 'p1' => self::FIXTURE_TYPE_FLOAT,
+ ],
+ 'path' => 'mrrfResult.any.mmrf.mr',
+ ],
+ 'Mrrf7D' => [
+ 'schema' => [
+ 'D0' => self::FIXTURE_TYPE_DATE,
+ 'val' => self::FIXTURE_TYPE_FLOAT,
+ ],
+ 'path' => 'mrrf7DResult.any.mmrf7d.mr',
+ ],
+ 'Saldo' => [
+ 'schema' => [
+ 'Dt' => self::FIXTURE_TYPE_DATE,
+ 'DEADLINEBS' => self::FIXTURE_TYPE_FLOAT,
+ ],
+ 'path' => 'SaldoResult.any.Saldo.So',
+ ],
+ 'RuoniaSV' => [
+ 'schema' => [
+ 'DT' => self::FIXTURE_TYPE_DATE,
+ 'RUONIA_Index' => self::FIXTURE_TYPE_FLOAT,
+ 'RUONIA_AVG_1M' => self::FIXTURE_TYPE_FLOAT,
+ 'RUONIA_AVG_3M' => self::FIXTURE_TYPE_FLOAT,
+ 'RUONIA_AVG_6M' => self::FIXTURE_TYPE_FLOAT,
+ ],
+ 'path' => 'RuoniaSVResult.any.RuoniaSV.ra',
+ ],
+ 'Ruonia' => [
+ 'schema' => [
+ 'D0' => self::FIXTURE_TYPE_DATE,
+ 'ruo' => self::FIXTURE_TYPE_FLOAT,
+ 'vol' => self::FIXTURE_TYPE_FLOAT,
+ 'DateUpdate' => self::FIXTURE_TYPE_DATE,
+ ],
+ 'path' => 'RuoniaResult.any.Ruonia.ro',
+ ],
+ 'MKR' => [
+ 'schema' => [
+ 'CDate' => self::FIXTURE_TYPE_DATE,
+ 'p1' => self::FIXTURE_TYPE_INT,
+ 'd1' => self::FIXTURE_TYPE_FLOAT,
+ 'd7' => self::FIXTURE_TYPE_FLOAT,
+ 'd30' => self::FIXTURE_TYPE_FLOAT,
+ 'd90' => self::FIXTURE_TYPE_FLOAT,
+ 'd180' => self::FIXTURE_TYPE_FLOAT,
+ 'd360' => self::FIXTURE_TYPE_FLOAT,
+ ],
+ 'path' => 'MKRResult.any.mkr_base.MKR',
+ ],
+ ];
+
+ /**
+ * @test
+ */
+ public function testGetCursOnDate(): void
+ {
+ [$courses, $response] = $this->createFixture(self::FIXTURES['CurrencyRate']);
+ $onDate = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'GetCursOnDate',
+ [
+ 'On_date' => $onDate->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->getCursOnDate($onDate);
+
+ $this->assertCount(\count($courses), $list);
+ $this->assertContainsOnlyInstancesOf(CurrencyRate::class, $list);
+ foreach ($courses as $key => $course) {
+ $this->assertSame(strtoupper($course['VchCode']), $list[$key]->getCharCode());
+ $this->assertSame($course['Vname'], $list[$key]->getName());
+ $this->assertSame($course['Vcode'], $list[$key]->getNumericCode());
+ $this->assertSame($course['Vcurs'], $list[$key]->getRate());
+ $this->assertSame($course['Vnom'], $list[$key]->getNom());
+ $this->assertSameDate($onDate, $list[$key]->getDate());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testGetCursOnDateByCharCode(): void
+ {
+ [$courses, $response] = $this->createFixture(self::FIXTURES['CurrencyRate']);
+ $charCode = $courses[0]['VchCode'] ?? '';
+ $onDate = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'GetCursOnDate',
+ [
+ 'On_date' => $onDate->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $item = $service->getCursOnDateByCharCode($onDate, $charCode);
+
+ $this->assertInstanceOf(CurrencyRate::class, $item);
+ $this->assertSame(strtoupper($charCode), $item->getCharCode());
+ }
+
+ /**
+ * @test
+ */
+ public function testGetCursOnDateByNumericCode(): void
+ {
+ [$courses, $response] = $this->createFixture(self::FIXTURES['CurrencyRate']);
+ $numericCode = $courses[0]['Vcode'] ?? 0;
+ $onDate = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'GetCursOnDate',
+ [
+ 'On_date' => $onDate->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $item = $service->getCursOnDateByNumericCode($onDate, $numericCode);
+
+ $this->assertInstanceOf(CurrencyRate::class, $item);
+ $this->assertSame($numericCode, $item->getNumericCode());
+ }
+
+ /**
+ * @test
+ */
+ public function testEnumValutes(): void
+ {
+ [$currencies, $response] = $this->createFixture(self::FIXTURES['EnumValutes']);
+ $seld = false;
+
+ $soapClient = $this->createSoapCallMock(
+ 'EnumValutes',
+ [
+ 'Seld' => $seld,
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->enumValutes($seld);
+
+ $this->assertCount(\count($currencies), $list);
+ $this->assertContainsOnlyInstancesOf(CurrencyEnum::class, $list);
+ foreach ($currencies as $key => $currency) {
+ $this->assertSame(strtoupper($currency['VcharCode']), $list[$key]->getCharCode());
+ $this->assertSame($currency['Vname'], $list[$key]->getName());
+ $this->assertSame($currency['Vcode'], $list[$key]->getInternalCode());
+ $this->assertSame($currency['VEngname'], $list[$key]->getEngName());
+ $this->assertSame($currency['Vnom'], $list[$key]->getNom());
+ $this->assertSame($currency['VnumCode'], $list[$key]->getNumericCode());
+ $this->assertSame($currency['VcommonCode'], $list[$key]->getCommonCode());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testEnumValuteByCharCode(): void
+ {
+ [$courses, $response] = $this->createFixture(self::FIXTURES['EnumValutes']);
+ $charCode = $courses[0]['VcharCode'] ?? '';
+ $seld = false;
+
+ $soapClient = $this->createSoapCallMock(
+ 'EnumValutes',
+ [
+ 'Seld' => $seld,
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $item = $service->enumValuteByCharCode($charCode, $seld);
+
+ $this->assertInstanceOf(CurrencyEnum::class, $item);
+ $this->assertSame(strtoupper($charCode), $item->getCharCode());
+ }
+
+ /**
+ * @test
+ */
+ public function testEnumValuteByNumericCode(): void
+ {
+ [$courses, $response] = $this->createFixture(self::FIXTURES['EnumValutes']);
+ $numericCode = $courses[0]['VnumCode'] ?? 0;
+ $seld = false;
+
+ $soapClient = $this->createSoapCallMock(
+ 'EnumValutes',
+ [
+ 'Seld' => $seld,
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $item = $service->enumValuteByNumericCode($numericCode, $seld);
+
+ $this->assertInstanceOf(CurrencyEnum::class, $item);
+ $this->assertSame($numericCode, $item->getNumericCode());
+ }
+
+ /**
+ * @test
+ */
+ public function testGetLatestDateTime(): void
+ {
+ $date = new DateTimeImmutable();
+ $response = new stdClass();
+ $response->GetLatestDateTimeResult = $date->format(CbrfSoapService::DATE_TIME_FORMAT);
+
+ $soapClient = $this->createSoapCallMock(
+ 'GetLatestDateTime',
+ null,
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $testDate = $service->getLatestDateTime();
+
+ $this->assertSameDate($date, $testDate);
+ }
+
+ /**
+ * @test
+ */
+ public function testGetLatestDateTimeSeld(): void
+ {
+ $date = new DateTimeImmutable();
+ $response = new stdClass();
+ $response->GetLatestDateTimeSeldResult = $date->format(CbrfSoapService::DATE_TIME_FORMAT);
+
+ $soapClient = $this->createSoapCallMock(
+ 'GetLatestDateTimeSeld',
+ null,
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $testDate = $service->getLatestDateTimeSeld();
+
+ $this->assertSameDate($date, $testDate);
+ }
+
+ /**
+ * @test
+ */
+ public function testGetLatestDate(): void
+ {
+ $date = new DateTimeImmutable();
+ $response = new stdClass();
+ $response->GetLatestDateResult = $date->format(CbrfSoapService::DATE_TIME_FORMAT);
+
+ $soapClient = $this->createSoapCallMock(
+ 'GetLatestDate',
+ null,
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $testDate = $service->getLatestDate();
+
+ $this->assertSameDate($date, $testDate);
+ }
+
+ /**
+ * @test
+ */
+ public function testGetLatestDateSeld(): void
+ {
+ $date = new DateTimeImmutable();
+ $response = new stdClass();
+ $response->GetLatestDateSeldResult = $date->format(CbrfSoapService::DATE_TIME_FORMAT);
+
+ $soapClient = $this->createSoapCallMock(
+ 'GetLatestDateSeld',
+ null,
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $testDate = $service->getLatestDateSeld();
+
+ $this->assertSameDate($date, $testDate);
+ }
+
+ /**
+ * @test
+ */
+ public function testGetCursDynamic(): void
+ {
+ [$currencies, $response] = $this->createFixture(self::FIXTURES['CursDynamic']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+ $charCode = 'EUR';
+ $numericCode = 978;
+ $name = 'Euro';
+ $internalCode = 'test01';
+
+ /** @var MockObject&CurrencyEnum */
+ $currencyEnum = $this->getMockBuilder(CurrencyEnum::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $currencyEnum->method('getInternalCode')->willReturn($internalCode);
+ $currencyEnum->method('getName')->willReturn($name);
+ $currencyEnum->method('getCharCode')->willReturn($charCode);
+ $currencyEnum->method('getNumericCode')->willReturn($numericCode);
+
+ $soapClient = $this->createSoapCallMock(
+ 'GetCursDynamic',
+ [
+ 'FromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ValutaCode' => $internalCode,
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->getCursDynamic($from, $to, $currencyEnum);
+
+ $this->assertCount(\count($currencies), $list);
+ $this->assertContainsOnlyInstancesOf(CurrencyRate::class, $list);
+ foreach ($currencies as $key => $currency) {
+ $this->assertSame($charCode, $list[$key]->getCharCode());
+ $this->assertSame($name, $list[$key]->getName());
+ $this->assertSame($numericCode, $list[$key]->getNumericCode());
+ $this->assertSame($currency['Vcurs'], $list[$key]->getRate());
+ $this->assertSame($currency['Vnom'], $list[$key]->getNom());
+ $this->assertSameDate(new DateTimeImmutable($currency['CursDate']), $list[$key]->getDate());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testKeyRate(): void
+ {
+ [$rates, $response] = $this->createFixture(self::FIXTURES['KeyRate']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'KeyRate',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->keyRate($from, $to);
+
+ $this->assertCount(\count($rates), $list);
+ $this->assertContainsOnlyInstancesOf(KeyRate::class, $list);
+ foreach ($rates as $key => $rate) {
+ $this->assertSameDate(new DateTimeImmutable($rate['DT']), $list[$key]->getDate());
+ $this->assertSame($rate['Rate'], $list[$key]->getRate());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testDragMetDynamic(): void
+ {
+ [$metals, $response] = $this->createFixture(self::FIXTURES['DragMetDynamic']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'DragMetDynamic',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->dragMetDynamic($from, $to);
+
+ $this->assertCount(\count($metals), $list);
+ $this->assertContainsOnlyInstancesOf(PreciousMetalRate::class, $list);
+ foreach ($metals as $key => $metal) {
+ $this->assertSameDate(new DateTimeImmutable($metal['DateMet']), $list[$key]->getDate());
+ $this->assertSame($metal['CodMet'], $list[$key]->getCode());
+ $this->assertSame($metal['price'], $list[$key]->getPrice());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testSwapDynamic(): void
+ {
+ [$swaps, $response] = $this->createFixture(self::FIXTURES['SwapDynamic']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'SwapDynamic',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->swapDynamic($from, $to);
+
+ $this->assertCount(\count($swaps), $list);
+ $this->assertContainsOnlyInstancesOf(SwapRate::class, $list);
+ foreach ($swaps as $key => $swap) {
+ $this->assertSameDate(new DateTimeImmutable($swap['DateBuy']), $list[$key]->getDateBuy());
+ $this->assertSameDate(new DateTimeImmutable($swap['DateSell']), $list[$key]->getDateSell());
+ $this->assertSame($swap['BaseRate'], $list[$key]->getBaseRate());
+ $this->assertSame($swap['TIR'], $list[$key]->getTIR());
+ $this->assertSame($swap['Stavka'], $list[$key]->getRate());
+ $this->assertSame($swap['Currency'], $list[$key]->getCurrency());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testDepoDynamic(): void
+ {
+ [$depos, $response] = $this->createFixture(self::FIXTURES['DepoDynamic']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'DepoDynamic',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->depoDynamic($from, $to);
+
+ $this->assertCount(\count($depos), $list);
+ $this->assertContainsOnlyInstancesOf(DepoRate::class, $list);
+ foreach ($depos as $key => $depo) {
+ $this->assertSameDate(new DateTimeImmutable($depo['DateDepo']), $list[$key]->getDate());
+ $this->assertSame($depo['Overnight'], $list[$key]->getRate());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testOstatDynamic(): void
+ {
+ [$depos, $response] = $this->createFixture(self::FIXTURES['OstatDynamic']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'OstatDynamic',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->ostatDynamic($from, $to);
+
+ $this->assertCount(\count($depos), $list);
+ $this->assertContainsOnlyInstancesOf(OstatRate::class, $list);
+ foreach ($depos as $key => $ostat) {
+ $this->assertSameDate(new DateTimeImmutable($ostat['DateOst']), $list[$key]->getDate());
+ $this->assertSame($ostat['InMoscow'], $list[$key]->getMoscow());
+ $this->assertSame($ostat['InRuss'], $list[$key]->getRussia());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testOstatDepo(): void
+ {
+ [$depos, $response] = $this->createFixture(self::FIXTURES['OstatDepo']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'OstatDepo',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->ostatDepo($from, $to);
+
+ $this->assertCount(\count($depos), $list);
+ $this->assertContainsOnlyInstancesOf(OstatDepoRate::class, $list);
+ foreach ($depos as $key => $ostat) {
+ $this->assertSameDate(new DateTimeImmutable($ostat['D0']), $list[$key]->getDate());
+ $this->assertSame($ostat['D1_7'], $list[$key]->getDays1to7());
+ $this->assertSame($ostat['total'], $list[$key]->getTotal());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testMrrf(): void
+ {
+ [$mrrfs, $response] = $this->createFixture(self::FIXTURES['Mrrf']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'mrrf',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->mrrf($from, $to);
+
+ $this->assertCount(\count($mrrfs), $list);
+ $this->assertContainsOnlyInstancesOf(InternationalReserve::class, $list);
+ foreach ($mrrfs as $key => $mrrf) {
+ $this->assertSameDate(new DateTimeImmutable($mrrf['D0']), $list[$key]->getDate());
+ $this->assertSame($mrrf['p1'], $list[$key]->getValue());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testMrrf7d(): void
+ {
+ [$mrrfs, $response] = $this->createFixture(self::FIXTURES['Mrrf7D']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'mrrf7D',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->mrrf7d($from, $to);
+
+ $this->assertCount(\count($mrrfs), $list);
+ $this->assertContainsOnlyInstancesOf(InternationalReserveWeek::class, $list);
+ foreach ($mrrfs as $key => $mrrf) {
+ $this->assertSameDate(new DateTimeImmutable($mrrf['D0']), $list[$key]->getDate());
+ $this->assertSame($mrrf['val'], $list[$key]->getValue());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testSaldo(): void
+ {
+ [$saldos, $response] = $this->createFixture(self::FIXTURES['Saldo']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'Saldo',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->saldo($from, $to);
+
+ $this->assertCount(\count($saldos), $list);
+ $this->assertContainsOnlyInstancesOf(Saldo::class, $list);
+ foreach ($saldos as $key => $saldo) {
+ $this->assertSameDate(new DateTimeImmutable($saldo['Dt']), $list[$key]->getDate());
+ $this->assertSame($saldo['DEADLINEBS'], $list[$key]->getValue());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testRuoniaSV(): void
+ {
+ [$ruoniaIndexes, $response] = $this->createFixture(self::FIXTURES['RuoniaSV']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'RuoniaSV',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->ruoniaSV($from, $to);
+
+ $this->assertCount(\count($ruoniaIndexes), $list);
+ $this->assertContainsOnlyInstancesOf(RuoniaIndex::class, $list);
+ foreach ($ruoniaIndexes as $key => $ruoniaIndex) {
+ $this->assertSameDate(new DateTimeImmutable($ruoniaIndex['DT']), $list[$key]->getDate());
+ $this->assertSame($ruoniaIndex['RUONIA_Index'], $list[$key]->getIndex());
+ $this->assertSame($ruoniaIndex['RUONIA_AVG_1M'], $list[$key]->getAverage1Month());
+ $this->assertSame($ruoniaIndex['RUONIA_AVG_3M'], $list[$key]->getAverage3Month());
+ $this->assertSame($ruoniaIndex['RUONIA_AVG_6M'], $list[$key]->getAverage6Month());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testMKR(): void
+ {
+ [$mkrs, $response] = $this->createFixture(self::FIXTURES['MKR']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'MKR',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->mkr($from, $to);
+
+ $this->assertCount(\count($mkrs), $list);
+ $this->assertContainsOnlyInstancesOf(Mkr::class, $list);
+ foreach ($mkrs as $key => $mkr) {
+ $this->assertSameDate(new DateTimeImmutable($mkr['CDate']), $list[$key]->getDate());
+ $this->assertSame($mkr['p1'], $list[$key]->getP1());
+ $this->assertSame($mkr['d1'], $list[$key]->getD1());
+ $this->assertSame($mkr['d7'], $list[$key]->getD7());
+ $this->assertSame($mkr['d30'], $list[$key]->getD30());
+ $this->assertSame($mkr['d180'], $list[$key]->getD180());
+ $this->assertSame($mkr['d360'], $list[$key]->getD360());
+ }
+ }
+
+ /**
+ * @test
+ */
+ public function testRuonia(): void
+ {
+ [$ruoniaBids, $response] = $this->createFixture(self::FIXTURES['Ruonia']);
+ $from = new DateTimeImmutable('-1 month');
+ $to = new DateTimeImmutable();
+
+ $soapClient = $this->createSoapCallMock(
+ 'Ruonia',
+ [
+ 'fromDate' => $from->format(CbrfSoapService::DATE_TIME_FORMAT),
+ 'ToDate' => $to->format(CbrfSoapService::DATE_TIME_FORMAT),
+ ],
+ $response
+ );
+
+ $service = new CbrfDaily($soapClient);
+ $list = $service->ruonia($from, $to);
+
+ $this->assertCount(\count($ruoniaBids), $list);
+ $this->assertContainsOnlyInstancesOf(RuoniaBid::class, $list);
+ foreach ($ruoniaBids as $key => $ruoniaBid) {
+ $this->assertSameDate(new DateTimeImmutable($ruoniaBid['D0']), $list[$key]->getDate());
+ $this->assertSame($ruoniaBid['ruo'], $list[$key]->getBid());
+ $this->assertSame($ruoniaBid['vol'], $list[$key]->getDealsVolume());
+ $this->assertSameDate(new DateTimeImmutable($ruoniaBid['DateUpdate']), $list[$key]->getDateUpdate());
+ }
+ }
+}
diff --git a/tests/CbrfSoapServiceTest.php b/tests/CbrfSoapServiceTest.php
new file mode 100644
index 0000000..e5b6226
--- /dev/null
+++ b/tests/CbrfSoapServiceTest.php
@@ -0,0 +1,94 @@
+getMockBuilder(SoapClient::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $soapClient->method('__soapCall')
+ ->with(
+ $this->identicalTo($method),
+ $this->identicalTo([$params])
+ )
+ ->willReturn($responseXml);
+
+ $service = new CbrfSoapService($soapClient);
+ $testResponse = $service->query($method, $params);
+
+ $this->assertSame($response, $testResponse);
+ }
+
+ public function queryProvider(): array
+ {
+ $responseXml = new stdClass();
+ $responseXml->TestSoapAnyXmlResult = new stdClass();
+ $responseXml->TestSoapAnyXmlResult->any = '';
+ $responseXml->TestSoapAnyXmlResult->any .= 'value';
+ $responseXml->TestSoapAnyXmlResult->any .= '';
+
+ $regularResponse = new stdClass();
+ $regularResponse->test = 'value';
+
+ return [
+ "xml response inside 'any' parameter" => [
+ 'TestSoapAnyXml',
+ [
+ 'param_name' => 'param_value',
+ 'param_name_1' => 'param_value_1',
+ ],
+ $responseXml,
+ [
+ 'test' => ['nested' => 'value'],
+ ],
+ ],
+ 'regular soap response' => [
+ 'TestRegularSoap',
+ [
+ 'param_name' => 'param_value',
+ 'param_name_1' => 'param_value_1',
+ ],
+ $regularResponse,
+ [
+ 'test' => 'value',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @test
+ */
+ public function testQueryException(): void
+ {
+ $soapClient = $this->getMockBuilder(SoapClient::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $soapClient->method('__soapCall')
+ ->with($this->equalTo('EnumValutes'))
+ ->will($this->throwException(new Exception()));
+
+ $service = new CbrfSoapService($soapClient);
+
+ $this->expectException(CbrfException::class);
+ $service->query('test');
+ }
+}
diff --git a/tests/DataHelperTest.php b/tests/DataHelperTest.php
new file mode 100644
index 0000000..0916e73
--- /dev/null
+++ b/tests/DataHelperTest.php
@@ -0,0 +1,588 @@
+expectException(\get_class($result));
+ }
+
+ $testDateTime = DataHelper::createImmutableDateTime($input);
+
+ if ($result instanceof DateTimeInterface) {
+ $this->assertInstanceOf(DateTimeImmutable::class, $testDateTime);
+ $this->assertSameDate($result, $testDateTime);
+ }
+ }
+
+ public function createImmutableDateTimeProvider(): array
+ {
+ $dateTime = new DateTimeImmutable('-1 hour');
+ $dateTimeTz = new DateTimeImmutable('+1 hour', new DateTimeZone('Asia/ShangHai'));
+
+ return [
+ 'dateTime instance' => [
+ $dateTime,
+ $dateTime,
+ ],
+ 'dateTime instance with time zone' => [
+ $dateTimeTz,
+ $dateTimeTz,
+ ],
+ 'string' => [
+ $dateTime->format(\DATE_ATOM),
+ $dateTime,
+ ],
+ 'exception on incorrect date' => [
+ 'test',
+ new CbrfException(),
+ ],
+ ];
+ }
+
+ /**
+ * @param string $path
+ * @param mixed $input
+ * @param array|Throwable $result
+ *
+ * @test
+ * @dataProvider arrayProvider
+ */
+ public function testArray(string $path, $input, $result): void
+ {
+ if ($result instanceof Throwable) {
+ $this->expectException(\get_class($result));
+ }
+
+ $testArray = DataHelper::array($path, $input);
+
+ if (\is_array($result)) {
+ $this->assertSame($result, $testArray);
+ }
+ }
+
+ public function arrayProvider(): array
+ {
+ $path = 'test1.test2';
+ $result = ['key' => 'value'];
+
+ $object = new stdClass();
+ $object->test1 = new stdClass();
+ $object->test1->test2 = $result;
+
+ $objectMixed = new stdClass();
+ $objectMixed->test1 = [
+ 'test2' => $result,
+ ];
+
+ return [
+ 'search inside array' => [
+ $path,
+ [
+ 'test1' => [
+ 'test2' => $result,
+ ],
+ ],
+ $result,
+ ],
+ 'search inside object' => [
+ $path,
+ $object,
+ $result,
+ ],
+ 'mixed search in array and object' => [
+ $path,
+ $objectMixed,
+ $result,
+ ],
+ 'nothing found' => [
+ $path,
+ [],
+ [],
+ ],
+ 'wrong type exception' => [
+ $path,
+ ['test1' => ['test2' => 'wqe']],
+ new CbrfException(),
+ ],
+ ];
+ }
+
+ /**
+ * @param string $path
+ * @param mixed $input
+ * @param DateTimeInterface|Throwable $result
+ *
+ * @test
+ * @dataProvider dateTimeProvider
+ */
+ public function testDateTime(string $path, $input, $result): void
+ {
+ if ($result instanceof Throwable) {
+ $this->expectException(\get_class($result));
+ }
+
+ $testDateTime = DataHelper::dateTime($path, $input);
+
+ if ($result instanceof DateTimeInterface) {
+ $this->assertInstanceOf(DateTimeImmutable::class, $testDateTime);
+ $this->assertSameDate($result, $testDateTime);
+ }
+ }
+
+ public function dateTimeProvider(): array
+ {
+ $path = 'test1.test2';
+ $result = new DateTimeImmutable();
+ $date = $result->format(\DATE_ATOM);
+
+ $object = new stdClass();
+ $object->test1 = new stdClass();
+ $object->test1->test2 = $date;
+
+ $objectMixed = new stdClass();
+ $objectMixed->test1 = [
+ 'test2' => $date,
+ ];
+
+ return [
+ 'search inside array' => [
+ $path,
+ [
+ 'test1' => [
+ 'test2' => $date,
+ ],
+ ],
+ $result,
+ ],
+ 'search inside object' => [
+ $path,
+ $object,
+ $result,
+ ],
+ 'mixed search in array and object' => [
+ $path,
+ $objectMixed,
+ $result,
+ ],
+ 'serach by not trimmed path' => [
+ " {$path} ",
+ $objectMixed,
+ $result,
+ ],
+ 'not found exception' => [
+ $path,
+ [],
+ new CbrfException(),
+ ],
+ 'empty string exception' => [
+ $path,
+ [
+ 'test1' => [
+ 'test2' => '',
+ ],
+ ],
+ new CbrfException(),
+ ],
+ 'non-string exception' => [
+ $path,
+ [
+ 'test1' => [
+ 'test2' => false,
+ ],
+ ],
+ new CbrfException(),
+ ],
+ ];
+ }
+
+ /**
+ * @param string $path
+ * @param mixed $input
+ * @param string|Throwable $result
+ * @param string|null $default
+ *
+ * @test
+ * @dataProvider stringProvider
+ */
+ public function testString(string $path, $input, $result, ?string $default = null): void
+ {
+ if ($result instanceof Throwable) {
+ $this->expectException(\get_class($result));
+ }
+
+ $testString = DataHelper::string($path, $input, $default);
+
+ if (\is_string($result)) {
+ $this->assertSame($result, $testString);
+ }
+ }
+
+ public function stringProvider(): array
+ {
+ $path = 'test1.test2';
+ $string = ' test ';
+ $result = 'test';
+
+ $object = new stdClass();
+ $object->test1 = new stdClass();
+ $object->test1->test2 = $string;
+
+ $objectMixed = new stdClass();
+ $objectMixed->test1 = [
+ 'test2' => $string,
+ ];
+
+ return [
+ 'search inside array' => [
+ $path,
+ [
+ 'test1' => [
+ 'test2' => $string,
+ ],
+ ],
+ $result,
+ ],
+ 'search inside object' => [
+ $path,
+ $object,
+ $result,
+ ],
+ 'mixed search in array and object' => [
+ $path,
+ $objectMixed,
+ $result,
+ ],
+ 'serach by not trimmed path' => [
+ " {$path} ",
+ $objectMixed,
+ $result,
+ ],
+ 'not found exception' => [
+ $path,
+ [],
+ new CbrfException(),
+ ],
+ 'test default' => [
+ $path,
+ [],
+ $result,
+ $result,
+ ],
+ ];
+ }
+
+ /**
+ * @param string $path
+ * @param mixed $input
+ * @param float|Throwable $result
+ * @param float|null $default
+ *
+ * @test
+ * @dataProvider floatProvider
+ */
+ public function testFloat(string $path, $input, $result, ?float $default = null): void
+ {
+ if ($result instanceof Throwable) {
+ $this->expectException(\get_class($result));
+ }
+
+ $testFloat = DataHelper::float($path, $input, $default);
+
+ if (\is_float($result)) {
+ $this->assertSame($result, $testFloat);
+ }
+ }
+
+ public function floatProvider(): array
+ {
+ $path = 'test1.test2';
+ $result = 12.3;
+ $float = '12.3';
+
+ $object = new stdClass();
+ $object->test1 = new stdClass();
+ $object->test1->test2 = $float;
+
+ $objectMixed = new stdClass();
+ $objectMixed->test1 = [
+ 'test2' => $float,
+ ];
+
+ return [
+ 'search inside array' => [
+ $path,
+ [
+ 'test1' => [
+ 'test2' => $float,
+ ],
+ ],
+ $result,
+ ],
+ 'search inside object' => [
+ $path,
+ $object,
+ $result,
+ ],
+ 'mixed search in array and object' => [
+ $path,
+ $objectMixed,
+ $result,
+ ],
+ 'serach by not trimmed path' => [
+ " {$path} ",
+ $objectMixed,
+ $result,
+ ],
+ 'not found exception' => [
+ $path,
+ [],
+ new CbrfException(),
+ ],
+ 'test default' => [
+ $path,
+ [],
+ $result,
+ $result,
+ ],
+ ];
+ }
+
+ /**
+ * @param string $path
+ * @param mixed $input
+ * @param float|null $result
+ *
+ * @test
+ * @dataProvider floatOrNullProvider
+ */
+ public function testFloatOrNull(string $path, $input, $result): void
+ {
+ $testFloat = DataHelper::floatOrNull($path, $input);
+
+ $this->assertSame($result, $testFloat);
+ }
+
+ public function floatOrNullProvider(): array
+ {
+ $path = 'test1.test2';
+ $result = 12.3;
+ $float = '12.3';
+
+ $object = new stdClass();
+ $object->test1 = new stdClass();
+ $object->test1->test2 = $float;
+
+ $objectMixed = new stdClass();
+ $objectMixed->test1 = [
+ 'test2' => $float,
+ ];
+
+ return [
+ 'search inside array' => [
+ $path,
+ [
+ 'test1' => [
+ 'test2' => $float,
+ ],
+ ],
+ $result,
+ ],
+ 'search inside object' => [
+ $path,
+ $object,
+ $result,
+ ],
+ 'mixed search in array and object' => [
+ $path,
+ $objectMixed,
+ $result,
+ ],
+ 'serach by not trimmed path' => [
+ " {$path} ",
+ $objectMixed,
+ $result,
+ ],
+ 'not found' => [
+ $path,
+ [],
+ null,
+ ],
+ ];
+ }
+
+ /**
+ * @param string $path
+ * @param mixed $input
+ * @param int|Throwable $result
+ * @param int|null $default
+ *
+ * @test
+ * @dataProvider intProvider
+ */
+ public function testInt(string $path, $input, $result, ?int $default = null): void
+ {
+ if ($result instanceof Throwable) {
+ $this->expectException(\get_class($result));
+ }
+
+ $testInt = DataHelper::int($path, $input, $default);
+
+ if (\is_int($result)) {
+ $this->assertSame($result, $testInt);
+ }
+ }
+
+ public function intProvider(): array
+ {
+ $path = 'test1.test2';
+ $result = 12;
+ $int = '12';
+
+ $object = new stdClass();
+ $object->test1 = new stdClass();
+ $object->test1->test2 = $int;
+
+ $objectMixed = new stdClass();
+ $objectMixed->test1 = [
+ 'test2' => $int,
+ ];
+
+ return [
+ 'search inside array' => [
+ $path,
+ [
+ 'test1' => [
+ 'test2' => $int,
+ ],
+ ],
+ $result,
+ ],
+ 'search inside object' => [
+ $path,
+ $object,
+ $result,
+ ],
+ 'mixed search in array and object' => [
+ $path,
+ $objectMixed,
+ $result,
+ ],
+ 'serach by not trimmed path' => [
+ " {$path} ",
+ $objectMixed,
+ $result,
+ ],
+ 'not found exception' => [
+ $path,
+ [],
+ new CbrfException(),
+ ],
+ 'test default' => [
+ $path,
+ [],
+ $result,
+ $result,
+ ],
+ ];
+ }
+
+ /**
+ * @param string $path
+ * @param mixed $input
+ * @param string|Throwable $result
+ * @param string|null $default
+ *
+ * @test
+ * @dataProvider charCodeProvider
+ */
+ public function testCharCode(string $path, $input, $result, ?string $default = null): void
+ {
+ if ($result instanceof Throwable) {
+ $this->expectException(\get_class($result));
+ }
+
+ $testString = DataHelper::charCode($path, $input, $default);
+
+ if (\is_string($result)) {
+ $this->assertSame($result, $testString);
+ }
+ }
+
+ public function charCodeProvider(): array
+ {
+ $path = 'test1.test2';
+ $string = ' TeSt ';
+ $result = 'TEST';
+
+ $object = new stdClass();
+ $object->test1 = new stdClass();
+ $object->test1->test2 = $string;
+
+ $objectMixed = new stdClass();
+ $objectMixed->test1 = [
+ 'test2' => $string,
+ ];
+
+ return [
+ 'search inside array' => [
+ $path,
+ [
+ 'test1' => [
+ 'test2' => $string,
+ ],
+ ],
+ $result,
+ ],
+ 'search inside object' => [
+ $path,
+ $object,
+ $result,
+ ],
+ 'mixed search in array and object' => [
+ $path,
+ $objectMixed,
+ $result,
+ ],
+ 'serach by not trimmed path' => [
+ " {$path} ",
+ $objectMixed,
+ $result,
+ ],
+ 'not found exception' => [
+ $path,
+ [],
+ new CbrfException(),
+ ],
+ 'test default' => [
+ $path,
+ [],
+ $result,
+ $result,
+ ],
+ ];
+ }
+}