Skip to main content
The AsDecimalMoney cast stores monetary values in their major units as decimal numbers (e.g., 10.00 for ten dollars). This approach provides human-readable values in the database but requires careful handling of decimal precision.
AsIntegerMoney is recommended for most use cases. Use AsDecimalMoney only when you specifically need decimal storage for legacy systems or human-readable database values.

How It Works

The cast converts between Money objects in your PHP code and decimal values in your database:
  • Storage: Money::of(10.00, 'USD')10.00 (stored as decimal)
  • Retrieval: 10.00Money::of(10.00, 'USD') (Money object)

Implementation

The AsDecimalMoney class uses the AsMoneyCast trait and implements two key methods:
AsDecimalMoney.php
protected function serializeMoney(Money $value): string
{
    return (string) $value->getAmount();
}

protected function deserializeMoney(float|int|string $amount, string $currency): Money
{
    return Money::of($amount, $currency);
}

Usage Patterns

Store amount and currency in separate columns.

Model Definition

use Devhammed\LaravelBrickMoney\Money;
use Devhammed\LaravelBrickMoney\Casts\AsDecimalMoney;
use Illuminate\Database\Eloquent\Model;

/**
 * @property Money $amount
 * @property string $currency
 */
class Transaction extends Model
{
    protected function casts(): array
    {
        return [
            'amount' => AsDecimalMoney::of('currency'),
        ];
    }
}

Migration

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::create('transactions', function (Blueprint $table) {
    $table->id();
    $table->decimal('amount', 36, 18);  // e.g., 10.00 = $10.00
    $table->string('currency', 3);       // e.g., 'USD'
    $table->timestamps();
});
Decimal Precision: DECIMAL(36, 18) provides enough precision for cryptocurrencies like Ethereum (18 decimal places). Adjust based on your needs:
  • Standard currencies: DECIMAL(19, 4) or DECIMAL(13, 2)
  • High precision: DECIMAL(36, 18)

Usage Example

// Create
$transaction = Transaction::create([
    'amount' => Money::of(10.00, 'USD'),
]);

// The database stores:
// amount: 10.00
// currency: "USD"

// Retrieve
$transaction = Transaction::find(1);
echo $transaction->amount->format();  // "$10.00"
echo $transaction->currency;          // "USD"

Decimal Precision Guide

Choosing the right decimal precision is critical:
Use CasePrecisionExplanation
Standard currencies (USD, EUR)DECIMAL(13, 2)2 decimal places, handles up to $99 billion
High-value transactionsDECIMAL(19, 4)4 decimal places, handles larger amounts
CryptocurrenciesDECIMAL(36, 18)18 decimal places for ETH and similar
Scientific/customDECIMAL(65, 30)Maximum MySQL precision

Examples

// Standard currency - 2 decimal places
$table->decimal('price', 13, 2);  // Max: 99,999,999,999.99

// High precision - 4 decimal places
$table->decimal('price', 19, 4);  // Max: 999,999,999,999,999.9999

// Cryptocurrency - 18 decimal places
$table->decimal('amount', 36, 18);  // Handles Ethereum precision

Complete Example

use Devhammed\LaravelBrickMoney\Money;
use Devhammed\LaravelBrickMoney\Casts\AsDecimalMoney;
use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    protected function casts(): array
    {
        return [
            'subtotal' => AsDecimalMoney::of('currency'),
            'tax' => AsDecimalMoney::of('currency'),
            'total' => AsDecimalMoney::of('currency'),
        ];
    }

    public function calculateTotal(): void
    {
        $this->total = $this->subtotal->plus($this->tax);
    }
}

Querying

Querying decimal amounts is straightforward since values are stored in major units:
// Find orders over $100
$expensiveOrders = Order::where('total', '>', 100)->get();

// Find orders between $50 and $200
$midRangeOrders = Order::whereBetween('total', [50, 200])->get();

