florianv/exchanger
Exchange rate provider layer for PHP: 30+ rate services behind one ExchangeRateService interface. Supports historical rates, chain fallback between providers, and PSR-16 caching. Use when you need more control than higher-level libraries.
Installation:
composer require florianv/exchanger symfony/http-client nyholm/psr7
(Note: symfony/http-client and nyholm/psr7 are required for HTTP operations.)
Basic Usage:
use Exchanger\Exchanger;
use Exchanger\ExchangeRateQueryBuilder;
use Exchanger\Service\FastForex;
$service = new FastForex(['api_key' => env('FASTFOREX_API_KEY')]);
$exchanger = new Exchanger($service);
$rate = $exchanger->getExchangeRate(
(new ExchangeRateQueryBuilder('EUR/USD'))->build()
);
$convertedAmount = 100 * $rate->getValue(); // 100 EUR → USD
Fallback Chain (Recommended for Production):
use Exchanger\Service\Chain;
use Exchanger\Service\EuropeanCentralBank;
$service = new Chain([
new FastForex(['api_key' => env('FASTFOREX_API_KEY')]),
new EuropeanCentralBank(), // Free fallback for EUR-base pairs
]);
symfony/cache).Chain to prioritize providers (e.g., paid → free fallback).
$chain = new Chain([
new FastForex(['api_key' => '...']),
new EuropeanCentralBank(),
]);
$primary = $currencyPair->startsWith('EUR') ? new EuropeanCentralBank() : new FastForex(['api_key' => '...']);
$exchanger = new Exchanger(new Chain([$primary, new EuropeanCentralBank()]));
$query = (new ExchangeRateQueryBuilder('USD/JPY'))
->setDate(new DateTime('2023-01-01'))
->build();
FastForex supports symbols):
$query->setOptions(['symbols' => 'USD,EUR']);
use Symfony\Contracts\Cache\CacheInterface;
$cache = new Symfony\Cache\Psr16Cache(new Symfony\Cache\Adapter\FilesystemAdapter());
$exchanger = new Exchanger($service, $cache);
ExchangeRateQueryBuilder's getCacheKey() for granular control.laravel-swap for bindings.symfony-swap.class CurrencyFacade {
public function convert(float $amount, string $from, string $to): float {
$rate = app(Exchanger::class)->getExchangeRate(
(new ExchangeRateQueryBuilder("$from/$to"))->build()
);
return $amount * $rate->getValue();
}
}
ChainException).try {
$rate = $exchanger->getExchangeRate($query);
} catch (ChainException $e) {
$rate = $exchanger->getExchangeRate($query->withCacheOnly(true));
}
API Key Management:
fastFOREX) require keys; others (e.g., EuropeanCentralBank) are free but limited (e.g., EUR-base only).Rate Precision:
$rate->getValue() to float to avoid string comparisons (e.g., "1.08" vs. 1.0800).$rateValue = (float) $rate->getValue();
Historical Data Gaps:
EuropeanCentralBank) update daily at fixed times. Historical queries may return null if the date isn’t published yet.fastFOREX) offer more granularity (e.g., hourly rates).Currency Pair Validation:
USD/TRY vs. EUR/USD).ExchangeRateQueryBuilder::validate() to check support before querying.Caching Quirks:
getCacheKey() if using the same query for different contexts (e.g., "live rate" vs. "historical").Log Provider Responses:
$service = new FastForex(['api_key' => '...']);
$service->setHttpClient(new MiddlewareStack(
new HttpClient(),
[new LogMiddleware()]
));
Exchanger\Service\HttpService::setDebug(true) for verbose output.Isolate Service Failures:
$fastForex = new FastForex(['api_key' => '...']);
$rate = $fastForex->getExchangeRate($query); // Bypass chain
Rate Limiting:
use Symfony\Component\HttpClient\RetryStrategy;
$client = new HttpClient([
'retry' => RetryStrategy::fromOptions([
'max_retries' => 3,
'delay' => 1000,
]),
]);
Custom Providers:
Exchanger\Contract\ExchangeRateService:
class MyCustomProvider implements ExchangeRateService {
public function getExchangeRate(ExchangeRateQuery $query): ExchangeRate {
// Fetch from your custom source (e.g., database, internal API).
return new ExchangeRate(1.23, new DateTime(), 'my_provider');
}
}
Registry::addService().Middleware:
$service = new FastForex(['api_key' => '...']);
$service->setHttpClient(new MiddlewareStack(
new HttpClient(),
[new AuthMiddleware()]
));
Query Modifiers:
ExchangeRateQueryBuilder to add custom options:
class ExtendedQueryBuilder extends ExchangeRateQueryBuilder {
public function setCustomOption(string $key, $value): self {
$this->options[$key] = $value;
return $this;
}
}
Rate Objects:
ExchangeRate to add metadata (e.g., getSourceUrl()):
class ExtendedRate extends ExchangeRate {
public function __construct(float $value, DateTimeInterface $date, string $provider, private string $sourceUrl) {
parent::__construct($value, $date, $provider);
}
public function getSourceUrl(): string { return $this->sourceUrl; }
}
Batch Requests:
ExchangeRateQueryBuilder::setBatch() to fetch multiple pairs in one call (supported by some providers like fastFOREX):
$query = (new ExchangeRateQueryBuilder('USD/EUR,GBP/JPY'))
->setBatch(true)
->build();
Cache Warming:
$exchanger->getExchangeRate($query, true); // Force refresh
Provider Selection:
How can I help you explore Laravel packages today?