assoconnect/php-percent
Small PHP library to represent and work with percentages in a way that pairs well with MoneyPHP, based on Frederik Bosch’s proposal. Install via Composer and use the PHPDoc-documented API to apply percent calculations safely alongside Money values.
Installation
composer require assoconnect/php-percent
Ensure moneyphp/money is also installed (dependency):
composer require moneyphp/money
Basic Usage
use Assoconnect\Percent\Percent;
use Money\Money;
$amount = Money::USD(100);
$percent = new Percent(20); // 20%
$result = $percent->of($amount); // Returns Money::USD(20)
First Use Case Calculate a 15% discount on a $100 product:
$productPrice = Money::USD(100);
$discountPercent = new Percent(15);
$discountAmount = $discountPercent->of($productPrice);
Percentage Calculations
$taxRate = new Percent(8.25); // 8.25% tax
$taxAmount = $taxRate->of($subtotal);
$total = $subtotal->add($taxAmount);
Dynamic Percentages Useful for tiered pricing or dynamic discounts:
$discountRates = [10, 15, 20]; // Tiered discounts
$discount = new Percent($discountRates[$userTier]);
Reverse Calculations Find the original amount given a percentage and result:
$percent = new Percent(25);
$original = $percent->reverseOf(Money::USD(50)); // Returns Money::USD(200)
Laravel Request Binding Bind a percentage input from a form:
use Illuminate\Http\Request;
use Assoconnect\Percent\Percent;
public function update(Request $request) {
$percent = new Percent($request->input('percentage'));
// Use $percent in calculations
}
Service Layer Abstraction Encapsulate percentage logic in a service:
class DiscountService {
public function applyDiscount(Money $amount, int $percentage) {
return (new Percent($percentage))->of($amount);
}
}
Validation Validate percentage inputs (e.g., 0-100):
$validator = Validator::make($request->all(), [
'percentage' => 'required|integer|min:0|max:100',
]);
Precision Handling
Percentages are stored as integers (e.g., 20 for 20%). Ensure inputs are integers to avoid floating-point errors:
$percent = new Percent(20.5); // May behave unexpectedly; use 205 and divide by 100 if needed.
Currency Context
The Percent class works with MoneyPHP objects, so ensure all amounts are properly instantiated with currencies:
// Avoid:
$percent->of(100); // Fails (no Money object)
// Do:
$percent->of(Money::USD(100)); // Works
Division by Zero
Avoid passing 0 to reverseOf():
$percent = new Percent(0);
$percent->reverseOf(Money::USD(50)); // Throws DivisionByZeroError
Log Calculations Log intermediate values for debugging:
$percent = new Percent(10);
$amount = Money::USD(100);
$result = $percent->of($amount);
\Log::debug("10% of {$amount->getAmount()} {$amount->getCurrency()} = {$result->getAmount()}");
Unit Testing Test edge cases (e.g., 0%, 100%, negative values):
public function testZeroPercent() {
$this->assertEquals(Money::USD(0), (new Percent(0))->of(Money::USD(100)));
}
Custom Rounding
Extend the Percent class to override rounding behavior:
class CustomPercent extends Percent {
public function of(Money $amount): Money {
$result = parent::of($amount);
return $result->round(2); // Round to 2 decimal places
}
}
Percentage Ranges Create a wrapper for range-based percentages (e.g., 10-20%):
class RangePercent {
public function __construct(private int $min, private int $max) {}
public function of(Money $amount): Money {
$randomPercent = rand($this->min, $this->max);
return (new Percent($randomPercent))->of($amount);
}
}
Laravel Macros
Add helper methods to the Percent class via Laravel macros:
Percent::macro('asDecimal', function() {
return $this->getValue() / 100;
});
// Usage:
$percent = new Percent(25);
$decimal = $percent->asDecimal(); // Returns 0.25
How can I help you explore Laravel packages today?