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
Ensure your Laravel project uses PHP 8.2+.
Basic Setup:
use Swap\Builder;
$swap = (new Builder())
->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')])
->build();
First Use Case: Fetch and use the latest EUR/USD rate:
$rate = $swap->latest('EUR/USD');
$amountInUSD = 100.00 * $rate->getValue();
Fallback Setup (for production):
$swap = (new Builder())
->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')])
->add('european_central_bank') // Free fallback for EUR pairs
->build();
Swap\Builder class: Core configuration and provider registration.Swap\Swap class: Main API for fetching rates.Rate Retrieval:
// Latest rate
$rate = $swap->latest('USD/JPY');
// Historical rate (e.g., 2023-01-01)
$rate = $swap->historical('USD/JPY', '2023-01-01');
Amount Conversion:
$amount = 100.00;
$converted = $amount * $swap->latest('EUR/USD')->getValue();
Multi-Currency Pricing (e.g., in a Laravel e-commerce app):
$prices = collect($products)->map(function ($product) {
$rate = $swap->latest($product->currency . '/USD');
return $product->price * $rate->getValue();
});
Caching Integration:
$swap = (new Builder())
->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')])
->useCache(new \Symfony\Component\Cache\Adapter\FilesystemAdapter())
->build();
Laravel Service Provider:
Bind Swap\Swap to the container in AppServiceProvider:
public function register()
{
$this->app->singleton(Swap::class, function ($app) {
return (new Builder())
->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')])
->useCache($app['cache'])
->build();
});
}
Dependency Injection:
Inject Swap\Swap into controllers/services:
public function __construct(private Swap $swap) {}
Rate Validation:
Validate rates before conversion (e.g., check getValue() is not null or 0):
$rate = $swap->latest('EUR/USD');
if ($rate->getValue() === null) {
throw new \RuntimeException("Failed to fetch EUR/USD rate");
}
Fallback Logic: Use the fallback chain for critical paths (e.g., payment processing):
try {
$rate = $swap->latest('USD/GBP');
} catch (\Swap\Exception\ChainException $e) {
// Log errors and notify admins
\Log::error('All FX providers failed', ['errors' => $e->getErrors()]);
throw new \RuntimeException("Currency conversion unavailable");
}
Historical Data: Fetch historical rates for audits/reconciliation:
$historicalRate = $swap->historical('EUR/USD', '2023-12-31');
Dynamic Provider Selection: Use environment variables to switch providers:
$provider = env('FX_PROVIDER', 'fastforex');
$swap = (new Builder())
->add($provider, ['api_key' => env('FX_API_KEY')])
->build();
Rate Monitoring: Log provider performance (e.g., response times) for observability:
$start = microtime(true);
$rate = $swap->latest('USD/JPY');
\Log::debug('FX rate fetch time', [
'pair' => 'USD/JPY',
'time_ms' => (microtime(true) - $start) * 1000,
'provider' => $rate->getProviderName()
]);
Custom Providers:
Extend functionality by implementing Exchanger\Contract\ExchangeRateService:
use Exchanger\Contract\ExchangeRateService;
class CustomProvider implements ExchangeRateService {
public function getLatest(string $base, string $quote): ?float { /* ... */ }
public function getHistorical(string $base, string $quote, string $date): ?float { /* ... */ }
}
$swap = (new Builder())
->addExchangeRateService(new CustomProvider())
->build();
API Key Management:
.env and env() helper:
->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')])
Rate Availability:
european_central_bank only supports EUR-base pairs).fastforex or apilayer_currency_data for broad coverage.Caching Quirks:
Fallback Behavior:
null values.$rate = $swap->latest('USD/XYZ'); // XYZ may not be supported
if ($rate === null) {
throw new \InvalidArgumentException("Unsupported currency pair: USD/XYZ");
}
Rate Precision:
bcmath or gmp for high-precision calculations:
$amountInUSD = bcdiv($amountInEUR, $rate->getValue(), 6);
decimal(19,6)).Rate Freshness:
european_central_bank) update less frequently (daily vs. real-time).->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')])
->add('european_central_bank') // Fallback for EUR pairs
HTTP Client Conflicts:
symfony/http-client and Guzzle may cause issues if not configured properly.$client = new \GuzzleHttp\Client();
$swap = (new Builder())
->useHttpClient($client)
->add('fastforex', ['api_key' => env('FASTFOREX_API_KEY')])
->build();
Enable Debug Logging: Configure Monolog to log Swap requests/responses:
\Log::debug('FX request', [
'pair' => 'EUR/USD',
'provider' => 'fastforex',
'response' => $rate->getRawData() // If available
]);
Inspect Provider Errors:
Catch ChainException to debug fallback failures:
try {
$rate = $swap->latest('USD/JPY');
} catch (\Swap\Exception\ChainException $e
How can I help you explore Laravel packages today?