{"id":13608028,"url":"https://github.com/brick/money","last_synced_at":"2026-04-02T17:06:49.561Z","repository":{"id":32982643,"uuid":"36608028","full_name":"brick/money","owner":"brick","description":"A money and currency library for PHP","archived":false,"fork":false,"pushed_at":"2026-03-28T14:16:21.000Z","size":893,"stargazers_count":1891,"open_issues_count":7,"forks_count":111,"subscribers_count":37,"default_branch":"main","last_synced_at":"2026-03-28T14:18:57.295Z","etag":null,"topics":["currency","currency-converter","exchange-rates","money","php"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brick.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"BenMorel"}},"created_at":"2015-05-31T15:01:17.000Z","updated_at":"2026-03-26T00:24:14.000Z","dependencies_parsed_at":"2023-11-26T17:31:12.818Z","dependency_job_id":"bcb78ebd-fd03-4eb3-b25a-c1c4226ade14","html_url":"https://github.com/brick/money","commit_stats":{"total_commits":419,"total_committers":12,"mean_commits":"34.916666666666664","dds":0.03341288782816232,"last_synced_commit":"310df1f2a9beedd36b26e6718238a70f0365c6cb"},"previous_names":[],"tags_count":40,"template":false,"template_full_name":null,"purl":"pkg:github/brick/money","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brick%2Fmoney","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brick%2Fmoney/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brick%2Fmoney/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brick%2Fmoney/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brick","download_url":"https://codeload.github.com/brick/money/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brick%2Fmoney/sbom","scorecard":{"id":253568,"data":{"date":"2025-08-11","repo":{"name":"github.com/brick/money","commit":"4ee860c0371aabef5faaddcc28b27f208bb67b76"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":1,"reason":"Found 4/27 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":2,"reason":"3 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 7 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T09:03:01.515Z","repository_id":32982643,"created_at":"2025-08-17T09:03:01.515Z","updated_at":"2025-08-17T09:03:01.515Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31311149,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["currency","currency-converter","exchange-rates","money","php"],"created_at":"2024-08-01T19:01:23.708Z","updated_at":"2026-04-02T17:06:49.548Z","avatar_url":"https://github.com/brick.png","language":"PHP","readme":"# Brick\\Money\n\n\u003cimg src=\"https://raw.githubusercontent.com/brick/brick/master/logo.png\" alt=\"\" align=\"left\" height=\"64\"\u003e\n\nA money and currency library for PHP.\n\n[![Build Status](https://github.com/brick/money/workflows/CI/badge.svg)](https://github.com/brick/money/actions)\n[![Coverage](https://codecov.io/github/brick/money/graph/badge.svg)](https://codecov.io/github/brick/money)\n[![Latest Stable Version](https://poser.pugx.org/brick/money/v/stable)](https://packagist.org/packages/brick/money)\n[![Total Downloads](https://poser.pugx.org/brick/money/downloads)](https://packagist.org/packages/brick/money)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT)\n\n## Introduction\n\nThis library provides immutable classes to work with monies and currencies, with exact arithmetic and explicit control over rounding — avoiding the silent rounding errors inherent to floating-point.\n\nIt is based on [brick/math](https://github.com/brick/math) and handles exact calculations on monies of any size.\n\n### Installation\n\nThis library is installable via [Composer](https://getcomposer.org/):\n\n```bash\ncomposer require brick/money\n```\n\n### Requirements\n\nThis library requires PHP 8.2 or later.\n\nFor PHP 8.1 compatibility, you can use version `0.10`. For PHP 8.0, you can use version `0.8`. For PHP 7.4, you can use version `0.7`. For PHP 7.1, 7.2 \u0026 7.3, you can use version `0.5`. Note that [these PHP versions are EOL](http://php.net/supported-versions.php) and not supported anymore. If you're still using one of these PHP versions, you should consider upgrading as soon as possible.\n\nAlthough not required, it is recommended that you **install the [GMP](http://php.net/manual/en/book.gmp.php) or [BCMath](http://php.net/manual/en/book.bc.php) extension** to speed up calculations.\n\n### Project status \u0026 release process\n\nWhile this library is still under development, it is well tested and should be stable enough to use in production environments.\n\nThe current releases are numbered `0.x.y`. When a non-breaking change is introduced (adding new methods, optimizing existing code, etc.), `y` is incremented.\n\n**When a breaking change is introduced, a new `0.x` version cycle is always started.**\n\nIt is therefore safe to lock your project to a given release cycle, such as `0.13.*`.\n\nIf you need to upgrade to a newer release cycle, check the [release history](https://github.com/brick/money/releases) for a list of changes introduced by each further `0.x.0` version.\n\n#### Currency updates\n\nThis library is based on the latest ISO 4217 standard. This is a living standard, so updates to currencies are expected to happen regularly.\n\nUpdates to the following features **will be considered breaking changes**, and **will only be released in a new major version** after 1.0:\n\n- Currencies obtained by alpha currency code such as `EUR` or `USD`, through `Currency::of()`, `Money::of()`, `Money::ofMinor()`, `IsoCurrencyProvider::getCurrency()`, etc.\n\nThe following features are evolving constantly, they will **not** be considered breaking changes and **may be updated in *minor* releases** after 1.0:\n\n- Currencies obtained by numeric currency code such as `978` or `840`, through `Currency::ofNumericCode()`, `IsoCurrencyProvider::getCurrencyByNumericCode()`, etc.\n- Current currencies obtained by country code such as `FR` or `US`, through `Currency::ofCountry()`, `IsoCurrencyProvider::getCurrencyForCountry()`, `IsoCurrencyProvider::getCurrenciesForCountry()`, etc.\n- Historical currencies obtained by country code through `IsoCurrencyProvider::getHistoricalCurrenciesForCountry()`.\n\n## Creating a Money\n\n### From a regular currency value\n\nTo create a Money, call the `of()` factory method:\n\n```php\nuse Brick\\Money\\Money;\n\n$money = Money::of(50, 'USD'); // USD 50.00\n$money = Money::of('19.9', 'USD'); // USD 19.90\n```\n\nIf the given amount does not fit in the currency's default number of decimal places (2 for `USD`), you can pass a `RoundingMode`:\n\n```php\n$money = Money::of('123.456', 'USD'); // RoundingNecessaryException\n$money = Money::of('123.456', 'USD', roundingMode: RoundingMode::Up); // USD 123.46\n```\n\n**Note that the rounding mode is only used once**, for the value provided in `of()`; it is not stored in the `Money` object, and any subsequent operation will still need to be passed a `RoundingMode` when necessary.\n\n### From minor units (cents)\n\nAlternatively, you can create a Money from a number of \"minor units\" (cents), using the `ofMinor()` method:\n\n```php\nuse Brick\\Money\\Money;\n\n$money = Money::ofMinor(1234, 'USD'); // USD 12.34\n```\n\n## Basic operations\n\nMoney is an immutable class: its value never changes, so it can be safely passed around. All operations on a Money therefore return a new instance:\n\n```php\nuse Brick\\Money\\Money;\n\n$money = Money::of(50, 'USD');\n\necho $money-\u003eplus('4.99'); // USD 54.99\necho $money-\u003eminus(1); // USD 49.00\necho $money-\u003emultipliedBy('1.999'); // USD 99.95\necho $money-\u003edividedBy(4); // USD 12.50\n```\n\nYou can add and subtract Money instances as well:\n\n```php\nuse Brick\\Money\\Money;\n\n$cost = Money::of(25, 'USD');\n$shipping = Money::of('4.99', 'USD');\n$discount = Money::of('2.50', 'USD');\n\necho $cost-\u003eplus($shipping)-\u003eminus($discount); // USD 27.49\n```\n\nIf the two Money instances are not of the same currency, an exception is thrown:\n\n```php\nuse Brick\\Money\\Money;\n\n$a = Money::of(1, 'USD');\n$b = Money::of(1, 'EUR');\n\n$a-\u003eplus($b); // CurrencyMismatchException\n```\n\nIf the result needs rounding, a [rounding mode](https://github.com/brick/math/blob/0.17.0/src/RoundingMode.php) must be passed as second parameter, or an exception is thrown:\n\n```php\nuse Brick\\Money\\Money;\nuse Brick\\Math\\RoundingMode;\n\n$money = Money::of(50, 'USD');\n\n$money-\u003eplus('0.999'); // RoundingNecessaryException\n$money-\u003eplus('0.999', RoundingMode::Down); // USD 50.99\n\n$money-\u003eminus('0.999'); // RoundingNecessaryException\n$money-\u003eminus('0.999', RoundingMode::Up); // USD 49.01\n\n$money-\u003emultipliedBy('1.2345'); // RoundingNecessaryException\n$money-\u003emultipliedBy('1.2345', RoundingMode::Down); // USD 61.72\n\n$money-\u003edividedBy(3); // RoundingNecessaryException\n$money-\u003edividedBy(3, RoundingMode::Up); // USD 16.67\n```\n\n## Comparing monies\n\nYou can compare two `Money` instances using the following methods:\n\n- `compareTo()` (returns `-1|0|1`)\n- `isEqualTo()`\n- `isGreaterThan()`\n- `isGreaterThanOrEqualTo()`\n- `isLessThan()`\n- `isLessThanOrEqualTo()`\n\nThese methods accept either a number or a `Money` instance. If the argument is a `Money` instance, it must be of the same currency as the `Money` instance on which the method is called, or an exception is thrown.\n\nIf you need to compare amount \u0026 currency without throwing on currency mismatch, you can use `isSameValueAs()` instead of `isEqualTo()`:\n\n```php\n$oneEuro = Money::of(1, 'EUR');\n\n$oneEuro-\u003eisEqualTo(Money::of(1, 'EUR')); // true\n$oneEuro-\u003eisEqualTo(Money::of(2, 'EUR')); // false\n$oneEuro-\u003eisEqualTo(Money::of(1, 'USD')); // CurrencyMismatchException\n\n$oneEuro-\u003eisSameValueAs(Money::of(1, 'EUR')); // true\n$oneEuro-\u003eisSameValueAs(Money::of(2, 'EUR')); // false\n$oneEuro-\u003eisSameValueAs(Money::of(1, 'USD')); // false\n```\n\n## Checking the sign\n\nYou can inspect the sign of a `Money` instance using the following methods:\n\n- `getSign()` (returns `-1|0|1`)\n- `isZero()`\n- `isPositive()`\n- `isPositiveOrZero()`\n- `isNegative()`\n- `isNegativeOrZero()`\n\n## Money contexts\n\nBy default, monies have the official scale for the currency, as defined by the [ISO 4217 standard](https://www.currency-iso.org/) (for example, EUR and USD have 2 decimal places, while JPY has 0) and increment by steps of 1 minor unit (cent); they internally use what is called the `DefaultContext`. You can change this behaviour by providing a `Context` instance. All operations on Money return another Money with the same context. Each context targets a particular use case:\n\n### Cash rounding\n\nSome currencies do not allow the same increments for cash and cashless payments. For example, `CHF` (Swiss Franc) has 2 fraction digits and allows increments of 0.01 CHF, but Switzerland does not have coins of less than 5 cents, or 0.05 CHF.\n\nYou can deal with such monies using `CashContext`:\n\n```php\nuse Brick\\Money\\Money;\nuse Brick\\Money\\Context\\CashContext;\nuse Brick\\Math\\RoundingMode;\n\n$money = Money::of(10, 'CHF', new CashContext(step: 5)); // CHF 10.00\n$money-\u003edividedBy(3, RoundingMode::Down); // CHF 3.30\n$money-\u003edividedBy(3, RoundingMode::Up); // CHF 3.35\n```\n\n### Custom scale\n\nYou can use custom scale monies by providing a `CustomContext`:\n\n```php\nuse Brick\\Money\\Money;\nuse Brick\\Money\\Context\\CustomContext;\nuse Brick\\Math\\RoundingMode;\n\n$money = Money::of(10, 'USD', new CustomContext(scale: 4)); // USD 10.0000\n$money-\u003edividedBy(7, RoundingMode::Up); // USD 1.4286\n```\n\n### Auto scale\n\nIf you need monies that adjust their scale to fit the operation result, then `AutoContext` is for you:\n\n```php\nuse Brick\\Money\\Money;\nuse Brick\\Money\\Context\\AutoContext;\n\n$money = Money::of('1.10', 'USD', new AutoContext()); // USD 1.1\n$money-\u003emultipliedBy('2.5'); // USD 2.75\n$money-\u003edividedBy(8); // USD 0.1375\n```\n\nNote that it is not advised to use `AutoContext` to represent an intermediate calculation result: in particular, it cannot represent the result of all divisions, as some of them may lead to an infinite repeating decimal, which would throw an exception. For these use cases, `RationalMoney` is what you need. Head on to the next section!\n\n## Advanced calculations\n\nYou may occasionally need to chain several operations on a Money, and only apply a rounding mode on the very last step; if you applied a rounding mode on every single operation, you might end up with a different result. This is where `RationalMoney` comes into play. This class internally stores the amount as a rational number (a fraction). You can create a `RationalMoney` from a `Money`, and conversely:\n\n```php\nuse Brick\\Money\\Money;\nuse Brick\\Math\\RoundingMode;\n\n$money = Money::of('9.5', 'EUR');                       // EUR 9.50 (Money)\n$money = $money-\u003etoRational()                           // EUR 19/2 (RationalMoney)\n  -\u003edividedBy(3)                                        // EUR 19/6 (RationalMoney)\n  -\u003eplus('17.795')                                      // EUR 12577/600 (RationalMoney)\n  -\u003emultipliedBy('1.196')                               // EUR 3760523/150000 (RationalMoney)\n  -\u003etoContext($money-\u003egetContext(), RoundingMode::Down) // EUR 25.07 (Money)\n```\n\nThe intermediate results are represented as fractions, and no rounding is ever performed. The final `toContext()` method converts it to a `Money`, applying a context and a rounding mode. Most of the time you want the result in the same context as the original Money, which is what the example above does. But you can really apply any context:\n\n```php\n...\n  -\u003etoContext(new CustomContext(scale: 8), RoundingMode::Up); // EUR 25.07015334\n```\n\n## Money allocation\n\n### Splitting\n\nYou can easily split a Money into a number of parts:\n\n```php\nuse Brick\\Money\\Money;\nuse Brick\\Money\\SplitMode;\n\n$money = Money::of(100, 'USD');\n[$a, $b, $c] = $money-\u003esplit(3, SplitMode::ToFirst); // USD 33.34, USD 33.33, USD 33.33\n```\n\nWith `SplitMode::ToFirst`, the remainder is distributed one step at a time to the first parts. You can also use `SplitMode::Separate` to get the remainder as a separate last element:\n\n```php\n$money-\u003esplit(3, SplitMode::Separate); // [USD 33.33, USD 33.33, USD 33.33, USD 0.01]\n```\n\n### Allocating\n\nYou can also allocate a Money according to a list of ratios. Say you want to distribute a profit of `987.65 CHF`f to 3 shareholders, having shares of `48%`, `41%` and `11%` of a company:\n\n```php\nuse Brick\\Money\\Money;\nuse Brick\\Money\\AllocationMode;\n\n$profit = Money::of('987.65', 'CHF');\n\n// CHF 474.08, CHF 404.93, CHF 108.64\n[$a, $b, $c] = $profit-\u003eallocate([48, 41, 11], AllocationMode::FloorToFirst);\n```\n\nIt plays well with cash roundings, too:\n\n```php\nuse Brick\\Money\\Money;\nuse Brick\\Money\\AllocationMode;\nuse Brick\\Money\\Context\\CashContext;\n\n$profit = Money::of('987.65', 'CHF', new CashContext(step: 5));\n\n// CHF 474.10, CHF 404.95, CHF 108.60\n[$a, $b, $c] = $profit-\u003eallocate([48, 41, 11], AllocationMode::FloorToFirst);\n```\n\n\u003e [!TIP]\n\u003e Ratios can be any type of number (integer, decimal, rational) and do not need to add up to 100.\n\nSeveral allocation modes are available. For example, given `1.00 USD` allocated by `[2, 3, 1]`:\n\n| Mode                                                                                                                                         | Result                         |\n|----------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|\n| `FloorToFirst`\u003cbr\u003e\u003csub\u003eProportional floor amounts, remainder distributed to first allocatees (Martin Fowler method)\u003c/sub\u003e                    | `0.34`, `0.50`, `0.16`         |\n| `FloorToLargestRemainder`\u003cbr\u003e\u003csub\u003eProportional floor amounts, remainder distributed to largest fractional remainders (Hamilton method)\u003c/sub\u003e | `0.33`, `0.50`, `0.17`         |\n| `FloorToLargestRatio`\u003cbr\u003e\u003csub\u003eProportional floor amounts, remainder distributed to largest ratios\u003c/sub\u003e                                      | `0.33`, `0.51`, `0.16`         |\n| `FloorSeparate`\u003cbr\u003e\u003csub\u003eProportional floor amounts, remainder returned as a separate last element\u003c/sub\u003e                                      | `0.33`, `0.50`, `0.16`, `0.01` |\n| `BlockSeparate`\u003cbr\u003e\u003csub\u003eOnly complete blocks allocated, remainder returned as a separate last element\u003c/sub\u003e                                  | `0.32`, `0.48`, `0.16`, `0.04` |\n\n## Money bags (mixed currencies)\n\nYou may sometimes need to add monies in different currencies together. `MoneyBag` comes in handy for this:\n\n```php\nuse Brick\\Money\\Money;\nuse Brick\\Money\\MoneyBag;\n\n$eur = Money::of('12.34', 'EUR');\n$jpy = Money::of(123, 'JPY');\n\n$moneyBag = MoneyBag::of($eur, $jpy);\n\n// or:\n\n$moneyBag = MoneyBag::zero()-\u003eplus($eur)-\u003eplus($jpy);\n```\n\nYou can add any kind of money to a MoneyBag: a `Money`, a `RationalMoney`, or even another `MoneyBag`.\n\nWhat can you do with a MoneyBag? Well, you can convert it to a Money in the currency of your choice, using a `CurrencyConverter`. Keep reading!\n\n## Currency conversion\n\nThis library ships with a `CurrencyConverter` that can convert any kind of money (`Money`, `RationalMoney` or `MoneyBag`) to a Money in another currency:\n\n```php\nuse Brick\\Money\\CurrencyConverter;\n\n$exchangeRateProvider = ...; // see below\n$converter = new CurrencyConverter($exchangeRateProvider);\n\n$money = Money::of('50', 'USD');\n$converter-\u003econvert($money, 'EUR', roundingMode: RoundingMode::Down);\n```\n\nThe converter performs the most precise calculation possible, internally representing the result as a rational number until the very last step.\n\nTo use the currency converter, you need an `ExchangeRateProvider`. Several implementations are provided, among which:\n\n### ConfigurableProvider \n\nThis provider allows you to configure exchange rates manually using a builder:\n\n```php\nuse Brick\\Money\\ExchangeRateProvider\\ConfigurableProvider;\n\n$provider = ConfigurableProvider::builder()\n    -\u003eaddExchangeRate('EUR', 'USD', '1.0987')\n    -\u003eaddExchangeRate('USD', 'EUR', '0.9123')\n    -\u003ebuild();\n```\n\n### BaseCurrencyProvider\n\nThis provider builds on top of another exchange rate provider, for the quite common case where all your available exchange rates are relative to a single currency. For example, the [exchange rates](https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml) provided by the European Central Bank are all relative to EUR. You can use them directly to convert EUR to USD, but not USD to EUR, let alone USD to GBP.\n\nThis provider will combine exchange rates to get the expected result:\n\n```php\nuse Brick\\Money\\ExchangeRateProvider\\ConfigurableProvider;\nuse Brick\\Money\\ExchangeRateProvider\\BaseCurrencyProvider;\n\n$provider = ConfigurableProvider::builder()\n    -\u003eaddExchangeRate('EUR', 'USD', '1.1')\n    -\u003eaddExchangeRate('EUR', 'GBP', '0.9')\n    -\u003ebuild();\n\n$provider = new BaseCurrencyProvider($provider, 'EUR');\n$provider-\u003egetExchangeRate(Currency::of('EUR'), Currency::of('USD')); // 1.1\n$provider-\u003egetExchangeRate(Currency::of('USD'), Currency::of('EUR')); // 10/11\n$provider-\u003egetExchangeRate(Currency::of('GBP'), Currency::of('USD')); // 11/9\n```\n\n\u003e [!TIP]\n\u003e Notice that exchange rate providers can return rational numbers (fractions)!\n\n### PdoProvider\n\nThis provider reads exchange rates from a database table:\n\n```php\nuse Brick\\Money\\ExchangeRateProvider\\PdoProvider;\n\n$pdo = new \\PDO(...);\n\n$provider = PdoProvider::builder($pdo, 'exchange_rates', 'exchange_rate')\n    -\u003esetSourceCurrencyColumn('source_currency_code')\n    -\u003esetTargetCurrencyColumn('target_currency_code')\n    -\u003ebuild();\n```\n\n`PdoProvider` supports fixed source or target currency, numeric currency codes, dimensions, and static `WHERE` conditions. Check the [PdoProviderBuilder](https://github.com/brick/money/blob/0.13.0/src/ExchangeRateProvider/Pdo/PdoProviderBuilder.php) class for more information.\n\n#### Dimensions\n\nDimensions allow you to narrow exchange rate lookups beyond just a currency pair. For example, if your exchange rates table includes a date or rate type, you can bind these as dimensions:\n\n```php\nuse Brick\\Money\\ExchangeRateProvider\\PdoProvider;\nuse Brick\\Money\\ExchangeRateProvider\\Pdo\\SqlCondition;\n\n$provider = PdoProvider::builder($pdo, 'exchange_rates', 'exchange_rate')\n    -\u003esetSourceCurrencyColumn('source_currency_code')\n    -\u003esetTargetCurrencyColumn('target_currency_code')\n    -\u003ebindDimension('year', fn (int $year) =\u003e new SqlCondition('year = ?', $year))\n    -\u003ebindDimension('month', fn (int $month) =\u003e new SqlCondition('month = ?', $month))\n    -\u003ebuild();\n```\n\nEach dimension binding is a callback that receives the dimension value and returns a `SqlCondition` (an SQL fragment with positional parameters), or `null` to skip filtering on that dimension.\n\nYou can then pass dimensions when looking up an exchange rate:\n\n```php\nuse Brick\\Money\\Currency;\n\n$rate = $provider-\u003egetExchangeRate(\n    Currency::of('EUR'),\n    Currency::of('USD'),\n    ['year' =\u003e 2017, 'month' =\u003e 8],\n);\n```\n\nDimensions also flow through the `CurrencyConverter`:\n\n```php\nuse Brick\\Math\\RoundingMode;\nuse Brick\\Money\\CurrencyConverter;\nuse Brick\\Money\\Money;\n\n$converter = new CurrencyConverter($provider);\n\n$converter-\u003econvert(\n    Money::of('10.00', 'EUR'),\n    'USD',\n    ['year' =\u003e 2017, 'month' =\u003e 8],\n    roundingMode: RoundingMode::HalfUp,\n);\n```\n\nA dimension binding can also accept complex types. For example, you can accept a `DateTimeInterface` and derive multiple SQL conditions from it:\n\n```php\n$provider = PdoProvider::builder($pdo, 'exchange_rates', 'exchange_rate')\n    -\u003esetSourceCurrencyColumn('source_currency_code')\n    -\u003esetTargetCurrencyColumn('target_currency_code')\n    -\u003ebindDimension(\n        'as_of',\n        fn (\\DateTimeInterface $date) =\u003e new SqlCondition(\n            'year = ? AND month = ?',\n            (int) $date-\u003eformat('Y'),\n            (int) $date-\u003eformat('m'),\n        ),\n    )\n    -\u003ebuild();\n\n$rate = $provider-\u003egetExchangeRate(\n    Currency::of('EUR'),\n    Currency::of('USD'),\n    ['as_of' =\u003e new \\DateTimeImmutable('2017-08-15')],\n);\n```\n\nIf your table may contain multiple matching rows (e.g. daily rates within a month), use `orderBy()` to select the first match:\n\n```php\n$provider = PdoProvider::builder($pdo, 'exchange_rates', 'exchange_rate')\n    -\u003esetSourceCurrencyColumn('source_currency_code')\n    -\u003esetTargetCurrencyColumn('target_currency_code')\n    -\u003ebindDimension('date', fn (string $date) =\u003e new SqlCondition('date \u003c= ?', $date))\n    -\u003eorderBy('date', 'DESC')\n    -\u003ebuild();\n```\n\n\u003e [!NOTE]\n\u003e If a dimension is passed that has no binding, the provider returns `null` (rate not found). Conversely, if a bound dimension is **not** passed, its condition is simply omitted from the query — this may cause multiple rows to match and throw an exception unless `orderBy()` is configured.\n\n### CachedProvider\n\nThis provider wraps another provider and caches the results using a [PSR-16](https://www.php-fig.org/psr/psr-16/) cache. Both found and not-found rates are cached:\n\n```php\nuse Brick\\Money\\ExchangeRateProvider\\CachedProvider;\n\n$cachedProvider = new CachedProvider($provider);\n```\n\nBy default, an in-memory array cache is used. You can pass your own PSR-16 cache implementation and a TTL:\n\n```php\n$cachedProvider = new CachedProvider(\n    provider: $provider,\n    cache: $yourPsr16Cache,\n    ttl: 3600, // seconds\n);\n```\n\nDimensions are included in the cache key. Scalars, `null`, `DateTimeInterface`, and `Stringable` values are supported out of the box. For other object types, pass a custom normalizer:\n\n```php\n$cachedProvider = new CachedProvider(\n    provider: $provider,\n    cache: $yourPsr16Cache,\n    dimensionObjectNormalizer: function (object $value) {\n        if ($value instanceof YourCustomType) {\n            return $value-\u003etoKey(); // string, int, float, bool accepted\n        }\n\n        return null; // fall back to built-in handling\n    },\n);\n```\n\nIf a dimension value is not cacheable (unsupported object type and no custom normalizer), the cache is bypassed and the wrapped provider is queried directly.\n\n### ChainProvider\n\nThis provider tries multiple providers in order and returns the first non-null result:\n\n```php\nuse Brick\\Money\\ExchangeRateProvider\\ChainProvider;\n\n$provider = new ChainProvider($primaryProvider, $fallbackProvider);\n```\n\nThis is useful for combining providers — for example, different providers each supporting specific currency pairs or dimensions. Dimensions are passed through to each provider unchanged.\n\n### Write your own provider\n\nWriting your own provider is easy: the `ExchangeRateProvider` interface has just one method, `getExchangeRate()`, that takes the currency codes, optional dimensions, and returns a number or `null` if the rate is not found:\n\n```php\npublic function getExchangeRate(\n    Currency $sourceCurrency,\n    Currency $targetCurrency,\n    array $dimensions = [],\n): ?BigNumber;\n```\n\n## Custom currencies\n\nMoney supports ISO 4217 currencies by default. You can also use custom currencies by creating a `Currency` instance. Let's create a Bitcoin currency:\n\n```php\nuse Brick\\Money\\Currency;\nuse Brick\\Money\\Money;\n\n$bitcoin = new Currency(\n    'XBT',     // currency code\n    null,      // numeric currency code, optional; set to null if unused\n    'Bitcoin', // currency name\n    8,         // default scale\n);\n```\n\nYou can now use this Currency instead of a currency code:\n\n```php\n$money = Money::of('0.123', $bitcoin); // XBT 0.12300000\n```\n\n\u003e [!WARNING]\n\u003e Do not create multiple `Currency` instances with the same currency code but different data. The library identifies currencies by code, so conflicting instances used together may lead to undefined behaviour.\n\n## Formatting\n\n**Formatting requires the [intl extension](http://php.net/manual/en/book.intl.php).**\n\nMoney objects can be formatted according to a given locale:\n\n```php\n$money = Money::of(5000, 'USD');\necho $money-\u003eformatToLocale('en_US'); // $5,000.00\necho $money-\u003eformatToLocale('fr_FR'); // 5 000,00 $US\n```\n\nAlternatively, you can format Money objects with your own instance of [NumberFormatter](http://php.net/manual/en/class.numberformatter.php), which gives you room for customization:\n\n```php\nuse Brick\\Money\\Money;\nuse Brick\\Money\\Formatter\\MoneyNumberFormatter;\n\n$formatter = new \\NumberFormatter('en_US', \\NumberFormatter::CURRENCY);\n$formatter-\u003esetSymbol(\\NumberFormatter::CURRENCY_SYMBOL, 'US$');\n$formatter-\u003esetSymbol(\\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL, '·');\n$formatter-\u003esetAttribute(\\NumberFormatter::MIN_FRACTION_DIGITS, 2);\n\n$money = Money::of(5000, 'USD');\necho (new MoneyNumberFormatter($formatter))-\u003eformat($money); // US$5·000.00\n```\n\n\u003e [!IMPORTANT]\n\u003e Because formatting is performed using `NumberFormatter`, the amount is converted to floating point in the process; so discrepancies can appear when formatting very large monetary values.\n\n## Storing Money objects in a database\n\n### Persisting the amount\n\n- **As an integer**: in many applications, monies are only ever used with their default scale (e.g. 2 decimal places for `USD`, 0 for `JPY`). In this case, the best practice is to store minor units (cents) as an integer field:\n  \n  ```php\n  $integerAmount = $money-\u003egetMinorAmount()-\u003etoInt();\n  ```\n  \n  And later retrieve it as:\n  \n  ```php\n  Money::ofMinor($integerAmount, $currencyCode);\n  ```\n  \n  This approach works well with all currencies, without having to worry about the scale. You only have to worry about not overflowing an integer (which would throw an exception), but this is unlikely to happen unless you're dealing with huge amounts of money.\n  \n- **As a decimal**: for most other cases, storing the amount string as a decimal type is advised:\n  \n  ```php\n  $decimalAmount = $money-\u003egetAmount()-\u003etoString();\n  ```\n  \n  And later retrieve it as:\n  \n  ```php\n  Money::of($decimalAmount, $currencyCode);\n  ```\n\n### Persisting the currency\n\n- **As a string**: if you only deal with ISO currencies, or custom currencies having a 3-letter currency code, you can store the currency in a `CHAR(3)`. Otherwise, you'll most likely need a `VARCHAR`. You may also use an `ENUM` if your application uses a fixed list of currencies.\n  \n  ```php\n  $currencyCode = $money-\u003egetCurrency()-\u003egetCurrencyCode();\n  ```\n  \n  When retrieving the currency: you can use ISO currency codes directly in `Money::of()` and `Money::ofMinor()`. For custom currencies, you'll need to convert them to `Currency` instances first.\n  \n- **As an integer**: if you only deal with ISO currencies, or custom currencies with a numeric code, you may store the currency code as an integer:\n  \n  ```php\n  $numericCode = $money-\u003egetCurrency()-\u003egetNumericCode();\n  ```\n\n  When retrieving the currency: for ISO currencies, first convert the numeric code to a `Currency` instance with `Currency::ofNumericCode()`, then pass that `Currency` instance to `Money::of()` or `Money::ofMinor()`. For custom currencies, you'll likewise need to convert the numeric code to a `Currency` instance first.\n\n- **Hardcoded**: if your application only ever deals with one currency, you may very well hardcode the currency code and not store it in your database at all.\n\n\u003e [!NOTE]\n\u003e Numeric currency codes of ISO currencies may be reassigned over time, so prefer alphabetical currency codes whenever possible.\n\n### Using an ORM\n\nIf you're using an ORM such as Doctrine, it is advised to store the amount and currency separately, and perform conversion in the getters/setters:\n\n  ```php\n  class Entity\n  {\n      private int $price;\n      private string $currencyCode;\n\n      public function getPrice() : Money\n      {\n          return Money::ofMinor($this-\u003eprice, $this-\u003ecurrencyCode);\n      }\n\n      public function setPrice(Money $price) : void\n      {\n          $this-\u003eprice = $price-\u003egetMinorAmount()-\u003etoInt();\n          $this-\u003ecurrencyCode = $price-\u003egetCurrency()-\u003egetCurrencyCode();\n      }\n  }\n  ```\n\n## FAQ\n\n\u003e How does this project compare with [moneyphp/money](https://github.com/moneyphp/money)?\n\nPlease see [this discussion](https://github.com/brick/money/issues/28).\n\n## PHPStan extension\n\nA third-party [PHPStan extension](https://github.com/simPod/phpstan-brick-money) is available for this library. It provides more specific throw type narrowing for brick/money methods, so that PHPStan can infer the exact exception classes thrown. Note that this extension is not maintained by the author of brick/money.\n","funding_links":["https://github.com/sponsors/BenMorel"],"categories":["Table of Contents","PHP","目录","Recently Updated","📌 Covered Topics"],"sub_categories":["E-commerce","电子商务 E-commerce","[Sep 02, 2024](/content/2024/09/02/README.md)"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrick%2Fmoney","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrick%2Fmoney","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrick%2Fmoney/lists"}