florianv/swap
PHP 8.2+ currency exchange rate library with a single API over 30+ providers. Supports conversion, historical rates, PSR-16 caching, and provider fallback. Works with PSR-18 HTTP clients and PSR-17 factories for flexible integrations.
Installation:
composer require florianv/swap symfony/http-client nyholm/psr7
(Alternative: Use php-http/guzzle7-adapter if preferring Guzzle.)
Basic Setup:
use Swap\Builder;
$swap = (new Builder())
->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')])
->build();
First Use Case: Fetch a rate and convert an amount:
$rate = $swap->latest('EUR/USD');
$convertedAmount = 100.00 * $rate->getValue(); // 100 EUR → USD
doc/readme.md: Deep dive into caching, HTTP clients, and provider configs.src/Swap.php: Core API methods (latest(), historical(), etc.).Provider Chaining (Fallback Logic):
$swap = (new Builder())
->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')]) // Primary
->add('european_central_bank') // Fallback for EUR pairs
->build();
Historical Rates:
$rate = $swap->historical('USD/JPY', new \DateTime('2023-01-01'));
Caching Integration:
use Symfony\Contracts\Cache\SimpleCacheInterface;
$cache = new \Symfony\Cache\Simple\FilesystemCache();
$swap = (new Builder())
->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')])
->useCache($cache)
->build();
Service Provider Binding:
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton(Swap::class, function ($app) {
return (new Builder())
->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')])
->useCache($app->make(\Symfony\Contracts\Cache\CacheInterface::class))
->build();
});
}
Facade (Optional):
Create a facade (app/Facades/SwapFacade.php) to wrap Swap::latest() calls:
public static function rate(string $pair): float
{
return app(Swap::class)->latest($pair)->getValue();
}
Job Queues for Heavy Conversions:
// Process bulk conversions asynchronously
RateConversionJob::dispatch($amount, $fromCurrency, $toCurrency);
MoneyPHP Integration:
Use SwapExchange for type-safe conversions:
use Money\Money;
use Swap\Money\SwapExchange;
$exchange = new SwapExchange($swap);
$money = new Money(10000, 'EUR');
$converted = $exchange->convert($money, 'USD');
Environment-Based Config:
Load providers dynamically from .env:
$builder = new Builder();
if (env('USE_FASTFOREX')) {
$builder->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')]);
}
$builder->add('european_central_bank');
Rate Limiting:
Implement middleware to throttle API calls (e.g., using GuzzleHttp\Middleware).
Provider-Specific Quirks:
USD/JPY) will fail silently.coin_layer) require explicit crypto pair formatting (e.g., BTC/USD).Caching Caveats:
null (no expiry). Set explicitly:
->useCache($cache)->withDefaultTTL(3600) // 1-hour expiry
if ($rate->getDate()->diff(new \DateTime())->days > 1) {
throw new \RuntimeException('Rate too stale');
}
Error Handling:
try {
$rate = $swap->latest('EUR/XYZ'); // Unsupported currency
} catch (ChainException $e) {
Log::error('Currency conversion failed', ['errors' => $e->getErrors()]);
throw new \InvalidArgumentException('Unsupported currency pair');
}
webservicex) may return null for unsupported pairs. Check:
if ($rate === null) {
throw new \RuntimeException('Unsupported currency pair');
}
API Key Management:
.env or a secrets manager.ProviderKeyResolver interface to dynamically fetch keys from a secure source.Log Provider Responses: Enable debug logging for HTTP requests:
$client = \Symfony\Contracts\HttpClient\HttpClientInterface::create([
'debug' => true,
]);
$builder->useHttpClient($client);
Inspect Rate Objects: Dump the full rate object to debug:
dd($rate->getValue(), $rate->getDate(), $rate->getProviderName(), $rate->getMetadata());
Test Fallback Logic: Mock providers to test fallback behavior:
$builder->addExchangeRateService(new MockFailingProvider());
$builder->add('european_central_bank'); // Should trigger on failure
Custom Providers:
Implement Exchanger\Contract\ExchangeRateService:
class MyCustomProvider implements ExchangeRateService
{
public function latest(string $pair): ?Rate
{
// Fetch from your custom source
return new Rate(1.2, new \DateTime(), 'my_custom_provider');
}
}
Register with:
$builder->addExchangeRateService(new MyCustomProvider());
Override HTTP Client: Use a custom client for retries or auth:
$client = \Http\Adapter\Guzzle\Guzzle18::createWithConfig([
'timeout' => 10.0,
'headers' => ['Authorization' => 'Bearer ' . env('API_TOKEN')],
]);
$builder->useHttpClient($client);
Modify Rate Processing:
Extend Swap to add pre/post-processing:
class CustomSwap extends Swap
{
public function latest(string $pair): Rate
{
$rate = parent::latest($pair);
// Apply business logic (e.g., rounding, fees)
return new Rate(round($rate->getValue(), 4), $rate->getDate(), $rate->getProviderName());
}
}
Batch Requests:
Use Swap::latestMultiple() to fetch multiple rates in one call (supported by some providers):
$rates = $swap->latestMultiple(['EUR/USD', 'GBP/JPY']);
Cache Invalidation: Invalidate cache manually when rates change (e.g., after a manual override):
$cache->delete('swap:latest:EUR/USD');
Provider Selection:
fastforex or apilayer_currency_data (paid tiers).european_central_bank (free, but EUR-only).coin_layer or cryptonator.How can I help you explore Laravel packages today?