// Sum all orders in USD
$totalRevenue = Order::where('currency', 'USD')->sum('total');
echo Money::of($totalRevenue, 'USD')->format();

// Average order value
$avgOrder = Order::where('currency', 'USD')->avg('total');
echo Money::of($avgOrder, 'USD')->format();

Use Cases

When working with existing databases that store amounts as decimals:
// Existing database has decimal columns
class LegacyOrder extends Model
{
    protected function casts(): array
    {
        return [
            'price' => AsDecimalMoney::of('currency_code'),
        ];
    }
}
When business intelligence tools expect decimal values:
// BI tools can read decimal values directly
SELECT AVG(amount) as avg_transaction
FROM transactions
WHERE currency = 'USD';
When exporting data for non-technical users:
// CSV export shows: 10.00 instead of 1000
$transactions = Transaction::all();
// Export to CSV with readable amounts
When external APIs send/expect decimal amounts:
// External API sends: {"amount": 10.50, "currency": "USD"}
$transaction = Transaction::create([
    'amount' => Money::of($apiData['amount'], $apiData['currency']),
]);

Important Considerations

Floating-Point Precision: Be aware that decimal arithmetic can introduce precision errors. The Brick\Money library handles this internally, but database aggregations may not.
// This could have precision issues in pure SQL:
DB::table('orders')->sum('total');  // Use with caution

// Better approach:
$orders = Order::all();
$total = $orders->reduce(
    fn($carry, $order) => $carry?->plus($order->total) ?? $order->total
);

Best Practices

1

Choose appropriate decimal precision

Match your decimal precision to your use case:
// Too little precision
$table->decimal('amount', 10, 2);  // ⚠️ Only $99,999,999.99 max

// Good for standard currencies
$table->decimal('amount', 19, 4);  // ✅ Handles most cases

// Required for crypto
$table->decimal('amount', 36, 18); // ✅ Handles high precision
2

Use separate columns for flexibility

'amount' => AsDecimalMoney::of('currency')  // ✅ Recommended
3

Add indexes for performance

$table->decimal('amount', 19, 4)->index();
$table->string('currency', 3)->index();
4

Document precision requirements

/**
 * @property Money $amount Stored with 4 decimal places
 * @property string $currency ISO 4217 currency code
 */
class Transaction extends Model { }

Comparison: Decimal vs Integer Storage

AspectAsDecimalMoneyAsIntegerMoney
ReadabilityHigh - human-readableLow - requires conversion
PrecisionDepends on DECIMAL sizeExact for supported range
PerformanceSlower decimal arithmeticFaster integer arithmetic
StorageMore space (decimal)Less space (integer)
CompatibilityBetter with BI toolsIndustry standard
RecommendationLegacy/reporting only✅ Preferred

Migration from Integer Storage

If you’re migrating from AsIntegerMoney to AsDecimalMoney:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;

class ConvertAmountToDecimal extends Migration
{
    public function up()
    {
        // Add temporary decimal column
        Schema::table('orders', function (Blueprint $table) {
            $table->decimal('amount_decimal', 19, 4)->nullable();
        });

        // Convert: divide by 100 for 2-decimal currencies
        DB::statement('UPDATE orders SET amount_decimal = amount / 100');

        // Drop old column and rename
        Schema::table('orders', function (Blueprint $table) {
            $table->dropColumn('amount');
            $table->renameColumn('amount_decimal', 'amount');
        });
    }

    public function down()
    {
        // Reverse the migration
        Schema::table('orders', function (Blueprint $table) {
            $table->bigInteger('amount_integer')->nullable();
        });

        DB::statement('UPDATE orders SET amount_integer = amount * 100');

        Schema::table('orders', function (Blueprint $table) {
            $table->dropColumn('amount');
            $table->renameColumn('amount_integer', 'amount');
        });
    }
}

Next Steps

AsIntegerMoney Cast

Learn about the recommended integer storage

AsCurrency Cast

Cast currency codes to Currency objects

Build docs developers (and LLMs) love