aichadigital/lara100

Laravel cast for handling decimal values as base-100 integers (cents/centesimals)

Installs: 1

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/aichadigital/lara100

v1.0.2 2025-10-11 12:38 UTC

This package is auto-updated.

Last update: 2025-10-11 14:22:54 UTC


README

Tests Code Style PHPStan Latest Version

A Laravel package that provides a custom Eloquent cast for handling monetary/decimal values by storing them as integers (cents) in the database while working with decimals in your PHP code, eliminating floating-point precision errors.

Why Lara100?

Floating-point arithmetic in PHP (and most programming languages) can lead to precision errors:

0.1 + 0.2 === 0.3  // false! 😱
// Result: 0.30000000000000004

This is particularly problematic when dealing with:

  • 💰 Monetary values (prices, amounts, balances)
  • 📊 Percentages (tax rates, discounts)
  • 📏 Any centesimal measurements

Lara100 solves this by storing values as integers (cents) in the database, while letting you work with familiar decimal values in your code.

// In your database: 1999 (integer - cents)
// In your application: 19.99 (decimal - dollars/euros)

Installation

You can install the package via Composer:

composer require aichadigital/lara100

Configuration (Optional)

You can optionally publish the configuration file:

php artisan vendor:publish --tag="lara100-config"

This will create a config/lara100.php file where you can configure:

  • Rounding mode (default: PHP_ROUND_HALF_UP)
  • BCMath usage (default: false)

Alternatively, you can set these via environment variables in your .env:

# Rounding mode (default: 2 = PHP_ROUND_HALF_UP)
# 1 = PHP_ROUND_HALF_UP (standard for Spain/EU)
# 2 = PHP_ROUND_HALF_DOWN
# 3 = PHP_ROUND_HALF_EVEN (Banker's rounding for accounting)
# 4 = PHP_ROUND_HALF_ODD
LARA100_ROUNDING_MODE=1

# Enable BCMath for arbitrary precision (requires bcmath extension)
LARA100_USE_BCMATH=false

Requirements

  • PHP 8.3 or 8.4
  • Laravel 11.x or 12.x
  • Optional: BCMath extension (for high-precision calculations)

Usage

Basic Usage (Cast)

Apply the Base100 cast to your model attributes:

use AichaDigital\Lara100\Casts\Base100;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected function casts(): array
    {
        return [
            'price' => Base100::class,
            'cost'  => Base100::class,
            'tax'   => Base100::class,
        ];
    }
}

Now you can work with decimals in your application while storing integers in the database:

$product = new Product;
$product->price = 19.99;  // You set: 19.99 (decimal)
$product->save();         // DB stores: 1999 (integer cents)

echo $product->price;     // You get: 19.99 (decimal)

// Arithmetic operations work perfectly with decimals
$total = $product->price + $product->tax;  // 19.99 + 2.50 = 22.49 ✅

Advanced Usage (Trait)

For convenience, you can use the HasBase100 trait to apply the cast to multiple attributes at once:

use AichaDigital\Lara100\Concerns\HasBase100;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use HasBase100;

    protected function base100Attributes(): array
    {
        return ['price', 'cost', 'tax', 'discount'];
    }
}

The trait automatically applies the Base100 cast to all specified attributes.

How It Works

Database → Application (GET)

// Database stores: 1999 (INTEGER cents)
// Cast converts to: 19.99 (DECIMAL)
$product->price;  // 19.99

Application → Database (SET)

// Application receives: 19.99 (DECIMAL)
// Cast converts to: 1999 (INTEGER cents)
$product->price = 19.99;
$product->save();  // Stores 1999 in DB

Rounding Behavior

By default, the cast uses Round Half Up (PHP_ROUND_HALF_UP):

  • 0.5550.56 (rounds up when exactly halfway)
  • 0.5540.55

This is the standard rounding mode used in Spain, the EU, and most countries.

Configuring Rounding Mode

You can configure the rounding mode globally via config or per-attribute:

Global configuration (affects all casts):

# In .env
LARA100_ROUNDING_MODE=1  # PHP_ROUND_HALF_UP (default)

Per-attribute override (in your model):

use AichaDigital\Lara100\Casts\Base100;

protected function casts(): array
{
    return [
        'price' => Base100::class,                      // Uses config default
        'tax' => new Base100(PHP_ROUND_HALF_EVEN),      // Banker's rounding
        'discount' => new Base100(PHP_ROUND_HALF_DOWN), // Always round down
    ];
}

Available Rounding Modes

