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 resolvesCalling 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 method | Action | Takes | Note |
|---|---|---|---|
Exchange::rates()->refresh() | RefreshExchangeRatesAction | RefreshExchangeRatesData | Runs now. |
Exchange::rates()->refreshLater() | RefreshExchangeRatesAction | RefreshExchangeRatesData | Through the queued RefreshExchangeRates job. |
Exchange::rates()->store(...$rates) | StoreExchangeRatesAction | iterable<ExchangeRate> | The one write path. |
Exchange::rates()->manual(…) | StoreExchangeRatesAction | iterable<ExchangeRate> | One rate with source manual. |
Exchange::rates()->prune(…) | PruneExchangeRatesAction | PruneExchangeRatesData | Returns 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 cryptoBy 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.