Weave Code
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

Money Laravel Package

brick/money

Brick\Money is a PHP library for precise, immutable money and currency values. It provides exact arithmetic (no float errors), explicit rounding control, and supports large amounts via brick/math, with optional GMP/BCMath acceleration.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require brick/money
    

    Ensure PHP 8.2+ is used (or downgrade for older versions if needed).

  2. First Use Case: Create a Money object from a string or numeric value with a currency code:

    use Brick\Money\Money;
    
    $price = Money::of('19.99', 'USD'); // USD 19.99
    $price = Money::ofMinor(1999, 'USD'); // USD 19.99 (from cents)
    
  3. Where to Look First:


Implementation Patterns

Core Workflows

  1. Immutable Operations: All operations return new Money instances. Avoid modifying state directly:

    $total = $subtotal->plus($tax)->minus($discount);
    
  2. Currency-Safe Arithmetic: Validate currencies before operations to avoid CurrencyMismatchException:

    if ($order->getAmount()->getCurrency() === $payment->getAmount()->getCurrency()) {
        $balance = $order->getAmount()->plus($payment->getAmount());
    }
    
  3. Contextual Money Handling: Use contexts for domain-specific rules (e.g., cash rounding for CHF):

    $cashMoney = Money::of(10, 'CHF', new CashContext(step: 5));
    
  4. Rounding Strategies: Pass RoundingMode explicitly for operations requiring precision control:

    $rounded = $amount->dividedBy(3, RoundingMode::HalfUp);
    

Integration Tips

  • Database Storage: Store Money as minor units (e.g., cents) in a numeric column to avoid floating-point issues:

    $minorAmount = $money->getAmount()->toScale(2)->toEngine()->toString();
    
  • Validation: Use isPositive() or isZero() for business logic checks:

    if (!$order->getAmount()->isPositive()) {
        throw new InvalidOrderException("Amount must be positive");
    }
    
  • API Responses: Serialize Money to JSON with currency and formatted amount:

    return response()->json([
        'amount' => $money->getAmount()->toString(),
        'currency' => $money->getCurrency()->getCode(),
    ]);
    
  • Testing: Use isEqualTo() for assertions (avoids floating-point errors):

    $this->assertTrue($expected->isEqualTo($actual));
    

Gotchas and Tips

Pitfalls

  1. Rounding Modes:

    • Gotcha: Rounding modes are not stored in Money; they must be passed to each operation.
      // ❌ Fails: No rounding mode provided
      $money->plus('0.999'); // RoundingNecessaryException
      
      // ✅ Works: Explicit rounding
      $money->plus('0.999', RoundingMode::Down);
      
    • Tip: Use RoundingMode::HalfUp (bankers' rounding) for financial calculations.
  2. Currency Mismatches:

    • Gotcha: Operations between mismatched currencies throw CurrencyMismatchException.
      $usd->plus($eur); // Throws exception
      
    • Tip: Use MoneyBag for multi-currency totals or validate currencies first.
  3. Context Inconsistency:

    • Gotcha: Operations between Money with different contexts (e.g., DefaultContext vs. CashContext) may yield unexpected results.
      $defaultMoney->plus($cashMoney); // May throw or behave unpredictably
      
    • Tip: Standardize contexts early in your workflow (e.g., convert all Money to DefaultContext before arithmetic).
  4. RationalMoney Precision:

    • Gotcha: RationalMoney avoids rounding but can’t represent infinite decimals (e.g., 1/3).
      $rational->dividedBy(3); // May throw ArithmeticException
      
    • Tip: Use toContext() to convert to Money with a defined rounding mode.
  5. Currency Updates:

    • Gotcha: ISO 4217 updates (e.g., new currencies like XBD for digital currencies) may break code using Currency::of().
      Currency::of('XBD'); // May fail in future versions
      
    • Tip: Lock to a specific brick/money version (e.g., 0.13.*) or use numeric codes for stability.

Debugging

  • Check Minor Units: Use getAmount()->toEngine()->toString() to inspect raw values:

    $money->getAmount()->toEngine()->toString(); // "12345678901234567890"
    
  • Enable GMP/BCMath: Install PHP extensions for faster calculations:

    sudo apt-get install php-gmp php-bcmath  # Linux
    

    Verify with:

    php -m | grep -E 'gmp|bcmath'
    
  • Log Rounding Decisions: Explicitly log rounding modes for auditing:

    $result = $amount->dividedBy(3, RoundingMode::Up);
    logger()->info("Rounded {$amount} / 3 to {$result} using Up mode");
    

Extension Points

  1. Custom Contexts: Extend Context for domain-specific rules (e.g., tax rounding):

    class TaxContext extends Context {
        public function __construct() {
            parent::__construct(2, 1, 100); // Scale, step, rounding increment
        }
    }
    
  2. Currency Providers: Implement IsoCurrencyProvider for custom currency sources (e.g., ERP integrations):

    class CustomCurrencyProvider implements IsoCurrencyProvider {
        public function getCurrency(string $code): Currency {
            return new Currency($code, 'Custom Currency', 2);
        }
    }
    
  3. MoneyBag Aggregation: Override MoneyBag to add custom logic (e.g., exchange rates):

    class ExchangeMoneyBag extends MoneyBag {
        public function getTotal(): Money {
            $total = parent::getTotal();
            return $total->multipliedBy($this->exchangeRate);
        }
    }
    
  4. Event Listeners: Use Laravel events to validate Money before persistence:

    // In a service provider
    Event::listen(OrderCreating::class, function (OrderCreating $event) {
        if (!$event->order->amount->isPositive()) {
            throw new \InvalidArgumentException("Amount must be positive");
        }
    });
    

Performance

  • Batch Operations: Use MoneyBag to aggregate multiple Money objects before processing:

    $bag = new MoneyBag([$order1->amount, $order2->amount]);
    $total = $bag->getTotal();
    
  • Avoid RationalMoney in Loops: Convert to Money early to reduce overhead:

    // ❌ Slow: RationalMoney in loop
    foreach ($items as $item) {
        $rational = $item->price->toRational()->multipliedBy($item->quantity);
    }
    
    // ✅ Faster: Convert to Money upfront
    foreach ($items as $item) {
        $money = $item->price->multipliedBy($item->quantity);
    }
    
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.
codraw/framework-extra-bundle
codraw/messenger
codraw/security
codraw/mailer
codraw/contracts
codraw/profiling
codraw/dependency-injection
codraw/tester
codraw/core
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony