PHPackages                             elriseio/finance-money-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. elriseio/finance-money-bundle

ActiveSymfony-bundle

elriseio/finance-money-bundle
=============================

Type-safe monetary value objects, currency registry, exchange-rate port, and Symfony Bundle integration for high-load financial applications.

v0.1.2(yesterday)00MITPHPPHP ^8.3CI passing

Since Jul 27Pushed yesterdayCompare

[ Source](https://github.com/elriseio/finance-money-bundle)[ Packagist](https://packagist.org/packages/elriseio/finance-money-bundle)[ Docs](https://github.com/elriseio/finance-money-bundle)[ RSS](/packages/elriseio-finance-money-bundle/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (9)Versions (3)Used By (0)

elriseio/finance-money-bundle
=============================

[](#elriseiofinance-money-bundle)

> Type-safe monetary value objects, currency registry, exchange-rate port, and Symfony Bundle integration for high-load financial applications.

[![CI](https://github.com/elriseio/finance-money-bundle/actions/workflows/ci.yml/badge.svg)](https://github.com/elriseio/finance-money-bundle/actions/workflows/ci.yml)[![Latest Stable Version](https://camo.githubusercontent.com/8aeaa0eb3c6f230f43452621a7ae2efa1e6ce60703d104addfa482a810ee1d68/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f656c72697365696f2f66696e616e63652d6d6f6e65792d62756e646c652e737667)](https://packagist.org/packages/elriseio/finance-money-bundle)[![Total Downloads](https://camo.githubusercontent.com/2287ae6fcd71ff40fc4d8db17a53b0e01e2ce9ecc79f24eb22176b615973e41f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f656c72697365696f2f66696e616e63652d6d6f6e65792d62756e646c652e737667)](https://packagist.org/packages/elriseio/finance-money-bundle)[![License](https://camo.githubusercontent.com/2b8ddbfa8f06e82501751b3a1b249f3085be27e399a8358388fc8f7d22b4e96b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f656c72697365696f2f66696e616e63652d6d6f6e65792d62756e646c652e737667)](https://github.com/elriseio/finance-money-bundle/blob/main/LICENSE)

**Money Bundle** is a Symfony-native bundle for high-load financial applications. It provides a type-safe monetary value object layer for PHP 8.3+ applications. Every arithmetic operation goes through BCMath; currency mismatch is a compile-time error; the exchange-rate port is injected through DI so consumers stay in control of regional API choice.

The bundle is part of the **elriseio/** vendor namespace alongside the sibling `elriseio/dbal-bundle`. The composer name `elriseio/finance-money-bundle` is the canonical identifier on Packagist and in the project's `composer.json` `name` field.

What you get
------------

[](#what-you-get)

- **`Money` value object** — immutable, `final readonly`, BCMath-backed. Static factories accept a string code or `Currency` instance; arithmetic (`plus`, `minus`, `multipliedBy`, `dividedBy`, `allocate`, `compare`) returns new instances. Optional cross-currency comparison via `Money::compare(other, ?provider)` (DE-020).
- **`Currency` value object** — code, scale, type (`Fiat` / `Crypto` / `Custom`), symbol, name.
- **`Decimal` engine** — BCMath-only facade. Direct `bcdiv` / `bcmod`/ `bcscale` outside `Decimal\Math` is forbidden by a custom PHPStan rule (`ForbiddenBcFunctionInDecimalRule`).
- **`FundConverter`** — float ↔ string ↔ int ↔ fund with `externalMultiplier` for PSP cents / satoshi / virtual-currency units.
- **`ExchangeRateProviderInterface`** — DI-only contract; concrete providers (`CbrRateProvider`, `EcbRateProvider`, …) are **not**part of this bundle's core. They live in optional companion packages. Provider metadata (`source`, `at`) is exposed through the `rateWithMetadata()` method (DE-021); the simpler `rate()` method stays for backwards compatibility.
- **`ConversionResult` DTO** — carries `(from, to, rate, source, at)`for conversion audit (DE-021). Returned by `Money::convertWithMetadata()` (opt-in sibling method).
- **`CurrencyRegistryInterface`** — registry of currencies with soft-fail lookup (`tryGet` returns `null`; `get` throws `UnknownCurrencyException`). The bundle ships **no default registry**; host applications populate the registry at boot time via `CurrencyRegistry::mutable()->register(...)` (or `CurrencyRegistry::fromCatalogue(...)` for an immutable seed).
- **`InMemoryExchangeRateProvider`** — a stub provider shipped in the core for tests; never use in production.
- **Symfony Bundle** — `elrise_finance_money` config key, services auto-wired, attribute-based registration (`#[AsCurrency]`), `ValidateConfigurationPass` compiler pass.
- **Strict exception taxonomy** — every public method has a declared `@throws` set; domain exceptions carry an `errorCode()`for structured logging.

When to use this
----------------

[](#when-to-use-this)

- Multi-currency fintech applications: iGaming, Banking, Lending, Crypto, PSP, Neobank.
- Any system where **BCMath is non-negotiable** and float drift is unacceptable.
- Symfony 7.x or 8.x projects that want first-class monetary contracts without giving up framework independence for the core.
- Codebases that need `Money` / `Currency` value objects but want consumer code to **not import BCMath directly**.

When NOT to use this
--------------------

[](#when-not-to-use-this)

- Mono-currency apps where `int cents` is enough — the `Money` value object adds overhead you do not need.
- High-throughput hot paths (&gt; 10k ops/sec) where the BCMath cost matters more than type safety — use a native `int` representation and store amounts in minor units.
- Pure presentation work — the bundle's `MoneyFormatter` uses `\NumberFormatter` via `ext-intl`. For non-monetary display, use `NumberFormatter` directly.

Requirements
------------

[](#requirements)

RequirementVersionMandatoryPHP8.3, 8.4, or 8.5yes`ext-bcmath`anyyes`ext-intl`anyoptional (used by `MoneyFormatter`)Composer2.xyesSymfony7.x or 8.xyes (for the Bundle layer; core is framework-agnostic)PHP 8.2 is **not** supported (the bundle uses `final readonly class`, `enum` cases, and typed constants).

Installation
------------

[](#installation)

```
composer require elriseio/finance-money-bundle
```

Register the bundle in `config/bundles.php` (Symfony Flex does this automatically):

```
return [
    // ...
    Elrise\Finance\Bundle\Money\FinanceMoneyBundle::class => ['all' => true],
];
```

Quick start
-----------

[](#quick-start)

```
use Elrise\Finance\Bundle\Money\Money;
use Elrise\Finance\Bundle\Money\Currency\Currency;
use Elrise\Finance\Bundle\Money\Currency\CurrencyRegistry;

$registry = CurrencyRegistry::mutable([
    Currency::iso('USD', '840', null, 'US Dollar'),
    Currency::iso('EUR', '978', null, 'Euro'),
    Currency::crypto('BTC', 8, null, 'Bitcoin'),
    Currency::crypto('ETH', 18, null, 'Ether'),
    Currency::custom('POINTS', 0, null, 'Bonus Points'),
]);
$usd = $registry->get('USD');

$deposit = Money::of('100.00', $usd);
$bonus   = $deposit->multipliedBy('1.10');         // '110.00'
$total   = $deposit->plus($bonus);                  // '110.00 USD'
$fmt     = $total->format('ru_RU');                 // '110.00 USD'

// Cross-currency conversion with metadata (DE-021)
$result = $total->convertWithMetadata(
    'EUR',
    new \DateTimeImmutable(),
    $rateProvider,
);
// $result->to()      === Money(95.85, EUR)
// $result->rate()    === '0.9585'
// $result->source()  === 'ecb' (provider-supplied)
// $result->at()      === DateTimeImmutable(...)

// Cross-currency comparison (DE-020)
$sign = $usdMoney->compare($eurMoney, $rateProvider);
// -1 / 0 / 1
```

A minimal Symfony `services.yaml` excerpt:

```
services:
    Elrise\Finance\Bundle\Money\Contract\ExchangeRateProviderInterface:
        alias: 'app.exchange_rate.ecb_provider'   # from your project

    Elrise\Finance\Bundle\Money\CurrencyRegistry:
        arguments:
            $currencies: '%elrise_finance_money.currencies%'

    _instanceof:
        Elrise\Finance\Bundle\Money\Contract\CurrencyInterface:
            tags: ['elrise_finance_money.currency']
```

Configuration
-------------

[](#configuration)

```
elrise_finance_money:
    enabled: true

    default_currency: USD
    default_multiplier: 100000   # internal precision (5 fractional digits)
    bcmath_scale: 8             # safety limit for BCMath operations
    view_precision: 2           # precision for UI formatting
    rounding_mode: half_up       # half_up | half_even | half_down

    # The bundle ships no default currency registry. Host
    # applications populate the registry at boot time via
    # CurrencyRegistry::mutable()->register(...). The currency
    # metadata below documents the recommended scale/type per
    # code; symbols are consumer-supplied at the UI edge.

    exchange_rate_provider: 'app.exchange_rate.ecb_provider'
```

Quality gates
-------------

[](#quality-gates)

Run the full battery locally before opening a PR:

```
composer test   # PHPUnit (Unit / Contract / Property-based)
composer stan   # PHPStan level 8 + ergebnis rules
composer cs     # PHPCS (PSR-12 + custom Money standard)
composer bench  # phpbench micro-benchmarks
```

All four commands exit non-zero on failure. CI runs the same matrix on PHP 8.3, 8.4, and 8.5; Composer resolves `symfony/*` to the highest stable branch that matches the platform PHP (Symfony 7.x on PHP 8.3, Symfony 7.x or 8.x on PHP 8.4, Symfony 8.x on PHP 8.5).

Custom PHPCS sniffs (`tests/Rules/Money/`):

- `Money.HotPath.NoFloatOnMoneyHotPath` — fails on `float` parameter, return type, or property under `src/Money.php`, `src/Currency/`, or `src/Decimal/`.
- `Money.HotPath.ImmutabilityGuard` — fails if a value object in `src/Money.php`, `src/Currency/Currency.php`, or `src/Decimal/Decimal.php` is missing `final` or `readonly`, declares a non-readonly property, or exposes a public setter.

Custom PHPStan rule:

- `ForbiddenBcFunctionInDecimalRule` — blocks direct `bcdiv`, `bcmod`, `bcscale` calls in `Decimal\` outside `Decimal\Math`.

Testing
-------

[](#testing)

Test suiteWherePurposeUnit`tests/Unit/`Pure unit tests, no Symfony containerContract`tests/Contract/`Public-interface invariants; cross-implementation consistencyProperty-based`tests/Property/`Round-trip and invariant fuzzing (Eris)Integration`itests/`End-to-end with real Symfony container, in-memory rate provider, scenario runnersBenchmark`bench/`phpbench micro-benchmarks for hot-path budgetsFailure-mode`itests/FailureMode/`Cache-down, provider-down, BCMath-scale-overflowProperty-based tests assert algebraic invariants like `add(a, b) == add(b, a)`, `multiply(a, 1) == a`, `allocate(n) sums to original`, and round-trip `fromCanonicalString(toCanonicalString(x)) == x`.

Performance
-----------

[](#performance)

The hot path (`Money::of`, `Money::plus`, `Money::minus`, `Money::multipliedBy`) stays under documented budgets:

OperationBudgetNotes`Money::of(string, Currency)`&lt; 100 µsincludes `Decimal` construction`Money::plus(Money)`&lt; 50 µsBCMath `bcadd` at receiver's scale`Money::multipliedBy(string)`&lt; 100 µsincludes quantization`Money::convert(Currency, …)`&lt; 1 mswith cached provider, single-hop`Money::convertWithMetadata(…)`&lt; 1.2 msadds `ConversionResult` construction`Money::compare(Money, …)`&lt; 200 µssingle-currency baseline`Money::compare(other, provider)`&lt; 1.2 mscross-currency, with provider call`Money::allocate(int)`&lt; 1 mslargest-remainder distribution`Money::format(string)`&lt; 500 µswith `\NumberFormatter``Money::toFloat()`&lt; 50 µsone `bcdiv` castBudgets are tracked via `phpbench`; regressions fail CI. See `bench/` for the exact scenarios.

Production deployment
---------------------

[](#production-deployment)

- **Runtime requirements** are the same as `## Requirements` above. No additional PHP extensions, no native libraries.
- **Container compilation** validates config at build time via `ValidateConfigurationPass`. Misconfiguration is caught in CI, not in production.
- **No floating-point state.** Every `amount` is a BCMath-precise decimal string. Floats appear only as transient parse targets.
- **Deterministic rounding.** HALF-UP by default; rounding mode is configurable globally. Same input produces the same output across PHP versions and platforms.
- **Exchange rates are not bundled.** Consumers must implement `ExchangeRateProviderInterface` and register it via DI. The bundle ships an in-memory provider for tests but never for production.
- **Logs and metrics.** Public exceptions carry `errorCode()` for structured logging; no bundle code emits PII or financial values to logs.

Compatibility matrix
--------------------

[](#compatibility-matrix)

PHPSymfonyBCMathStatus8.37.xrequiredsupported8.47.xrequiredsupported8.48.xrequiredsupported8.58.xrequiredsupported8.2any—not supportedRoadmap
-------

[](#roadmap)

Active wave: **Wave 0 — Foundation and Contract Baseline** (see `docs/ROADMAP.md` for the full sequence). The roadmap is reviewed after each closed implementation task.

Production-readiness status is tracked in [`docs/PRODUCTION_READINESS_CHECKLIST.md`](docs/PRODUCTION_READINESS_CHECKLIST.md).

Architecture at a glance
------------------------

[](#architecture-at-a-glance)

```
                        ┌────────────────────────────────┐
                        │  elriseio/finance-money-bundle │
                        │  (this package, framework-    │
                        │   agnostic core + Symfony     │
                        │   Bundle integration)         │
                        └─────────────┬──────────────────┘
                                      │
                ┌─────────────────────┼─────────────────────┐
                │                     │                     │
        ┌───────▼────────┐    ┌────────▼────────┐    ┌───────▼────────┐
        │ Money /        │    │ Decimal\Math    │    │ CurrencyReg.  │
        │ Currency VO    │    │ (BCMath facade) │    │ (ISO + custom)│
        └───────┬────────┘    └────────┬────────┘    └───────┬────────┘
                │                     │                     │
                └─────────────────────┼─────────────────────┘
                                      │
                        ┌─────────────▼──────────────────┐
                        │  Symfony DI / Bundle            │
                        │  (services, compiler passes,    │
                        │   config validation)            │
                        └─────────────┬──────────────────┘
                                      │
                        ┌─────────────▼──────────────────┐
                        │  Sub-packages (Doctrine, Cycle, │
                        │  Serializer, Forms, Exchange-   │
                        │  Rate providers) — optional,    │
                        │  per consumer                   │
                        └────────────────────────────────┘

```

For component-by-component documentation, see [`docs/components/`](docs/components/). For public-contract documentation, see [`docs/contracts/`](docs/contracts/). For architectural decision records, see [`docs/adr/`](docs/adr/).

Contributing
------------

[](#contributing)

See [`CONTRIBUTING.md`](CONTRIBUTING.md). Notable requirements:

- PHPStan level 8 over `src/`, `tests/`, `bench/`.
- PHPCS PSR-12 + the `Money` custom standard.
- New public contracts require an ADR before implementation.
- Changes to the arithmetic core require a property-based test covering the new invariant.
- Production-readiness checklist ([`docs/PRODUCTION_READINESS_CHECKLIST.md`](docs/PRODUCTION_READINESS_CHECKLIST.md)) must stay green before a release tag.
- No references to internal production projects, vendor paths, or private repositories in public docs (sanitization contract).

Security
--------

[](#security)

- **No PII handling.** The bundle operates on amounts and codes; no customer identifiers, no card data, no account numbers.
- **No secrets in code.** API keys for rate providers belong in the consumer's secret store (Vault, Symfony secrets, AWS Secrets Manager, …), injected via environment variables.
- **BCMath safety limit** is set per configuration; exceeding it raises `MathScaleException` rather than silently truncating.
- **CSPRNG** is not used by the bundle (no token generation).

Report security issues privately — see the GitHub Security tab.

Acknowledgements
----------------

[](#acknowledgements)

This bundle consolidates patterns from four production fintech systems that have run since 2018. The architecture rationale is documented in [`docs/architecture.md`](docs/architecture.md). The acknowledgements section is intentionally generic — no internal project names are exposed in public documentation per the sanitization contract.

More examples and more docs
---------------------------

[](#more-examples-and-more-docs)

Working examples, end-to-end tutorials, provider recipes, and architectural deep-dives live on the project page:

[**elrise.io/projects/finance-money-bundle**](https://elrise.io/projects/finance-money-bundle/)

If something is missing on that page, open an issue on GitHub and tag it `docs`.

License
-------

[](#license)

MIT — see [`LICENSE`](LICENSE).

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity39

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

1d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/207833371?v=4)[Elrise IO](/maintainers/elriseio)[@elriseio](https://github.com/elriseio)

---

Top Contributors

[![alexk136](https://avatars.githubusercontent.com/u/21287520?v=4)](https://github.com/alexk136 "alexk136 (28 commits)")

---

Tags

moneycurrencyValue ObjectdecimalfinanceSymfony Bundlebcmath

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/elriseio-finance-money-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/elriseio-finance-money-bundle/health.svg)](https://phpackages.com/packages/elriseio-finance-money-bundle)
```

###  Alternatives

[grumpydictator/firefly-iii

Firefly III: a personal finances manager.

23.9k69.5k](/packages/grumpydictator-firefly-iii)[torann/currency

This provides Laravel with currency functions such as currency formatting and conversion using up-to-date exchange rates.

4031.1M6](/packages/torann-currency)[firefly-iii/data-importer

Firefly III Data Import Tool.

8055.8k](/packages/firefly-iii-data-importer)[ulabox/money

Yet another PHP implementation of the Money value object using BCMath

78242.9k](/packages/ulabox-money)[rtlopez/decimal

An object oriented immutable arbitrary-precision arithmetic library for PHP

27274.7k2](/packages/rtlopez-decimal)[adsmurai/currency

A small library to handle currencies and money values

4443.1k](/packages/adsmurai-currency)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
