merk/dough
Dough is a lightweight money value object for Laravel and PHP, with currency-aware arithmetic, parsing, formatting, comparisons, and Eloquent casts. Handle prices, totals, taxes, and conversions safely using immutable amounts and avoid floating-point errors. Includes helpers for common money operati...
What it is. merk/dough is a standalone immutable Money value object, a direct port of Kent Beck's TDD-by-Example implementation. It is not a Laravel package — there is no service provider, config file, facade, or Eloquent integration. It is a small, framework-agnostic domain primitive that Laravel can consume the same way any Composer library is consumed.
Architecture fit. Reasonable fit for a domain-layer value object in a DDD/hexagonal Laravel application: put it in app/Domain/Support or a dedicated internal package and keep it out of controllers and Blade templates. It is a poor fit for teams following Laravel's default idiom of decimal columns plus number_format() helpers, because adopting Dough forces a discipline change (integer minor units + explicit currency) across every layer. It also overlaps almost entirely with moneyphp/money (the de facto PHP standard) and brick/money, both of which have materially larger feature surfaces (allocation, rounding modes, currency repositories, parsers, formatters).
Integration feasibility. Mechanically trivial — composer require plus PSR-4 autoloading. The real friction is version constraint compatibility. A release dated 2016-01-18 predates PHP 7.1, and its composer.json will almost certainly pin php: >=5.x and dev-dependencies (PHPUnit 4/5-era) that conflict with Laravel 9/10/11's PHP 8.1+ requirement. Expect Composer resolution failures; --ignore-platform-reqs in CI is not an acceptable workaround. The practical path is a fork into an internal namespace with an updated constraint block.
Technical risk. High, and asymmetric — the upside is modest, the downside is a long-lived liability.
Key questions to resolve before committing.
brick/money or moneyphp/money if we adopt it today?Stack fit. Technically zero-friction, idiomatically awkward. Because it is pure PHP, it drops into any layer — form requests, jobs, domain services, policies — but nothing in Laravel knows about it. Every boundary (database, JSON API, queue payloads, Blade formatting) requires an explicit adapter written by us.
Compatibility. Treat compatibility as unproven until a spike proves it. Requirements: Composer install on our exact PHP version, the upstream test suite executed under PHP 8.x, a round-trip serialization test (serialize/unserialize and an Eloquent-to-queue-to-worker cycle), and an overflow test at both 32-bit and realistic 64-bit ceilings. If any of these fail, vendor the source into an internal package rather than patching in vendor/.
Migration path. Adopt behind an anti-corruption layer, not directly.
App\Support\Contracts\Money — exposing only the operations we actually need (add, subtract, multiply, equals, isGreaterThan, getAmount, getCurrency).DoughMoney as the first adapter. Application code type-hints the interface only.amount_minor (BIGINT) and currency_code (CHAR(3)) as separate columns. Never store floats.brick/money later becomes a one-class change plus a container binding, with no call-site churn.Sequencing.
brick/money — the effort saved is not worth the ownership burden.Money interface with domain and finance stakeholders.Maintenance. This becomes our codebase on day one. Budget for an internal owner, a vendored fork in a private repository, and a standing commitment to re-audit on every PHP major upgrade. Realistically a few days a year of maintenance — small in absolute terms, but it competes for attention against libraries with active upstreams.
Support. MIT with no SLA, no security advisory feed, no issue triage. There is no external escalation path; the internal owner is the support tier. Document that explicitly so it does not surface as an incident-time surprise.
Scaling. Computational cost is negligible — immutable value objects are cheap and allocation-bounded, and this will never be a throughput bottleneck. Two real scaling considerations: object churn in tight loops (batch operations should sum in integers, not construct thousands of Money instances), and serialization size for queue payloads and API responses, where a lean array representation is preferable to serializing the object graph.
Failure modes to design against.
Ramp-up. The API is small and the Money pattern is well-understood, so individual ramp-up is a few hours. The organizational ramp-up is the real cost: developers accustomed to float arithmetic will need a written convention (integers in, integers out, explicit currency everywhere), review checklist items, and at least one worked example in the codebase. Onboarding friction is higher than with moneyphp/money, where prior exposure is far more common and documentation is external and maintained.
Recommendation. Do not adopt for greenfield. The package is technically sound and architecturally inoffensive, but it is abandoned, unverifiable, and functionally a subset of a well-maintained alternative; the only durable benefit is negligible security surface. If existing code already depends on it, contain it behind the adapter described above and schedule replacement. If the goal is simply "an immutable Money type in Laravel," brick/money delivers the same value with an active maintainer and a credible upgrade path.
How can I help you explore Laravel packages today?