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

Absolute Percent Value Bundle Laravel Package

assoconnect/absolute-percent-value-bundle

Represents either an absolute value or a percent, with a type (PERCENT or ABSOLUTE) and a positive numeric string value; percent values must be under 10000.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install

composer require assoconnect/absolute-percent-value-bundle

PHP 8.2+ is required (support for older versions was dropped in v1.4.0). The package is tiny — no Laravel service provider exists, nothing to register, nothing to publish.

2. Read one file first

Ignore the bundle plumbing (DependencyInjection/, the Symfony serializer normalizer) and open src/Object/AbsolutePercentValue.php. That class is the package. Budget ten minutes: check the constructor/factory names, the constants for PERCENT / ABSOLUTE, the getType()/getValue() accessors, and the exact exception thrown on invalid input. Everything else in this assessment follows from that file.

3. Understand the unit before writing a line of code

The README's constraint — "value must be less than 10000 (100%)" — tells you the value is stored in basis points, not percent. 100 means 1%, 1500 means 15%. The value is always a string (a numeric positive string), never a float, and it carries no currency.

4. First use case: a discount that can be "$10 off" or "15% off"

This is the whole reason the package exists — one column/field that models two shapes of the same business concept.

use Assoconnect\AbsolutePercentValueBundle\Object\AbsolutePercentValue; // verify namespace in the file header

$fixed   = AbsolutePercentValue::absolute('1000'); // 10.00 in your minor unit
$percent = AbsolutePercentValue::percent('1500');  // 15% (basis points)

Then apply it yourself — the package deliberately does not know how to apply a value to a base amount:

function discountedTotal(AbsolutePercentValue $v, int $baseMinor): int
{
    return $v->getType() === AbsolutePercentValue::PERCENT
        ? intdiv($baseMinor * (int) $v->getValue(), 10000)
        : max(0, $baseMinor - (int) $v->getValue());
}

Wire the first use case into a single feature (a coupon or a promotion on one model), assert the boundary values (0, 100, 10000), and only then think about persisting it.

Implementation Patterns

Persist it as two columns, not one

Store discount_type (PERCENT/ABSOLUTE) and discount_value (string/unsignedBigInteger) side by side. This keeps your SQL sortable/filterable and avoids lock-in to the package's serialization format. A JSON blob couples your schema to a one-star dependency.

Custom Eloquent cast at the boundary

class AbsolutePercentValueCast implements CastsAttributes
{
    public function get($model, $key, $value, $attributes): ?AbsolutePercentValue
    {
        $type = $attributes[$key.'_type'] ?? null;
        if ($type === null || $value === null) {
            return null;
        }

        return AbsolutePercentValue::{$type === 'PERCENT' ? 'percent' : 'absolute'}($value);
    }

    public function set($model, $key, $value, $attributes): array
    {
        return [
            $key.'_type'  => $value?->getType(),
            $key         => $value?->getValue(),
        ];
    }
}

Validate in the FormRequest, construct in the DTO

Don't let InvalidArgumentException leak as a 500. Validate the shape with Laravel's rules, then construct the VO inside a try in your DTO/action and convert the failure into a validation error:

'type'  => ['required', Rule::in(['PERCENT', 'ABSOLUTE'])],
'value' => ['required', 'string', 'regex:/^\d+$/'],
// plus: if type === PERCENT, value must be < 10000

Rules like required_if / prohibited_if on the type field get you most of the way; the package's own check is the second line of defence, not the first.

Normalize at the edge, compute with integers

Treat the VO as a transport/domain-boundary type. The moment a service needs to do arithmetic, flatten it to an int (plus an explicit currency/unit) and work with intdiv/bcmath. Never let the VO wander into a pricing engine.

Serialize explicitly in API Resources

return ['type' => $this->discount->getType(), 'value' => (string) $this->discount->getValue()];

Do not rely on the bundled Symfony normalizer. Laravel doesn't autowire Symfony's serializer, so unless you install and configure symfony/serializer yourself, that integration point is inert — and it drags a framework you don't use into your dependency graph.

Pair with a money library for anything user-facing

The VO has no currency, no rounding, no precision policy. For 15% off €19.99, use moneyphp/money or brick/money for the amount and rounding mode, and use this package only to describe which kind of adjustment is being applied.

Keep it behind your own interface

interface DiscountValue { public function apply(int $baseMinor): int; }

One adapter class wraps AbsolutePercentValue. When this package dies (see below), you swap a 40-line enum + readonly class in behind the same interface and nothing else changes.

Gotchas and Tips

  • It is a Symfony bundle in a Laravel app. There is no ServiceProvider, no config merge, no DI extension that Laravel will ever run. Autoloading works fine, but ~half the package is dead weight. Vendor-lock risk is low precisely because so little of it is used.
  • Basis points, not percent. 1500 = 15%, 100 = 1%. If your forms accept "15" for 15%, multiply by 100 on the way in — or you'll ship a 0.15%-off bug that QA will not catch. Put the conversion in exactly one place.
  • 10000 is the boundary. README says the percent value must be less than 10000, so a literal 100% may throw. Test it; if you need "100% off", special-case it outside the VO or fork the check.
  • The value is a string. Don't (float) it — you'll lose precision and reintroduce the float-money bug the design was avoiding. Cast to int and use intdiv, or use bcmath if you allow fractional basis points.
  • No negatives. Refunds, surcharges, and adjustments in the negative direction cannot be represented. Track the sign in your own column/service and keep the VO magnitude-only.
  • No "apply" logic, no currency, no rounding. Every calculation helper is yours to write and yours to test. The package validates a value; it doesn't compute anything.
  • Unknown factory API. Release notes mention fixing a denormalize return type, which implies static factories plus a serializer normalizer. Confirm the exact factory method names in src/Object/AbsolutePercentValue.php before writing ten call sites — the README doesn't document them.
  • No tests you can lean on. With one star and zero dependents, assume the edge cases (zero, empty string, "1.5", leading +, huge values) are unverified. Write your own table-driven tests around those inputs and keep them adjacent to your cast.
  • Upgrade cadence is a solo maintainer's cadence. Minor releases have bumped PHP and Symfony floors. Pin with "assoconnect/absolute-percent-value-bundle": "^1.5" and let CI tell you when it breaks, rather than floating *.
  • Debugging tip: when a value "mysteriously" fails to construct, dump the raw attribute before the cast runs — nine times out of ten it's a 100 vs 10000 scaling mistake upstream in a form request, not a package bug.
  • Extension points: effectively none. The object looks intended to be immutable/final, so customization means decoration (wrap it) or a fork. Prefer the wrapper — the MIT license lets you fork, but a fork of a one-star repo is maintenance you didn't budget for.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource