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

Open Exchange Rates Service Laravel Package

mrzard/open-exchange-rates-service

A Laravel integration for the Open Exchange Rates API that lets you fetch live and historical currency exchange rates, convert amounts between currencies, and list supported currencies through a simple, configurable service wrapper.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a thin Laravel wrapper around the mrzard/open-exchange-rates-php client — it does one job: bind an OpenExchangeRates API client into the container and expose config for your app_id. Given the last release was December 2017 (Laravel 5.5 era) and it has essentially no adoption, treat the first 30 minutes as a compatibility audit rather than a normal install.

1. Install and inspect what you actually got

composer require mrzard/open-exchange-rates-service

Before writing any code, open vendor/mrzard/open-exchange-rates-service/ and read three things:

  • composer.json → require (which Laravel/Illuminate versions, which PHP) and extra.laravel (does it support package auto-discovery?)
  • src/ → the service provider class name and the container binding key
  • config/ → the config file name and key names

If extra.laravel.providers is missing, you must register manually in config/app.php:

'providers' => [
    // ...
    Mrzard\OpenExchangeRates\ServiceProvider::class, // verify exact FQCN
],

2. Publish config and set your key

php artisan vendor:publish --provider="Mrzard\OpenExchangeRates\ServiceProvider"
// config/open-exchange-rates.php (name per the package's config file)
return [
    'app_id' => env('OXR_APP_ID'),
    // 'base_currency' => 'USD', // if present
];
OXR_APP_ID=your_app_id_here

Get a key from openexchangerates.org — the free tier is the primary use case here.

3. First use case: a currency conversion

Inject the client rather than reaching for a facade (see Gotchas on facade aliases):

use Mrzard\OpenExchangeRates\...; // confirm the concrete class from src/

public function __construct(private OpenExchangeRates $oxr) {}

public function priceInEur(float $usd): float
{
    $rates = $this->oxr->latest(); // or ->getLatest(), check src/
    return $usd * $rates['rates']['EUR'];
}

That round trip — resolve from container, fetch latest rates, read from rates — is 90% of what this package is for.

Implementation Patterns

Constructor injection everywhere. The package's only real value is that the client is a singleton in the container with your key already wired. Never new the client yourself; you lose the config and the ability to fake it.

Wrap it in your own service — don't call the client from controllers. Because the package is unmaintained, you want a single file to change when you inevitably replace it:

final class ExchangeRateService
{
    public function __construct(private OpenExchangeRates $client, private int $ttl = 3600) {}

    public function rate(string $from, string $to): float
    {
        $rates = Cache::remember('oxr:latest', $this->ttl, fn () => $this->client->latest());
        return $rates['rates'][$to] / $rates['rates'][$from];
    }
}

Cache aggressively — the client does not. This is the most important pattern in the whole package. Free-tier quota is small (historically ~1,000 requests/month), and latest() on a page load will burn it in days. Cache the full rates array, not individual pairs, and use a TTL matched to the provider's refresh cadence (hourly for free tier).

Schedule a warm-up instead of cache-miss fetches. A scheduled job that writes oxr:latest every hour means user requests never touch the API:

// app/Console/Kernel.php
$schedule->call(fn () => Cache::put('oxr:latest', app(OpenExchangeRates::class)->latest(), 7200))
         ->hourly()
         ->withoutOverlapping();

Then your read path is Cache::get('oxr:latest') — fast, quota-safe, and degrades gracefully if the API is down.

Test by swapping the binding. $this->app->instance(OpenExchangeRates::class, $fake) (or $this->mock(...)) in a test case. For a thin API wrapper this is often easier than fighting the package's own test setup, which likely targets Laravel 5.4-era PHPUnit.

Historical/time-series endpoints. If src/ exposes historical($date) / timeSeries(...), note these hit different endpoints with different quota costs — route them through a separate cache namespace and don't schedule them on the same tight cadence as latest().

Gotchas and Tips

  • Compatibility is the first bug you'll hit. A 2017-12 release was built against Illuminate 5.x. On Laravel 9/10/11 the provider may fail to boot over removed helpers or bind differences. Check composer.json constraints before composer require and be ready to either pin a compatible Laravel or fork the provider. If you only need the client, installing mrzard/open-exchange-rates-php directly and binding it yourself in AppServiceProvider is often less work than making the wrapper boot.

  • Config quirks are silent. If app_id is empty, most versions of the underlying client fail at request time with a generic HTTP error rather than at boot. Add a guard: abort_unless(config('open-exchange-rates.app_id'), 'OXR_APP_ID missing') somewhere in a boot/test path.

  • php artisan config:cache will freeze your key. Standard Laravel trap, but acute here because the wrapper leans entirely on config. Always re-run config:cache after changing OXR_APP_ID, and never call env() directly from your own code — go through the package config.

  • Facade aliases may not be registered. If extra.laravel.aliases is absent, OpenExchangeRates::latest() throws "class not found" until you add the alias to config/app.php. Constructor injection sidesteps this; prefer it.

  • Currency codes are uppercase and case-sensitive. API returns USD, EUR, etc. $rates['rates']['eur'] is null, and multiplying by null silently yields 0 in PHP — a conversion bug that looks like a data bug.

  • Historical rates are UTC. historical('2024-01-01') means midnight UTC. An order timestamped late on Dec 31 local time may need the previous day's rate. Convert explicitly, don't assume server timezone.

  • Handle API errors yourself. The client will throw on non-200 (invalid key, quota exceeded, rate-limited 429). Wrap calls in try/catch with a fallback — a stale cached rate is almost always better than a 500 on a checkout page:

    try {
        return $this->fetchFresh();
    } catch (\Throwable $e) {
        Log::warning('OXR failed', ['e' => $e->getMessage()]);
        return Cache::get('oxr:latest') ?? throw $e;
    }
    
  • Extension points are minimal. Don't expect a manager/driver pattern or a contract to implement. If you need behavior changes (retry, base-currency handling, caching), extend your own wrapper class or fork — patching the provider's register() is a maintenance debt you'll regret.

  • Plan an exit. Given 1 star, 0 dependents, and a 7+ year gap, budget for replacing this with Http::withToken(...)->get('https://openexchangerates.org/api/latest.json') — about 30 lines. Keeping the dependency confined behind your own service class (above) makes that a one-file change.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle