florianv/swap
Retrieve live currency exchange rates from multiple providers (Fixer, currencylayer, exchangeratesapi, etc.) with optional caching and fallbacks. PSR-friendly and integrates with MoneyPHP; also available via Symfony bundle and Laravel package.
Install Dependencies:
composer require florianv/swap php-http/curl-client nyholm/psr7 php-http/message
Basic Configuration (in a service provider or bootstrap file):
use Swap\Builder;
$swap = (new Builder())
->add('apilayer_fixer', ['api_key' => env('FIXER_API_KEY')])
->add('apilayer_currency_data', ['api_key' => env('CURRENCYLAYER_API_KEY')])
->build();
First Use Case:
// Register the swap instance in Laravel's service container
$this->app->singleton('swap', fn() => $swap);
// Retrieve latest EUR/USD rate
$rate = app('swap')->latest('EUR/USD');
$value = $rate->getValue(); // e.g., 1.129
Service Chaining: Configure a fallback chain for reliability (e.g., Fixer → CurrencyLayer → ExchangeRatesAPI).
$swap = (new Builder())
->add('apilayer_fixer', ['api_key' => env('FIXER_API_KEY')])
->add('apilayer_currency_data', ['api_key' => env('CURRENCYLAYER_API_KEY')])
->build();
Rate Retrieval:
// Latest rate
$rate = $swap->latest('USD/EUR');
// Historical rate (e.g., 15 days ago)
$date = (new \DateTime())->modify('-15 days');
$historicalRate = $swap->historical('USD/EUR', $date);
Provider Awareness: Track which service provided the rate (useful for debugging or logging):
$provider = $rate->getProviderName(); // e.g., 'apilayer_fixer'
Laravel Service Provider:
Bind the Swap instance to the container for easy access:
public function register()
{
$this->app->singleton('swap', fn() => (new Builder())
->add('apilayer_fixer', ['api_key' => env('FIXER_API_KEY')])
->build()
);
}
Caching Rates (Redis example):
use Cache\Adapter\Predis\PredisCachePool;
use Cache\Bridge\SimpleCache\SimpleCacheBridge;
$client = new \Predis\Client(env('REDIS_URL'));
$cache = new SimpleCacheBridge(new PredisCachePool($client));
$swap = (new Builder(['cache_ttl' => 3600]))
->useSimpleCache($cache)
->build();
HTTP Request Caching (for bulk requests): Cache API responses to avoid redundant calls (e.g., for multiple rates with the same base currency):
use Http\Client\Common\PluginClient;
use Http\Client\Common\Plugin\CachePlugin;
use Cache\Adapter\PHPArray\ArrayCachePool;
$pool = new ArrayCachePool();
$cachePlugin = new CachePlugin($pool, new \Http\Message\StreamFactory\GuzzleStreamFactory());
$client = new PluginClient(new \Http\Adapter\Guzzle6\Client(), [$cachePlugin]);
$swap = (new Builder())->setHttpClient($client)->build();
Dynamic Configuration: Override cache settings per request:
$rate = $swap->latest('USD/EUR', ['cache_ttl' => 60, 'cache' => true]);
Custom Services:
Extend functionality by implementing ExchangeRateService for unsupported APIs.
API Key Management:
.env or a secrets manager.Cache Key Limits:
cache_key_prefix must not exceed 24 characters (due to SHA-1 hashing).{}()/\@:) in prefixes; they are auto-replaced with -.Fallback Behavior:
try {
$rate = $swap->latest('USD/EUR');
} catch (\Exception $e) {
\Log::error("Currency rate failed: " . $e->getMessage());
}
Historical Data Gaps:
webservicex) do not support historical rates. Verify service capabilities before relying on them.Timezone Handling:
\DateTime::createFromFormat() for precision:
$date = \DateTime::createFromFormat('Y-m-d', '2023-01-01', new \DateTimeZone('UTC'));
Inspect Provider:
Use getProviderName() to debug which service is being used:
$rate = $swap->latest('USD/EUR');
\Log::debug("Provider: " . $rate->getProviderName());
Disable Caching Temporarily: Bypass cache to test live API responses:
$rate = $swap->latest('USD/EUR', ['cache' => false]);
HTTP Client Logging: Enable Guzzle logging to debug API requests:
$client = new \Http\Adapter\Guzzle6\Client([
'debug' => fopen('guzzle.log', 'w'),
]);
$swap = (new Builder())->setHttpClient($client)->build();
Custom Services:
Implement Swap\Service\ExchangeRateService to add support for new APIs:
class MyCustomService implements ExchangeRateService {
public function latest(string $pair): Rate {
// Fetch from your custom API
}
public function historical(string $pair, \DateTime $date): Rate {
// Fetch historical data
}
}
Register it with the builder:
$swap = (new Builder())->add('my_custom_service', new MyCustomService())->build();
Middleware for Requests: Add request/response middleware to the HTTP client (e.g., for auth headers):
use Http\Message\BaseMessage;
$client = new \Http\Adapter\Guzzle6\Client();
$stack = \Http\Message\Middleware::createStack();
$stack->push(function (callable $handler) {
return function (BaseMessage $request) use ($handler) {
$request = $request->withHeader('X-Custom-Header', 'value');
return $handler($request);
};
});
$swap = (new Builder())->setHttpClient($stack($client))->build();
Rate Validation:
Validate rates before use (e.g., check for null or invalid values):
$rate = $swap->latest('USD/EUR');
if ($rate->getValue() === null) {
throw new \RuntimeException("Invalid rate received");
}
Bulk Rate Fetching: For efficiency, fetch all rates for a base currency at once (if supported by the service):
$rates = $swap->latest(['USD/EUR', 'USD/GBP']); // If service supports bulk
Cache TTL Strategy:
Batch Requests:
Use the latest() method with an array of pairs to reduce API calls:
$rates = $swap->latest(['USD/EUR', 'USD/GBP', 'USD/JPY']);
Fallback Optimization: Place the most reliable/cheapest service first in the chain to minimize costs.
**Avoid Redundant C
How can I help you explore Laravel packages today?