rimu/formatted-number
A PHP package from rimu for formatting numbers into readable, presentation-ready strings, with support for custom separators, decimals, and locale-style output. Useful for displaying prices, counts, and other numeric values consistently in Laravel or plain PHP applications.
Architecture fit — narrow and conditional. Despite the description ("lightweight PHP package for human-readable formatted numbers," configurable decimals, currency/percentage, reusable format definitions, "Laravel-friendly API"), the artifact is a Laravel Nova field: one PHP class extending Laravel\Nova\Fields\Field plus a compiled Vue component. There is no facade, no service provider, no publishable config, and no Blade helper. That changes the fit test entirely: this is only relevant if Nova is already your back-office surface and the gap is genuinely in resource display. It is not a formatting library for APIs, exports, Blade, Filament, or Inertia, and it does not solve input masking, validation, locale-aware currency semantics, or storage.
Integration feasibility — resolve before you plan. The gating fact is the laravel/nova and php constraint in composer.json. A last release of 2019-10-25 almost certainly pins Nova ^2.0/^3.0 and PHP 7.x. On Nova 4/5 (Inertia + Vue 3 + Vite) resolution will fail — and that failure is the cheapest, best outcome in the whole evaluation. The correct first action is not a design doc; it is composer require rimu/formatted-number --dry-run. Everything downstream is contingent on that single result. If it resolves, the install is three commands (require, optionally publish nova-assets, nova:publish) and the field class is small enough to read end to end in minutes.
Technical risk — asymmetric, and mostly ownership risk. Ranked by what a TPM should care about:
FormField/DetailField mixins and Mix conventions may fail to register, or register and throw in-browser — which breaks create/update forms for staff, not just the one column you wanted to prettify.$1.2M before $900K. Nova's numeric sort/search behavior must be verified per column, not assumed.1234567.891.Key questions to answer before scoping any work:
composer require ... --dry-run resolve?onlyOnIndex() / onlyOnDetail())?Number subclass using displayUsing() + number_format) fail any documented requirement? If not, the case for adoption collapses.Stack fit. Realistic fit is Nova 3 / PHP 7.x only. If we are on Nova 2 or 3 and the decision is "make ARR, cents-stored-as-integers, quota, and row counts render consistently across ops dashboards," this is a plausible candidate. If we are on Nova 4/5, or on Filament/Inertia/Blade, it is out of scope by construction — the entire value is the Nova field contract.
The baseline comes first, not last. The right sequencing is to write the in-house equivalent before installing anything:
class FormattedNumber extends Number
{
public function currency(string $symbol = '$', int $decimals = 2): static
{
return $this->displayUsing(
fn ($v) => is_null($v) ? null : $symbol . number_format((float) $v, $decimals)
);
}
}
Thirty lines, zero dependencies, no upgrade exposure, and it covers essentially the entire stated value. If the requirement survives that comparison, we have a concrete gap worth scoping. If it doesn't — and it usually won't — we're done in an hour with first-party code.
Migration / adoption path (only if the baseline is insufficient). Do not composer require into production. Copy the class and component into app/Nova/Fields/ with an attribution header, add PHP tests around grouping, decimals, and separators, and record a named owner. That converts an unmaintained dependency into a small piece of first-party code we can reason about during the next Nova upgrade. The attribution obligation is trivial; the upgrade-survivability is not.
Compatibility checks, in order: composer.json constraints → field class source → resources/js/components/ and dist/ (legacy mixins vs. Inertia composition — this single fact decides feasibility) → asset registration path (vendor:publish --tag=nova-assets, nova:publish) → absence of tests.
Sequencing for the pilot:
Number + displayUsing() baseline.1234567.891, open edit, save untouched, re-read the model. Any change is a stop signal.config/nova-formats.php map plus a FormatsNumbers trait on the base resource, so the consistency win isn't re-implemented forty times.Structural pattern to enforce regardless of vendor decision: a native Number field scoped onlyOnForms() and the formatted field scoped onlyOnIndex()/onlyOnDetail(). Two fields, one column, zero write path through the formatter. That is what preserves the containment that makes this a low-risk decision at all.
Maintenance. There is no upstream to escalate to. No issue triage, no compatibility patches, no security response. Every defect and every Nova upgrade is our work. Budget it explicitly or don't adopt. Per-upgrade cost is small in absolute terms (one class, one component) but non-zero and recurring, and it lands on whoever owns the admin surface — typically one or two people.
Support. Users are internal staff; the support path is our own team. Two stars and zero dependents mean no community answers, no prior-art fixes, no Stack Overflow signal. Failure presents as "the number is blank" or "the column won't sort," which arrives as a support ticket to the team least equipped to triage a Vue bundle.
Scaling. Computational scaling is a non-issue — number_format on a render path is free. The real scaling surface is organizational: number of resources × number of formatters × number of people writing displayUsing() closures by hand. Left uncentralized, that is how you get $1.2M on one dashboard and 1234567.891 on the next, which is precisely the problem this package nominally solves. The config-map-plus-trait layer delivers the consistency benefit and survives a Nova upgrade regardless of which field class sits underneath — that is the durable asset, not the package.
Failure modes, ranked by operational consequence:
Ramp-up. Using it is an hour. Owning it means understanding Nova's field lifecycle, the asset registration pipeline, and the Vue component contract well enough to fix code you didn't write during an upgrade we don't control. For a thirty-line formatter, "fork it and own it" and "write it yourself" are the same amount of work — and the second comes with no attribution obligations and no legacy constraints.
The one genuinely favorable factor: blast radius. If this breaks, internal staff see an unformatted or blank number in an admin panel. It does not touch APIs, exports, customer-facing output, or transactional data — provided display-only is enforced structurally. The moment formatting leaks into a form's write path, that containment disappears and the risk profile changes completely. Record that boundary as the decision.
Roadmap placement. Internal DX/admin backlog line item at most. It should never appear on a customer-facing roadmap. It supports one product decision — presentation consistency across operational dashboards — and explicitly does not answer "how do we accept, validate, and store monetary input." Any estimate built on the package description rather than composer.json and the field class is invalid and should be re-scoped.
How can I help you explore Laravel packages today?