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

Laravel Money Laravel Package

elegantly/laravel-money

Laravel integration for brick/money: Eloquent casting to Brick\Money\Money, safe parsing from strings/ints/floats, and a ValidMoney validation rule with min/max bounds and nullability. Store amounts cleanly (with currency column or fixed currency).

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Installation: Add the package via Composer:
    composer require elegantly/laravel-money
    
  2. Publish Config (Optional): Customize defaults (e.g., default_currency) with:
    php artisan vendor:publish --tag="money-config"
    
  3. First Use Case: Cast a model attribute to Money in your Invoice model:
    use Elegantly\Money\MoneyCast;
    
    protected $casts = [
        'amount' => MoneyCast::of('currency'), // Dynamic currency
        // OR
        'price' => MoneyCast::of('USD'),      // Fixed currency
    ];
    
  4. Database Setup: Use bigInteger for amounts and string for currency (ISO 4217):
    Schema::create('invoices', function (Blueprint $table) {
        $table->bigInteger('amount');   // e.g., 1000 = $10.00
        $table->string('currency', 3); // 'USD', 'EUR'
    });
    

Implementation Patterns

1. Model Casting Workflows

  • Dynamic Currency: Reference a currency column for flexible multi-currency models:

    class Invoice extends Model {
        protected $casts = [
            'amount' => MoneyCast::of('currency'), // Uses `currency` column
        ];
    }
    

    Use case: E-commerce platforms with variable currency support.

  • Fixed Currency: Hardcode currency for models with static currencies:

    class Subscription extends Model {
        protected $casts = [
            'price' => MoneyCast::of('EUR'), // Always EUR
        ];
    }
    

    Use case: SaaS subscriptions tied to a single currency.

2. Parsing User Input

Convert strings/ints/floats to Money safely:

use Elegantly\Money\MoneyParser;

// API/Request handling
$amount = MoneyParser::parse($request->input('price'), 'USD');

// Form validation (Livewire/Form Requests)
$validated = $request->validate([
    'price' => ['required', new ValidMoney(min: 0, max: 1000)],
]);

Tip: Use MoneyParser in setters or accessors to sanitize input early.

3. Validation Integration

  • Form Requests:
    public function rules() {
        return [
            'price' => [new ValidMoney(nullable: false, min: 0, currency: 'USD')],
        ];
    }
    
  • Livewire:
    #[Validate([new ValidMoney(min: 0, max: 100)])]
    public ?int $price = null;
    

Pattern: Centralize validation logic in a base request class or trait.

4. Database Operations

  • Precision Queries: Use Brick\Money methods in queries:
    $invoices = Invoice::where('amount', '>=', Money::of(100, 'USD'))
                       ->get();
    
  • Aggregations: Sum amounts with Money arithmetic:
    $total = Invoice::sum('amount'); // Returns Money object
    

5. Testing

Mock Money objects in tests:

public function test_invoice_total() {
    $invoice = new Invoice(['amount' => Money::of(100, 'USD')]);
    $this->assertEquals('100.00', $invoice->amount->getAmount()->toString());
}

Tip: Use Money::of() for test data to ensure consistency.


Gotchas and Tips

Pitfalls

  1. Floating-Point Serialization:

    • Issue: MoneyCast serializes to float (e.g., 100.00) instead of formatted strings.
    • Fix: Override serialize in your model if you need formatted output:
      public function getAmountAttribute($value) {
          return $value->getAmount()->toString();
      }
      
  2. Currency Column Mismatch:

    • Issue: If the currency column is missing or invalid, MoneyCast throws an exception.
    • Fix: Add a default currency in config/money.php or handle missing columns:
      $casts = [
          'amount' => MoneyCast::of('currency', default: 'USD'),
      ];
      
  3. Validation Edge Cases:

    • Issue: ValidMoney may reject valid inputs if min/max are set as floats (e.g., 0.99).
    • Fix: Use integers or strings for bounds (v2.3.1+ supports decimals):
      new ValidMoney(min: '0.99', max: '100.00')
      
  4. Database Schema Mismatch:

    • Issue: Using decimal instead of bigInteger for amounts can introduce precision errors.
    • Fix: Stick to bigInteger (e.g., 10000 = $100.00) and let Money handle formatting.

Debugging Tips

  • Check Parsing: Use MoneyParser::parse() directly to debug input:
    dd(MoneyParser::parse($userInput, 'USD'));
    
  • Inspect Casts: Verify model casts with:
    dd($model->getCasts());
    
  • Log Money Objects: Override toArray() for debugging:
    public function toArray() {
        return array_merge(parent::toArray(), [
            'amount_debug' => $this->amount->getAmount()->toString(),
        ]);
    }
    

Extension Points

  1. Custom Rounding:

    • Override MoneyParser rounding mode (v2.3.2+):
      config(['money.rounding_mode' => \Brick\Math\RoundingMode::UP]);
      
  2. Additional Validation:

    • Extend ValidMoney for custom rules (e.g., tax compliance):
      class ValidTaxAmount extends ValidMoney {
          public function passes($attribute, $value) {
              return parent::passes($attribute, $value) &&
                     $value->getAmount() >= Money::of(10, 'USD');
          }
      }
      
  3. Currency Conversion:

    • Integrate with a service like brick/math or a third-party API:
      use Brick\Money\Money;
      
      $usd = Money::of(100, 'USD');
      $eur = $usd->convertTo('EUR'); // Requires exchange rate logic
      

Performance Considerations

  • Avoid Over-Casting: Only cast columns that need Money behavior (e.g., skip created_at).
  • Batch Operations: Use Money arithmetic in queries sparingly—fetch data first for complex calculations:
    // Slow: Avoid in loops
    Invoice::where('amount', '>=', Money::of(100, 'USD'))->get();
    
    // Fast: Fetch first, then filter
    $invoices = Invoice::all();
    $filtered = $invoices->filter(fn($i) => $i->amount >= Money::of(100, 'USD'));
    

Migration Quirks

  • Existing Data: Convert legacy float/decimal columns to bigInteger:
    // Migration
    Schema::table('invoices', function (Blueprint $table) {
        $table->bigInteger('amount')->default(0);
    });
    
    // Update data
    Invoice::query()->update([
        'amount' => Invoice::selectRaw('ROUND(amount * 100)')->value('amount'),
    ]);
    
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.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky