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 Bundle Laravel Package

astina/money-bundle

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require astina/money-bundle:dev-master
    

    Add the bundle to config/bundles.php (Symfony 4+) or AppKernel.php (Symfony 3):

    Astina\Bundle\MoneyBundle\AstinaMoneyBundle::class => ['all' => true],
    
  2. Database Setup: Run migrations to create the currency_exchange_rate table:

    php bin/console doctrine:migrations:diff
    php bin/console doctrine:migrations:migrate
    
  3. First Use Case: Create exchange rates via Doctrine:

    $rate = new Astina\Bundle\MoneyBundle\Entity\CurrencyExchangeRate();
    $rate->setFromCurrency('USD');
    $rate->setToCurrency('EUR');
    $rate->setRate(0.85); // 1 USD = 0.85 EUR
    $em->persist($rate);
    $em->flush();
    
  4. Basic Conversion: Inject the converter service:

    $money = new \Money\Money(100, 'USD');
    $converter = $this->container->get('astina_money.money_converter');
    $eurMoney = $converter->convert($money, 'EUR');
    

Implementation Patterns

Core Workflows

  1. Currency Conversion:

    • Use the MoneyConverter service for all conversions:
      $converter->convert($money, 'JPY'); // Automatically fetches rate from DB
      
    • Handle missing rates gracefully (throws CurrencyExchangeRateNotFoundException by default).
  2. Entity Integration:

    • Attach Money objects to Doctrine entities:
      use Astina\Bundle\MoneyBundle\Type\MoneyType;
      
      /**
       * @ORM\Column(type="money")
       */
      private $price;
      
    • Use the MoneyType for form handling:
      $builder->add('price', MoneyType::class, [
          'currency' => 'USD',
          'scale' => 2,
      ]);
      
  3. Batch Processing:

    • Preload exchange rates for performance:
      $rates = $em->getRepository(CurrencyExchangeRate::class)
          ->findBy(['fromCurrency' => 'USD']);
      $converter->setExchangeRates($rates); // Cache rates in memory
      

Integration Tips

  • APIs: Serialize Money objects with custom JSON encoders:
    $encoder = new \Astina\Bundle\MoneyBundle\Serializer\MoneyEncoder();
    $serializer->encode($money, 'json');
    
  • Commands: Use the converter in CLI tasks:
    $converter = $this->get('astina_money.money_converter');
    $converted = $converter->convert($inputMoney, 'GBP');
    
  • Events: Listen for money.conversion events to log/audit conversions:
    $dispatcher->addListener('money.conversion', function ($event) {
        // Log conversion details
    });
    

Gotchas and Tips

Pitfalls

  1. Missing Exchange Rates:

    • Always validate rates exist before conversion:
      if (!$converter->hasRate('USD', 'CAD')) {
          throw new \RuntimeException("Exchange rate not configured");
      }
      
    • Default behavior throws exceptions; override with custom logic:
      $converter->setFallbackStrategy(function ($money, $toCurrency) {
          return $money; // Return original if rate missing
      });
      
  2. Database Locking:

    • Concurrent conversions may cause deadlocks if rates are frequently updated. Use transactions:
      $em->beginTransaction();
      try {
          $rate = $em->find(CurrencyExchangeRate::class, $id);
          $rate->setRate($newRate);
          $em->flush();
          $em->commit();
      } catch (\Exception $e) {
          $em->rollback();
          throw $e;
      }
      
  3. Precision Loss:

    • Configure scale in MoneyType to avoid floating-point errors:
      $builder->add('price', MoneyType::class, [
          'scale' => 4, // For micro-currency precision
      ]);
      

Debugging

  • Rate Lookup: Enable debug mode to log rate queries:
    $converter->setDebug(true);
    
  • Doctrine Events: Subscribe to prePersist/preUpdate to validate Money objects:
    $em->getEventManager()->addEventListener(
        \Doctrine\ORM\Events::prePersist,
        function ($event) {
            $entity = $event->getObject();
            if ($entity instanceof HasMoney) {
                $entity->getMoney()->validate(); // Custom validation
            }
        }
    );
    

Extension Points

  1. Custom Rate Sources:

    • Implement Astina\Bundle\MoneyBundle\Rate\RateProviderInterface:
      class ApiRateProvider implements RateProviderInterface {
          public function getRate(string $from, string $to): float {
              return $this->fetchFromExternalApi($from, $to);
          }
      }
      
    • Register with the converter:
      # config/services.yaml
      Astina\Bundle\MoneyBundle\MoneyConverter:
          arguments:
              $rateProviders: ['@api_rate_provider']
      
  2. Custom Money Types:

    • Extend MoneyType for domain-specific validation:
      class ProductPriceType extends MoneyType {
          public function configureOptions(OptionsResolver $resolver) {
              $resolver->setDefaults([
                  'currency' => 'USD',
                  'min_value' => new Money(0, 'USD'),
              ]);
          }
      }
      
  3. Fallback Strategies:

    • Override default behavior for unsupported currencies:
      $converter->setFallbackStrategy(function ($money, $toCurrency) {
          if ($toCurrency === 'XBT') { // Bitcoin
              return $money->multiply(0.00005); // Arbitrary fallback
          }
          return $money;
      });
      

Configuration Quirks

  • Symfony Flex: The bundle lacks autoconfiguration. Manually define services in config/packages/astina_money.yaml:
    astina_money:
        default_currency: 'USD'
        rate_providers:
            - '@database_rate_provider'
    
  • Doctrine DBAL: If using non-ANSI SQL databases, ensure the money DBAL type is registered:
    $connection->getDatabasePlatform()->registerDoctrineTypeMapping('money', MoneyType::class);
    
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.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle