PHPackages                             gosuperscript/schema-money - 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. [Validation &amp; Sanitization](/categories/validation)
4. /
5. gosuperscript/schema-money

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

gosuperscript/schema-money
==========================

Monetary extension for Axiom - provides schema types, parsers, and operators for monetary values with strong type safety and currency validation

v0.6.0(1mo ago)01.6k[2 PRs](https://github.com/gosuperscript/axiom-money/pulls)MITPHPPHP ^8.4CI passing

Since Jun 19Pushed 1mo agoCompare

[ Source](https://github.com/gosuperscript/axiom-money)[ Packagist](https://packagist.org/packages/gosuperscript/schema-money)[ Docs](https://github.com/gosuperscript/axiom-money)[ RSS](/packages/gosuperscript-schema-money/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (8)Dependencies (36)Versions (21)Used By (0)

Axiom Money
===========

[](#axiom-money)

[![Tests](https://github.com/gosuperscript/axiom-money/workflows/Tests/badge.svg)](https://github.com/gosuperscript/axiom-money/actions)[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](https://opensource.org/licenses/MIT)

A monetary extension for [Axiom](https://github.com/gosuperscript/axiom), providing schema types, a parser, and operator rules for monetary values with strong type safety and currency validation.

Features
--------

[](#features)

- **Schema Types**: Type-safe monetary value handling with currency validation
- **Money Parser**: Parse monetary values from various string formats (e.g., "EUR 100", "£50.25")
- **Operator rules**: Addition, subtraction, comparison and equality between monies of the same currency, plus multiplication/division by a numeric scalar — resolved and type-checked at compile time, declared per currency by a `MoneyExtension`
- **Multiple Type Variants**:
    - `MonetaryType`: Standard monetary type with currency validation
    - `MinorMonetaryType`: Money from minor units (cents, pence, etc.)
    - `DynamicMonetaryType`: Flexible parsing that auto-detects currency (boundary type only)
    - `MonetaryIntervalType`: Intervals of monetary values
- **Monetary Intervals**: Support for ranges of monetary values

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

[](#requirements)

- PHP 8.4 or higher
- ext-intl extension

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

[](#installation)

Install via Composer:

```
composer require gosuperscript/axiom-money
```

Usage
-----

[](#usage)

### Basic Money Types

[](#basic-money-types)

```
use Brick\Money\Currency;
use Brick\Money\Money;
use Superscript\Axiom\Money\Types\MonetaryType;

// Create a monetary type for EUR
$eurType = new MonetaryType(Currency::of('EUR'));

// Coerce values to Money objects
$money = $eurType->coerce('100.50')->unwrap()->unwrap();
// Result: Money object with EUR 100.50

// Assert existing Money objects
$result = $eurType->assert(Money::of(50, 'EUR'));
// Result: Ok(Some(Money))

// Format money for display
$formatted = $eurType->format(Money::of(100.50, 'EUR'));
// Result: "€100.50"
```

Every money type projects into Axiom's shape algebra as an opaque `money` identity parameterized by its currency (`Money`). `Money` fills a `Money` slot and shares no values with `Money` — all without a single core relation rule mentioning money.

### Money Parser

[](#money-parser)

Parse money from various string formats:

```
use Superscript\Axiom\Money\MoneyParser;
use Brick\Money\Money;

// Parse from "CURRENCY AMOUNT" format
$result = MoneyParser::parse('EUR 100');
$money = $result->unwrap(); // Money object: EUR 100

// Parse from currency symbol format
$result = MoneyParser::parse('£50.25');
$money = $result->unwrap(); // Money object: GBP 50.25

// Already a Money object? Just returns it
$existing = Money::of(100, 'EUR');
$result = MoneyParser::parse($existing);
$money = $result->unwrap(); // Same Money object
```

### Minor Units

[](#minor-units)

Work with minor currency units (cents, pence, etc.):

```
use Brick\Money\Currency;
use Superscript\Axiom\Money\Types\MinorMonetaryType;

$gbpType = new MinorMonetaryType(Currency::of('GBP'));

// Coerce from minor units (100 pence = £1.00)
$money = $gbpType->coerce(100)->unwrap()->unwrap();
// Result: Money object with GBP 1.00
```

`MinorMonetaryType` projects to the *same* opaque `money` shape as `MonetaryType`: the two differ only in how they read raw input at the boundary, and a value of either is the same `Money`, so both resolve the same operator rules.

### Dynamic Monetary Type

[](#dynamic-monetary-type)

Automatically detect and parse currency from string:

```
use Superscript\Axiom\Money\Types\DynamicMonetaryType;

$dynamicType = new DynamicMonetaryType();

$money = $dynamicType->coerce('USD 100')->unwrap()->unwrap();
// Result: Money object with USD 100
```

`DynamicMonetaryType` admits any currency at the boundary, so its currency is *not* statically known — it projects to an opaque `money` with **no** currency parameter. That makes it a coercion/boundary type only: it is deliberately not assignable to the currency-parameterized `Money` the operator rules resolve for. Declare a concrete `MonetaryType` where you need arithmetic or comparison.

### Operator rules — the `MoneyExtension`

[](#operator-rules--the-moneyextension)

Money's typing is *parameterized* by currency, and a signature's return type is fixed, so the rules are declared by **enumeration over the host's configured currencies**. Compose the extension onto the core dialect and hand it to an expression; the compiler resolves and type-checks every operator, and the compiled `Program` runs what it resolved with no runtime dispatch.

```
use Brick\Money\Money;
use Superscript\Axiom\Dialect;
use Superscript\Axiom\Expression;
use Superscript\Axiom\Money\MoneyExtension;
use Superscript\Axiom\Money\Types\MonetaryType;
use Superscript\Axiom\Sources\InfixExpression;
use Superscript\Axiom\Sources\SymbolSource;

$dialect = Dialect::core()->with(new MoneyExtension(['GBP', 'USD', 'EUR']));

$expression = new Expression(
    new InfixExpression(new SymbolSource('a'), '+', new SymbolSource('b')),
    dialect: $dialect,
    declarations: ['a' => new MonetaryType(Currency::of('GBP')), 'b' => new MonetaryType(Currency::of('GBP'))],
);

$program = $expression->compile()->unwrap();
$program(['a' => Money::of(1, 'GBP'), 'b' => Money::of(2, 'GBP')])->unwrap()->unwrap(); // GBP 3.00
```

The rules, per configured currency:

- **`+` / `-`** — same currency in, same currency out.
- **`*` / `/`** — by a numeric scalar (multiplication on either side; division is `Money / number`). The calculation runs in Brick's exact rational domain and is **rounded back to the currency scale**, so the result is a `Money` of the same currency — each operator has one honest return type. The rounding mode is the extension's second constructor argument (default `RoundingMode::HALF_UP`). Division by zero is a *value-dependent* error, returned as an `Err`, not a compile-time refusal.
- **`=`** — ordering between two monies of the same currency → boolean.
- **`=` / `==` / `===` and `!=` / `!==`** — equality via Brick's amount-and-currency comparison. Core refuses equality on opaque operands, so the money package owns its own.

A **cross-currency** operation (`Money + Money`) matches no rule and is refused at compile time with a named diagnostic — no program containing it can be compiled, let alone run.

> **Rounding note (breaking change):** previous versions returned an exact `Brick\Money\RationalMoney` from `*`/`/` and propagated it through `+`/`-` ("rational is contagious"). Under the typed model each operator has a fixed return type, so `*`/`/` now round to a `Money` at the currency scale using the extension's rounding mode. Chain in the rational domain yourself (via Brick) if you need to defer rounding.

### Monetary Intervals

[](#monetary-intervals)

Work with ranges of monetary values:

```
use Brick\Money\Currency;
use Brick\Money\Money;
use Superscript\Axiom\Money\Types\MonetaryIntervalType;
use Superscript\MonetaryInterval\MonetaryInterval;
use Superscript\MonetaryInterval\IntervalNotation;

$intervalType = new MonetaryIntervalType(Currency::of('EUR'));

// Parse an interval from string notation
$interval = $intervalType->coerce('[100,200]')->unwrap()->unwrap();

// Or construct one directly
$interval = new MonetaryInterval(
    left: Money::of(100, 'EUR'),
    right: Money::of(200, 'EUR'),
    notation: IntervalNotation::Closed,
);

$intervalType->format($interval); // "[EUR 100.00, EUR 200.00]"
```

`MonetaryIntervalType` projects to an opaque `monetary-interval` shape. The `MoneyExtension` contributes, per currency, the comparison of a monetary interval against a money of that currency (`=` → boolean) and equality between two monetary intervals of the same currency (`=`/`==`/`===`, `!=`/`!==`).

```
$dialect = Dialect::core()->with(new MoneyExtension(['EUR']));

$expression = new Expression(
    new InfixExpression(new SymbolSource('range'), '>', new SymbolSource('amount')),
    dialect: $dialect,
    declarations: [
        'range' => new MonetaryIntervalType(Currency::of('EUR')),
        'amount' => new MonetaryType(Currency::of('EUR')),
    ],
);
```

Development
-----------

[](#development)

### Running Tests

[](#running-tests)

```
# Run all tests
composer test

# Run unit tests only
composer test:unit

# Run type checking
composer test:types

# Run mutation testing
composer test:infection
```

### Code Quality

[](#code-quality)

The project enforces 100% code coverage and uses:

- **PHPUnit**: Unit testing
- **PHPStan**: Static analysis
- **Laravel Pint**: Code style
- **Infection**: Mutation testing

Dependencies
------------

[](#dependencies)

This library builds on several excellent packages:

- [brick/money](https://github.com/brick/money): Robust money and currency library
- [gosuperscript/axiom](https://github.com/gosuperscript/axiom): The expression language it extends
- [superscript/interval](https://github.com/superscript/interval): Interval mathematics
- [superscript/monetary-interval](https://github.com/superscript/monetary-interval): Monetary interval support

License
-------

[](#license)

MIT License - see [LICENSE](LICENSE) file for details.

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

[](#contributing)

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate and maintain the existing code quality standards.

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance94

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity54

Maturing project, gaining track record

 Bus Factor2

2 contributors hold 50%+ of commits

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 ~35 days

Recently: every ~24 days

Total

8

Last Release

30d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/14931924?v=4)[Robert van Steen](/maintainers/robertvansteen)[@robertvansteen](https://github.com/robertvansteen)

---

Top Contributors

[![robertvansteen](https://avatars.githubusercontent.com/u/14931924?v=4)](https://github.com/robertvansteen "robertvansteen (9 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (7 commits)")[![erikgaal](https://avatars.githubusercontent.com/u/1234268?v=4)](https://github.com/erikgaal "erikgaal (2 commits)")[![fawazsuleiman](https://avatars.githubusercontent.com/u/129744165?v=4)](https://github.com/fawazsuleiman "fawazsuleiman (1 commits)")[![jcmvrij](https://avatars.githubusercontent.com/u/71216496?v=4)](https://github.com/jcmvrij "jcmvrij (1 commits)")

---

Tags

schemavalidationmoneycurrencyintervalmonetary

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/gosuperscript-schema-money/health.svg)

```
[![Health](https://phpackages.com/badges/gosuperscript-schema-money/health.svg)](https://phpackages.com/packages/gosuperscript-schema-money)
```

###  Alternatives

[opis/json-schema

Json Schema Validator for PHP

65446.2M365](/packages/opis-json-schema)[romaricdrigon/metayaml

Using \[Yaml|Xml|json\] schemas files to validate \[Yaml|Xml|json\]

103314.0k8](/packages/romaricdrigon-metayaml)[evaisse/php-json-schema-generator

A JSON Schema Generator.

18321.8k1](/packages/evaisse-php-json-schema-generator)[romegasoftware/laravel-schema-generator

Generate TypeScript Zod validation schemas from Laravel validation rules

3223.9k](/packages/romegasoftware-laravel-schema-generator)

PHPackages © 2026

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