florianv/exchanger
Exchanger is a PHP framework for retrieving currency exchange rates from multiple providers (Fixer, Currency Data, Exchange Rates Data, and more). It offers a unified API for live and historical rates, supporting different base/quote currency constraints per service.
composer require florianv/exchanger php-http/curl-client nyholm/psr7
use Exchanger\Service\ApiLayer\Fixer;
use Http\Client\Curl\Client as CurlClient;
$client = new CurlClient();
$service = new Fixer($client, null, ['api_key' => 'YOUR_API_KEY']);
$exchanger = new \Exchanger\Exchanger($service);
EUR/USD rate:
$query = (new \Exchanger\ExchangeRateQueryBuilder('EUR/USD'))->build();
$rate = $exchanger->getExchangeRate($query);
echo $rate->getValue(); // Output: e.g., 1.1159
Where to Look First:
Fetching Rates:
// Latest rate
$query = (new ExchangeRateQueryBuilder('USD/JPY'))->build();
$rate = $exchanger->getExchangeRate($query);
// Historical rate (if supported)
$query = (new ExchangeRateQueryBuilder('EUR/GBP'))
->setDate(new \DateTime('2023-01-01'))
->build();
Chaining Services (Fallback Logic):
use Exchanger\Service\Chain;
$chain = new Chain([
new Fixer($client, null, ['api_key' => 'FIXER_KEY']),
new CurrencyData($client, null, ['api_key' => 'CURRENCY_DATA_KEY']),
]);
$exchanger = new Exchanger($chain);
Caching Integration (PSR-16):
use Cache\Adapter\Filesystem\FilesystemCachePool;
use Cache\Bridge\SimpleCache\SimpleCacheBridge;
$cache = new SimpleCacheBridge(new FilesystemCachePool());
$exchanger = new Exchanger($service, $cache, ['cache_ttl' => 3600]);
Service Provider Binding:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton(Exchanger::class, function ($app) {
$client = new CurlClient();
$service = new Fixer($client, null, ['api_key' => config('services.fixer.key')]);
return new Exchanger($service, $app->make('cache.store'));
});
}
Config File (config/exchanger.php):
return [
'default_service' => 'fixer',
'services' => [
'fixer' => [
'api_key' => env('FIXER_API_KEY'),
'base_currency' => 'EUR',
],
],
'cache' => [
'enabled' => true,
'ttl' => 3600,
],
];
Facade Usage:
// Create ExchangerFacade.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class ExchangerFacade extends Facade
{
protected static function getFacadeAccessor() { return 'exchanger'; }
}
Register in config/app.php under aliases.
Eloquent Model Observers (Auto-update rates):
// app/Observers/OrderObserver.php
use Exchanger\Facades\Exchanger;
class OrderObserver
{
public function saved(Order $order)
{
if ($order->currency !== config('app.default_currency')) {
$rate = Exchanger::getExchangeRate(
(new ExchangeRateQueryBuilder(config('app.default_currency') . '/' . $order->currency))->build()
);
$order->amount_in_default_currency = $order->amount * $rate->getValue();
$order->save();
}
}
}
API Response Formatting:
// app/Http/Controllers/CurrencyController.php
use Exchanger\Facades\Exchanger;
class CurrencyController extends Controller
{
public function getRates(Request $request)
{
$query = (new ExchangeRateQueryBuilder($request->currency_pair))->build();
$rate = Exchanger::getExchangeRate($query);
return response()->json([
'rate' => $rate->getValue(),
'date' => $rate->getDate()->format('Y-m-d'),
'provider' => $rate->getProviderName(),
]);
}
}
Command for Bulk Rate Updates:
// app/Console/Commands/UpdateExchangeRates.php
use Exchanger\Facades\Exchanger;
use Illuminate\Console\Command;
class UpdateExchangeRates extends Command
{
protected $signature = 'exchange:update {--currencies=*}';
protected $description = 'Update exchange rates for specified currencies';
public function handle()
{
$currencies = $this->option('currencies') ?: ['USD', 'EUR', 'GBP'];
foreach ($currencies as $currency) {
$query = (new ExchangeRateQueryBuilder('USD/' . $currency))->build();
$rate = Exchanger::getExchangeRate($query);
Cache::put("exchange_rate_{$currency}", $rate->getValue(), 3600);
}
}
}
API Key Management:
.env and config/services.php.$client = new CurlClient(null, null, [
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
Rate Limiting:
cache_ttl per-query to balance freshness and rate limits:
$query->addOption('cache_ttl', 1800); // 30-minute cache
Currency Pair Validity:
$chain = new Chain([$service1, $service2, $service3]);
if ($service->supportsHistorical()) {
$query->setDate($date);
}
Caching Quirks:
cache_key_prefix:
// Bad: 'myapp-long-prefix-' (exceeds 24 chars)
// Good: 'app-'
$query->addOption('cache_key_prefix', 'app-');
cache/predis-adapter for Redis caching with TTL:
$cache = new SimpleCacheBridge(new PredisCachePool(new \Predis\Client('redis://localhost')));
HTTPlug Configuration:
Accept: application/json). Configure the client:
$client = new CurlClient(null, [
'headers' => ['Accept' => 'application/json'],
]);
Timezone Handling:
$query->setDate((new \DateTime('2023-01-01', new \DateTimeZone('UTC'))));
Error Handling:
null or throw exceptions. Wrap calls:
try {
$rate = $exchanger->getExchangeRate($query);
} catch (\Exception $e) {
Log::error("Exchange rate failed: {$e->getMessage()}");
return response()->json(['error' => 'Service unavailable'], 503);
}
How can I help you explore Laravel packages today?