Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Percentage Laravel Package

satoved/percentage

satoved/percentage offers simple helpers for percentage calculations in PHP and Laravel, including percent of a value, percentage change, discounts, and formatted output, with a clean API for everyday pricing and reporting tasks.

View on GitHub
Deep Wiki
Context7

Getting Started

Step 1 — Install and immediately identify what you actually got.

composer require satoved/percentage:1.0.0

Pin an exact version from day one. With 7 stars and 0 dependents, a floating ^1.0 constraint buys you nothing and can silently change stored-unit behavior on a composer update.

There is no service provider to register, no config to publish, no migration to run. Before writing any code, open composer.json and src/ in vendor/satoved/percentage. You are looking for one of two things:

What you find What you have
A class extending Laravel\Nova\Fields\Field A Nova UI field — presentation only
Static helpers like Percentage::of(), Percentage::change() A math utility — the "Nova field" metadata is wrong
Both Two separate dependencies with two separate risk profiles

The package description and its registry metadata disagree about what it is. Resolve that ambiguity in the first ten minutes by reading the source, not the README.

Step 2 — First use case: one percentage input on one admin resource.

use Satoved\Percentage\Percentage;

public function fields(NovaRequest $request): array
{
    return [
        Percentage::make('Discount Rate')
            ->rules('required', 'numeric', 'min:0', 'max:100'),
    ];
}

Step 3 — Verify persistence before you build anything else. This is the real first task, and it takes longer than the install:

  1. Submit 12.5 through the Nova form and read the raw database column. Is it 12.5 (0–100 scale) or 0.125 (0–1 scale)?
  2. curl the endpoint directly with -5, 150, and abc. Does server-side validation reject them, or is the field relying on a JavaScript mask?

Do not proceed until you can answer both. The 0.125-vs-12.5 unit question is a hundred-fold error that throws no exception and fails no validation — it surfaces weeks later as "why is every discount showing as 0.12%".

Step 4 — If it's the calculator, verify semantics.

Percentage::of(25, 200);        // 12.5
Percentage::change(80, 100);    // 25
Percentage::apply(200, 12.5);   // 225

Check the return type (float vs int), the rounding convention (half-up? truncation?), and behavior at 0, with negatives, and with large values. Budget half a day total for the spike. The package is tiny; the verification is the work.

Implementation Patterns

Wrap the field class before using it in more than one resource.

namespace App\Nova\Fields;

class InternalPercentage extends \Satoved\Percentage\Percentage
{
    public function __construct($name, $attribute = null, $resolveCallback = null)
    {
        parent::__construct($name, $attribute ?? \Str::snake($name), $resolveCallback);

        $this->rules('numeric', 'min:0', 'max:100');
        $this->step(0.01);
    }
}

Every resource then references App\Nova\Fields\InternalPercentage. When Nova 5 breaks the vendor class, when you need to fix locale handling, or when you decide to vendor the source entirely, it's one file changed instead of a grep across twenty resources. This is the single highest-leverage pattern with this package.

Normalize units at the model boundary, not in the field.

If the field writes 0–1 but your domain speaks 0–100, convert in an accessor/mutator so the convention lives in one auditable place:

protected function discountRate(): Attribute
{
    return Attribute::make(
        get: fn ($value) => $value === null ? null : $value * 100,
        set: fn ($value) => $value === null ? null : $value / 100,
    );
}

This keeps the domain layer independent of the package and makes the unit assumption explicit and testable. It also means a Nova upgrade or a package swap doesn't touch your business logic.

Put the calculator behind an interface if you adopt it at all.

interface PercentageCalculator
{
    public function of(float $value, float $percent): float;
    public function change(float $from, float $to): float;
    public function apply(float $amount, float $percent): float;
    public function reverse(float $amount, float $percent): float;
}

Bind it in a service provider and inject it. Rounding policy belongs in the implementation — not duplicated between Nova display formatting and domain calculations. If the package is abandoned, you swap one class for a bcmath implementation and nothing else moves.

Use a small, fixed vocabulary in day-to-day code. Four operations cover nearly everything on an admin surface:

  • of($part, $whole) — completion values, allocation shares.
  • change($from, $to) — variance and month-over-month reporting.
  • apply($amount, $percent) — discounts, tax, markups.
  • reverse($amount, $percent) — recovering a base value from a grossed-up figure.

Route all four through your own service. Don't let raw Percentage::of() calls spread through controllers and Blade templates; that turns a 200-line utility into a hidden dependency with no single place to audit.

Pilot on one low-traffic resource and check every screen. Index, detail, create, and update. Nova fields routinely render correctly on detail and break on index — those are different Vue components and different serialization paths.

Write characterization tests around persistence on day one.

public function test_percentage_field_persists_expected_units(): void
{
    // Assert the database column for: 0, 0.5, 12.5, 100,
    // comma-decimal "12,5", out-of-range 150, non-numeric "abc"
}

Assert what lands in the column, not what the form displays. This is the only thing that reliably catches a silent unit error, and it's cheap — write it before the feature.

Gotchas and Tips

Client-side masking is not validation. A JavaScript mask is bypassed by DevTools, CSV imports, artisan tinker, and queued jobs writing to the same column. Your ->rules() on the field is the only server-side gate, and if the field's fluent ->min()/->max()/->step() translate only to HTML attributes, they enforce nothing. Test the endpoint with curl, never trust the UI.

Comma-decimal locales will bite you. A French or German user typing 12,5 may have the value silently truncated to 12, rejected, or passed through as a string depending on the field's JS and how Nova serializes the payload. Test with a non-en_US locale before you claim international support — and if you can't test it, don't claim it.

Find out where rounding happens. Hydration, dehydration, and display are three distinct places. If rounding occurs in more than one, you get drift. If you need round-half-up on persist, do it in the mutator rather than hoping the field does it. Keep the rounding policy in exactly one place and test it there.

Nova major upgrades will break this, probably silently. The Nova 3 → 4/5 transition moved to Vue 3 and a new asset pipeline. A single-maintainer micro-package with 7 stars will almost certainly not receive a patch. Symptoms are a blank field, a raw unstyled <input>, or a Vue console error — and you'll find them in production admin, not in CI. Add "open every resource using InternalPercentage and confirm render + persist" to your Nova upgrade checklist.

No upstream support, no dependents as an early warning. No issue history, no production forks, no blog posts. When it breaks, you are the maintainer. Because you wrapped it on day one, composer require can be replaced with a copy of the source into app/Nova/Fields/ in about five minutes. Keep that exit path open.

Keep the calculator out of anything financial. Percentages for pricing, billing, commissions, or compliance belong in your domain layer with an explicit, tested rounding policy. The Nova field is presentation. A wrong percentage on an admin screen should be visibly wrong and staff-correctable — not silently wrong and propagating into a customer-facing export.

Prefer writing your own if the spike shows semantic mismatches. A percentage Nova field is roughly a hundred lines of PHP plus a small Vue component. If the storage unit is wrong for you, server-side validation is absent, or locale handling is broken, patching the vendor is more work than writing it — and a custom App\Nova\Fields\Percentage removes the single-maintainer risk, the Nova upgrade risk, and the metadata ambiguity in one move. Greenlight for internal admin screens; decline for anything where a silent unit error becomes a revenue incident.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core