Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Currency Exchange Bundle Laravel Package

ongr/currency-exchange-bundle

Symfony bundle for currency exchange rates and conversion, offering configurable rate providers, storage, and service-based APIs to fetch and apply exchange rates in PHP applications. Integrates with ONGR/Elasticsearch for managing currency data.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and register (Symfony 2.8/3.x era package):

composer require ongr/currency-exchange-bundle
// app/AppKernel.php
$bundles = [
    // ...
    new ONGR\CurrencyExchangeBundle\ONGRCurrencyExchangeBundle(),
];

On Symfony 4+ there is no recipe and no auto-registration — add it to config/bundles.php manually and be prepared to hand-wire services (see Gotchas).

Configure a single adapter and a cache pool in config/config.yml:

ongr_currency_exchange:
    adapter: ongr_currency_exchange.adapter.european_central_bank
    cache_lifetime: 3600
    # cache: my_redis_pool     # a PSR-6 service id

Look here first, in this order:

  1. DependencyInjection/Configuration.php — the authoritative list of config keys and adapter service ids. This is the file that answers 90% of "how do I configure X" questions.
  2. Resources/config/services.yml — the service ids you will inject or alias.
  3. The underlying library ongr/currency-exchange — the bundle is a thin DI wrapper; all conversion logic, adapters and cache handling live there.

First use case — converting a stored base price for display:

use ONGR\CurrencyExchange\CurrencyExchangeInterface;

class PriceFormatter
{
    public function __construct(private CurrencyExchangeInterface $exchange) {}

    public function format(float $amount, string $from, string $to): string
    {
        $converted = $this->exchange->convert($amount, $from, $to);
        return number_format($converted, 2) . ' ' . $to;
    }
}
{{ product.price|currency_exchange_convert('EUR', 'USD') }}

If the Twig extension is not registered for you, add it explicitly from Resources/config/services.yml ids — check the bundle's service definitions before assuming a filter name.

Implementation Patterns

Inject the interface, not the concrete service. Type-hint CurrencyExchangeInterface (or alias the bundle's service id to it in your own services.yml). This keeps your code testable and lets you swap adapters without touching call sites.

Store money in one base currency, convert at the edge. Persist prices, orders and invoices in your accounting currency; call the exchange only in presenters, Twig extensions and API transformers. Never write a converted value back to the database — the rate will be stale tomorrow and your books will drift.

Pick the adapter by base currency, not by preference.

  • EuropeanCentralBankAdapter — free, no key, EUR-based, ~30 currencies, published once per working day. Best default for a European store.
  • Open Exchange Rates / CurrencyLayer / Fixer adapters — require an access key, USD-based (usually), higher currency coverage, free tiers are heavily rate-limited.

Warm the cache out-of-band. The most common production pattern is a cron job / console command that iterates the currency pairs you actually display and calls the exchange, so no web request ever pays the HTTP latency:

foreach ([['EUR','USD'], ['EUR','GBP'], ['EUR','PLN']] as [$from, $to]) {
    $exchange->getRate($from, $to); // populates the PSR-6 pool
}

Bind rates to a business date. If you need "the rate we used when the order was placed", capture the rate at order time and store it alongside the order. The bundle gives you a current rate, not a historical series.

Guard the boundary. Wrap conversions in a small service that try/catches adapter exceptions and falls back to a persisted last-known rate:

try {
    return $this->exchange->getRate($from, $to);
} catch (\Exception $e) {
    $this->logger->warning('FX fallback used', ['pair' => "$from/$to"]);
    return $this->rateRepository->lastKnown($from, $to);
}

Tests: never hit a live endpoint. Bind a stub implementation of CurrencyExchangeInterface returning fixed rates in config/services_test.yml; assert on formatted output, not on float equality.

Gotchas and Tips

The package is effectively abandoned. Last release August 2016, three stars, zero dependents. Expect Composer friction: it pins old symfony/* ranges and older PSR cache contracts. On modern Symfony you will likely need "replace" tricks, a fork, or to depend on ongr/currency-exchange directly and skip the bundle entirely. Budget for that.

The default cache is the trap. If no PSR-6 pool is configured, you can end up with an in-memory/array cache that is discarded per request — meaning one upstream HTTP call per page view. Always point cache at Redis/APCu/filesystem and set cache_lifetime to something sane (hours, not seconds — these are daily rates).

ECB rates are EUR-based and cross-rates are derived. Converting USD→GBP is computed as USD→EUR→GBP, so it carries two roundings. Fine for display, not for accounting. Also: ECB publishes on TARGET business days only — weekends and holidays return the previous day's file, and a naive "is this stale?" check will misfire.

Daily rates are not transaction rates. Do not use this for checkout totals, tax or payment capture without an explicit business decision and a stored rate snapshot.

Float precision. convert() returns a float. For anything that must reconcile to the cent, round once at the boundary with a money library or BCMath, and be consistent about half-up vs half-even across the app.

API-key adapters fail loudly and often. Free tiers rate-limit; a 429 from the provider surfaces as an exception mid-render and takes down the page. Always combine the fallback wrapper above with a short retry/backoff and a health check on your cache age.

No persistence layer. There is no rate table, no history, no migrations. If you need an audit trail or a rate-per-order, you build it yourself — that is the single biggest piece of surrounding code this bundle does not give you.

Extension point: a custom adapter. Implement the library's adapter interface, register it as a service, and reference it by service id in the adapter config key. This is the clean way to plug in your bank's treasury feed or a provider the bundle never supported.

Debugging tips: dump the raw adapter response before it is parsed (ECB's XML is the usual culprit when a currency "disappears"); check the cache key the library builds for the pair — a reversed pair is a separate cache entry and a separate upstream call; and confirm the bundle's service definitions actually loaded by running debug:container and grepping for currency_exchange.

Pin your expectations in config tests. Because the package will not receive fixes, add an integration test that asserts a known conversion works against a recorded HTTP fixture. It is your only early warning when a provider changes its response shape.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect