NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Money for Laravel

DI and actions

The facades are the recommended default, not the only way in. There are three equivalent entry points to the same code:

  • The Exchange facade — the shortest form, used throughout these docs.
  • The manager — RoundlyConsulting\Money\Exchange\ExchangeManager, the container singleton and the root of the Exchange facade. Inject it for the same API with an explicit dependency and no static calls.
  • Actions — RefreshExchangeRatesAction, StoreExchangeRatesAction and PruneExchangeRatesAction: single-purpose classes with execute(), for composing into your own actions, jobs and commands.

Injecting the manager

use RoundlyConsulting\Money\Exchange\ExchangeManager;

final class RecordCzkRate
{
    public function __construct(private ExchangeManager $exchange) {}

    public function __invoke(string $rate): void
    {
        $this->exchange->rates()->manual('EUR', 'CZK', $rate); // same call as Exchange::rates()->manual()
    }
}

Exchange::fake() swaps the injected instance too — the fake extends ExchangeManager, so a class that type-hints the manager receives it in tests rather than a TypeError. For currencies, inject the RoundlyConsulting\Money\Contracts\CurrencyRegistry contract:

use RoundlyConsulting\Money\Contracts\CurrencyRegistry;

public function __construct(private CurrencyRegistry $currencies) {}

$this->currencies->get('EUR')->exponent;   // 2 — the same singleton Currencies resolves

Calling an action

use RoundlyConsulting\Money\Actions\StoreExchangeRatesAction;
use RoundlyConsulting\Money\Exchange\ExchangeRate;

app(StoreExchangeRatesAction::class)->execute([
    ExchangeRate::fromDecimal('EUR', 'CZK', '25.10', now(), source: 'manual'),
]);
use Carbon\CarbonImmutable;
use RoundlyConsulting\Money\Actions\PruneExchangeRatesAction;
use RoundlyConsulting\Money\Actions\RefreshExchangeRatesAction;
use RoundlyConsulting\Money\DataTransferObjects\PruneExchangeRatesData;
use RoundlyConsulting\Money\DataTransferObjects\RefreshExchangeRatesData;
use RoundlyConsulting\Money\Enums\EcbFeed;

$result = app(RefreshExchangeRatesAction::class)->execute(
    new RefreshExchangeRatesData('ecb', EcbFeed::Recent),
);
$result->stored;                   // rows written
$result->skippedManual;            // manual rows left untouched
$result->skippedUnknownCurrency;   // feed currencies the registry does not know
$result->latestDate;               // ?CarbonImmutable

$deleted = app(PruneExchangeRatesAction::class)->execute(
    new PruneExchangeRatesData(CarbonImmutable::now()->subYear()),
);

Facade method → action

Facade methodActionTakesNote
Exchange::rates()->refresh()RefreshExchangeRatesActionRefreshExchangeRatesDataRuns now.
Exchange::rates()->refreshLater()RefreshExchangeRatesActionRefreshExchangeRatesDataThrough the queued RefreshExchangeRates job.
Exchange::rates()->store(...$rates)StoreExchangeRatesActioniterable<ExchangeRate>The one write path.
Exchange::rates()->manual(…)StoreExchangeRatesActioniterable<ExchangeRate>One rate with source manual.
Exchange::rates()->prune(…)PruneExchangeRatesActionPruneExchangeRatesDataReturns the rows deleted or counted.
  • RefreshExchangeRatesData takes source = 'ecb', feed = EcbFeed::Daily and optional from and to dates; PruneExchangeRatesData takes before, includeManual = false and pretend = false.
  • Refresh and store return a RefreshResult (source, stored, skippedManual, skippedUnknownCurrency, latestDate); prune returns an int.
  • The commands and the queued RefreshExchangeRates job run the same code.
  • Exchange::fake() records what goes through Exchange::rates() — on the facade or an injected manager. An action you call directly runs for real.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.