Mode Constant Behavior Use Case
Half Up PHP_ROUND_HALF_UP (1) 0.555→0.56, 0.545→0.55 Spain/EU standard
Half Even PHP_ROUND_HALF_EVEN (3) 0.555→0.56, 0.545→0.54 Accounting (Banker's)
Half Down PHP_ROUND_HALF_DOWN (2) 0.555→0.55, 0.545→0.54 Conservative rounding
Half Odd PHP_ROUND_HALF_ODD (4) 0.555→0.55, 0.545→0.55 Specialized cases

BCMath Support

For maximum precision with very large amounts, enable BCMath:

LARA100_USE_BCMATH=true

Or per-attribute:

'balance' => new Base100(useBcmath: true),

Note: Requires the bcmath PHP extension to be installed.

Examples

Working with Monetary Values

$invoice = new Invoice;
$invoice->subtotal = 100.00;  // You set: $100.00 (decimal)
$invoice->tax = 13.00;        // You set: $13.00 (decimal)
$invoice->total = 113.00;     // You set: $113.00 (decimal)
$invoice->save();             // DB stores: 10000, 1300, 11300 (integers)

// Calculate percentage (works naturally with decimals)
$taxRate = ($invoice->tax / $invoice->subtotal) * 100;  // 13%

// Display to user (already a decimal!)
$formatted = '$' . number_format($invoice->total, 2);  // "$113.00"

Performing Calculations

$product = Product::find(1);  // price = 19.99 (DB has 1999)
$quantity = 3;

$lineTotal = $product->price * $quantity;  // 59.97 (19.99 × 3)
$discount = 5.00;                          // $5.00 discount
$finalTotal = $lineTotal - $discount;      // 54.97 ✅

// Works naturally with decimal arithmetic!

Handling Edge Cases

// Zero values
$product->price = 0.00;  // Stores 0 in DB

// Negative values (refunds, discounts)
$refund->amount = -25.00;  // Stores -2500 in DB (negative cents)

// Large numbers
$property->price = 500000.00;  // Stores 50000000 in DB ($500,000.00)

Database Schema

Your database columns should be defined as INTEGER (to store cents):

Schema::create('products', function (Blueprint $table) {
    $table->id();
    $table->integer('price')->default(0);  // Stores cents: 1999 = $19.99
    $table->integer('cost')->default(0);   // Stores cents: 1500 = $15.00
    $table->integer('tax')->default(0);    // Stores cents: 250 = $2.50
    $table->timestamps();
});

Why INTEGER instead of DECIMAL?

  • ✅ Better performance (integer operations are faster)
  • ✅ Less storage space
  • ✅ No floating-point precision issues at database level
  • ✅ Compatible with all database engines

Comparison with Alternatives

Solution DB Column Type PHP Value Type Precision Package Size
Lara100 INTEGER (cents) float (19.99) ✅ Perfect Lightweight cast
moneyphp/money INTEGER (cents) Money object ✅ Perfect Full-featured library
brick/money INTEGER (cents) Money object ✅ Perfect Full-featured library
Native DECIMAL DECIMAL(10,2) float (19.99) ⚠️ Precision issues No package needed

Key Differences:

  • Lara100: Lightweight cast that stores integers in DB (efficient), but lets you work with decimals in PHP (natural)
  • Money libraries: Full-featured libraries with objects, currency conversion, formatting, etc.
  • Native DECIMAL: Traditional approach, works with floats in PHP (precision issues)

Choose Lara100 when:

  • ✅ You want a simple, Laravel-native solution
  • ✅ You prefer working with familiar decimal values (19.99)
  • ✅ You want efficient integer storage in the database
  • ✅ You don't need currency conversions or complex money operations
  • ✅ You want to avoid float precision errors without heavy dependencies

Consider alternatives when:

  • ❌ You need multi-currency support
  • ❌ You need complex monetary operations (allocation, distribution, rounding strategies)
  • ❌ You prefer working with Money objects instead of scalar decimals
  • ❌ You need advanced formatting and localization features

Testing

The package includes comprehensive tests for both the cast and trait:

composer test

Run tests with coverage:

composer test-coverage

Run tests in parallel:

composer test-parallel

Code Quality

Run PHPStan static analysis:

composer phpstan

Run Laravel Pint code formatter:

composer format

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Security Vulnerabilities

If you discover a security vulnerability, please send an e-mail to Abdelkarim Mateos Sanchez via abdelkarim.mateos@castris.com.

Credits

License

The MIT License (MIT). Please see License File for more information.

About AichaDigital

AichaDigital is a ITt company focused on IT services.