diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..f9814c5 --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,17 @@ +setRules(array( + '@Symfony' => true, + '@Symfony:risky' => true, + 'array_syntax' => array('syntax' => 'short'), + 'no_unreachable_default_argument_value' => false, + 'heredoc_to_nowdoc' => false, + 'phpdoc_annotation_without_dot' => false, + )) + ->setRiskyAllowed(true) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') + ) +; diff --git a/.travis.yml b/.travis.yml index 1621da3..384db7e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ php: - 5.6 - 7.0 - 7.1 - - hhvm sudo: false diff --git a/README.md b/README.md index 1f18b48..64b5562 100755 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Here is the list of the currently implemented services. | [Central Bank of the Czech Republic](http://www.cnb.cz) | * | CZK | No | | [Russian Central Bank](http://http://www.cbr.ru) | * | RUB | Yes | | [currencylayer](https://currencylayer.com) | USD (free), * (paid) | * | Yes | +| Array | * | * | Yes | ## Credits diff --git a/doc/readme.md b/doc/readme.md index 006f6c9..8bcdea7 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -279,7 +279,20 @@ $service = new Chain([ new Google(), new NationalBankOfRomania(), new OpenExchangeRates($client, null, ['app_id' => 'app_id', 'enterprise' => false]), - new PhpArray(['EUR/USD' => new ExchangeRate('1.5')]), + new PhpArray( + [ + 'EUR/USD' => new ExchangeRate('1.1'), + 'EUR/GBP' => 1.5 + ], + [ + '2017-01-01' => [ + 'EUR/USD' => new ExchangeRate('1.5') + ], + '2017-01-03' => [ + 'EUR/GBP' => 1.3 + ], + ] + ), new WebserviceX(), new Xignite($client, null, ['token' => 'token']), new Yahoo() diff --git a/src/Exception/ChainException.php b/src/Exception/ChainException.php index 09421fe..2e92d83 100755 --- a/src/Exception/ChainException.php +++ b/src/Exception/ChainException.php @@ -27,7 +27,7 @@ class ChainException extends Exception */ public function __construct(array $exceptions) { - $messages = array_map(function(\Exception $exception) { + $messages = array_map(function (\Exception $exception) { return sprintf( '%s: %s', get_class($exception), diff --git a/src/Service/HistoricalService.php b/src/Service/HistoricalService.php index 7a04de4..53026ac 100755 --- a/src/Service/HistoricalService.php +++ b/src/Service/HistoricalService.php @@ -17,7 +17,7 @@ use Exchanger\Exception\UnsupportedCurrencyPairException; /** - * Base class for historical services. + * Base class for historical http based services. * * @author Florian Voutzinos */ diff --git a/src/Service/PhpArray.php b/src/Service/PhpArray.php index 6c48505..1cd4d67 100755 --- a/src/Service/PhpArray.php +++ b/src/Service/PhpArray.php @@ -13,9 +13,10 @@ use Exchanger\Contract\ExchangeRateQuery; use Exchanger\Contract\ExchangeRateService; -use Exchanger\Contract\HistoricalExchangeRateQuery; use Exchanger\Exception\InternalException; +use Exchanger\Exception\UnsupportedCurrencyPairException; use Exchanger\ExchangeRate; +use Exchanger\HistoricalExchangeRateQuery; /** * Service that retrieves rates from an array. @@ -25,20 +26,29 @@ final class PhpArray implements ExchangeRateService { /** - * The rates. + * The latest rates. * - * @var ExchangeRate[] + * @var ExchangeRate[]|string[] */ - private $rates; + private $latestRates; + + /** + * The historical rates. + * + * @var ExchangeRate[][]|string[][] + */ + private $historicalRates; /** * Constructor. * - * @param ExchangeRate[]|string[] $rates An array of rates indexed by the corresponding currency pair symbol + * @param ExchangeRate[]|string[] $latestRates An array of rates indexed by the corresponding currency pair symbol + * @param ExchangeRate[][]|string[][] $historicalRates An array of rates indexed by the date in Y-m-d format */ - public function __construct(array $rates) + public function __construct(array $latestRates, array $historicalRates = []) { - $this->rates = $rates; + $this->latestRates = $latestRates; + $this->historicalRates = $historicalRates; } /** @@ -47,18 +57,47 @@ public function __construct(array $rates) public function getExchangeRate(ExchangeRateQuery $exchangeQuery) { $currencyPair = $exchangeQuery->getCurrencyPair(); - $rate = $this->rates[$currencyPair->__toString()]; - if (is_scalar($rate)) { - $rate = new ExchangeRate($rate); - } elseif (!$rate instanceof ExchangeRate) { - throw new InternalException(sprintf( - 'Rates passed to the PhpArray service must be Rate instances or scalars "%s" given.', - gettype($rate) - )); + if ($exchangeQuery instanceof HistoricalExchangeRateQuery) { + if ($rate = $this->getHistoricalExchangeRate($exchangeQuery)) { + return $rate; + } + } elseif ($rate = $this->getLatestExchangeRate($exchangeQuery)) { + return $rate; } - return $rate; + throw new UnsupportedCurrencyPairException($currencyPair, $this); + } + + /** + * Gets the latest rate. + * + * @param ExchangeRateQuery $exchangeQuery + * + * @return ExchangeRate|null + * + * @throws InternalException + */ + private function getLatestExchangeRate(ExchangeRateQuery $exchangeQuery) + { + $currencyPair = $exchangeQuery->getCurrencyPair(); + + return $this->processRateValue($this->latestRates[(string) $currencyPair]); + } + + /** + * Gets an historical rate. + * + * @param HistoricalExchangeRateQuery $exchangeQuery + * + * @return ExchangeRate|null + */ + private function getHistoricalExchangeRate(HistoricalExchangeRateQuery $exchangeQuery) + { + $date = $exchangeQuery->getDate(); + $currencyPair = $exchangeQuery->getCurrencyPair(); + + return $this->processRateValue($this->historicalRates[$date->format('Y-m-d')][(string) $currencyPair]); } /** @@ -68,7 +107,35 @@ public function supportQuery(ExchangeRateQuery $exchangeQuery) { $currencyPair = $exchangeQuery->getCurrencyPair(); - return !$exchangeQuery instanceof HistoricalExchangeRateQuery - && isset($this->rates[$currencyPair->__toString()]); + if ($exchangeQuery instanceof HistoricalExchangeRateQuery) { + $date = $exchangeQuery->getDate(); + + return isset($this->historicalRates[$date->format('Y-m-d')][(string) $currencyPair]); + } + + return isset($this->latestRates[(string) $currencyPair]); + } + + /** + * Processes the rate value. + * + * @param mixed $rate + * + * @return ExchangeRate + * + * @throws InternalException + */ + private function processRateValue($rate) + { + if (is_scalar($rate)) { + $rate = new ExchangeRate($rate); + } elseif (!$rate instanceof ExchangeRate) { + throw new InternalException(sprintf( + 'Rates passed to the PhpArray service must be Rate instances or scalars "%s" given.', + gettype($rate) + )); + } + + return $rate; } } diff --git a/src/Service/RussianCentralBank.php b/src/Service/RussianCentralBank.php index fcd161f..04de636 100644 --- a/src/Service/RussianCentralBank.php +++ b/src/Service/RussianCentralBank.php @@ -7,12 +7,10 @@ use Exchanger\Exception\UnsupportedCurrencyPairException; use Exchanger\Exception\UnsupportedDateException; use Exchanger\ExchangeRate; -use Exchanger\Service\HistoricalService; use Exchanger\StringUtil; /** * Russian Central Bank Service. - * */ class RussianCentralBank extends HistoricalService { @@ -48,7 +46,7 @@ protected function getHistoricalExchangeRate(HistoricalExchangeRateQuery $exchan $baseCurrency = $exchangeQuery->getCurrencyPair()->getBaseCurrency(); $formattedDate = $exchangeQuery->getDate()->format('d.m.Y'); - $content = $this->request(self::URL . '?' . http_build_query(['date_req' => $formattedDate])); + $content = $this->request(self::URL.'?'.http_build_query(['date_req' => $formattedDate])); $element = StringUtil::xmlToElement($content); $elements = $element->xpath('./Valute[CharCode="'.$baseCurrency.'"]'); diff --git a/src/Service/Service.php b/src/Service/Service.php index 1292289..39ec857 100755 --- a/src/Service/Service.php +++ b/src/Service/Service.php @@ -18,7 +18,7 @@ use Exchanger\Contract\ExchangeRateService; /** - * Base class for services. + * Base class for http based services. * * @author Florian Voutzinos */ @@ -74,7 +74,7 @@ public function processOptions(array &$options) * Fetches the content of the given url. * * @param string $url - * @param array $headers + * @param array $headers * * @return string */ diff --git a/tests/Tests/Exception/ChainExceptionTest.php b/tests/Tests/Exception/ChainExceptionTest.php index 7ac429e..e325e99 100644 --- a/tests/Tests/Exception/ChainExceptionTest.php +++ b/tests/Tests/Exception/ChainExceptionTest.php @@ -30,10 +30,10 @@ public function setUp() $this->exception1 = new InternalException('Something bad happened.'); $this->exception2 = new \Exception('General exception.'); - $this->chainException = new ChainException(array( + $this->chainException = new ChainException([ $this->exception1, $this->exception2, - )); + ]); } /** diff --git a/tests/Tests/Service/PhpArrayTest.php b/tests/Tests/Service/PhpArrayTest.php index 1ee2d1f..8868ff1 100644 --- a/tests/Tests/Service/PhpArrayTest.php +++ b/tests/Tests/Service/PhpArrayTest.php @@ -14,6 +14,7 @@ use Exchanger\ExchangeRate; use Exchanger\ExchangeRateQuery; use Exchanger\CurrencyPair; +use Exchanger\HistoricalExchangeRateQuery; use Exchanger\Service\PhpArray; class PhpArrayTest extends \PHPUnit_Framework_TestCase @@ -21,10 +22,37 @@ class PhpArrayTest extends \PHPUnit_Framework_TestCase /** * @test */ - public function it_does_not_support_all_queries() + public function it_supports_latest_queries() { $service = new PhpArray([]); $this->assertFalse($service->supportQuery(new ExchangeRateQuery(CurrencyPair::createFromString('EUR/USD')))); + + $service = new PhpArray(['EUR/USD' => 1, 'EUR/GBP' => new ExchangeRate(2)]); + $this->assertTrue($service->supportQuery(new ExchangeRateQuery(CurrencyPair::createFromString('EUR/USD')))); + $this->assertTrue($service->supportQuery(new ExchangeRateQuery(CurrencyPair::createFromString('EUR/GBP')))); + $this->assertFalse($service->supportQuery(new ExchangeRateQuery(CurrencyPair::createFromString('USD/GBP')))); + } + + /** + * @test + */ + public function it_supports_historical_queries() + { + $now = new \DateTimeImmutable(); + + $service = new PhpArray([], []); + $this->assertFalse($service->supportQuery(new HistoricalExchangeRateQuery(CurrencyPair::createFromString('USD/EUR'), $now))); + + $service = new PhpArray([], [ + $now->format('Y-m-d') => [ + 'EUR/USD' => 1, + 'EUR/GBP' => new ExchangeRate('2.0'), + ], + ]); + + $this->assertTrue($service->supportQuery(new HistoricalExchangeRateQuery(CurrencyPair::createFromString('EUR/USD'), $now))); + $this->assertTrue($service->supportQuery(new HistoricalExchangeRateQuery(CurrencyPair::createFromString('EUR/GBP'), $now))); + $this->assertFalse($service->supportQuery(new HistoricalExchangeRateQuery(CurrencyPair::createFromString('USD/GBP'), $now))); } /** @@ -32,7 +60,7 @@ public function it_does_not_support_all_queries() * @expectedException \Exchanger\Exception\InternalException * @expectedExceptionMessage Rates passed to the PhpArray service must be Rate instances or scalars "array" given. */ - public function it_throws_an_exception_when_fetching_invalid_rate() + public function it_throws_an_exception_when_fetching_latest_invalid_rate() { $arrayProvider = new PhpArray([ 'EUR/USD' => [], @@ -44,7 +72,7 @@ public function it_throws_an_exception_when_fetching_invalid_rate() /** * @test */ - public function it_fetches_a_rate_from_rates() + public function it_fetches_a_latest_rate_from_rates() { $arrayProvider = new PhpArray([ 'EUR/USD' => $rate = new ExchangeRate('1.50'), @@ -56,7 +84,7 @@ public function it_fetches_a_rate_from_rates() /** * @test */ - public function it_fetches_a_rate_from_scalars() + public function it_fetches_a_latest_rate_from_scalars() { $arrayProvider = new PhpArray([ 'EUR/USD' => 1.50, @@ -72,4 +100,62 @@ public function it_fetches_a_rate_from_scalars() $this->assertEquals('1.25', $usdGbp->getValue()); $this->assertEquals('1', $jpyGbp->getValue()); } + + /** + * @test + * @expectedException \Exchanger\Exception\InternalException + * @expectedExceptionMessage Rates passed to the PhpArray service must be Rate instances or scalars "array" given. + */ + public function it_throws_an_exception_when_fetching_historical_invalid_rate() + { + $now = new \DateTimeImmutable(); + + $arrayProvider = new PhpArray([], [ + $now->format('Y-m-d') => [ + 'EUR/USD' => [], + ], + ]); + + $arrayProvider->getExchangeRate(new HistoricalExchangeRateQuery(CurrencyPair::createFromString('EUR/USD'), $now)); + } + + /** + * @test + */ + public function it_fetches_a_historical_rate_from_exchange_rates() + { + $now = new \DateTimeImmutable(); + + $arrayProvider = new PhpArray([], [ + $now->format('Y-m-d') => [ + 'EUR/USD' => $rate = new ExchangeRate('1.50'), + ], + ]); + + $this->assertSame($rate, $arrayProvider->getExchangeRate(new HistoricalExchangeRateQuery(CurrencyPair::createFromString('EUR/USD'), $now))); + } + + /** + * @test + */ + public function it_fetches_a_historical_rate_from_scalars() + { + $now = new \DateTimeImmutable(); + + $arrayProvider = new PhpArray([], [ + $now->format('Y-m-d') => [ + 'EUR/USD' => 1.50, + 'USD/GBP' => '1.25', + 'JPY/GBP' => 1, + ], + ]); + + $eurUsd = $arrayProvider->getExchangeRate(new HistoricalExchangeRateQuery(CurrencyPair::createFromString('EUR/USD'), $now)); + $usdGbp = $arrayProvider->getExchangeRate(new HistoricalExchangeRateQuery(CurrencyPair::createFromString('USD/GBP'), $now)); + $jpyGbp = $arrayProvider->getExchangeRate(new HistoricalExchangeRateQuery(CurrencyPair::createFromString('JPY/GBP'), $now)); + + $this->assertEquals('1.50', $eurUsd->getValue()); + $this->assertEquals('1.25', $usdGbp->getValue()); + $this->assertEquals('1', $jpyGbp->getValue()); + } }