EasyCorp/EasyAdminBundle

Unsupported moneyphp/money package by MoneyField

Troi opened this issue · 3 comments

Troi commented

Describe the bug
I need to work with Money object in all lifecycle of entities but MoneyField doesn't support that. It is clearly issue of MoneyConfigurator.

TypeError:
Unsupported operand types: Money\Money / int

  at vendor/easycorp/easyadmin-bundle/src/Field/Configurator/MoneyConfigurator.php:54
  at EasyCorp\Bundle\EasyAdminBundle\Field\Configurator\MoneyConfigurator->configure(object(FieldDto), object(EntityDto), object(AdminContext))
     (vendor/easycorp/easyadmin-bundle/src/Factory/FieldFactory.php:107)
  at EasyCorp\Bundle\EasyAdminBundle\Factory\FieldFactory->processFields(object(EntityDto), object(FieldCollection))
     (vendor/easycorp/easyadmin-bundle/src/Factory/EntityFactory.php:43)
  at EasyCorp\Bundle\EasyAdminBundle\Factory\EntityFactory->processFields(object(EntityDto), object(FieldCollection))
     (vendor/easycorp/easyadmin-bundle/src/Controller/AbstractCrudController.php:217)
  at EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController->edit(object(AdminContext))
     (vendor/symfony/http-kernel/HttpKernel.php:181)
  at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
     (vendor/symfony/http-kernel/HttpKernel.php:76)
  at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
     (vendor/symfony/http-kernel/Kernel.php:197)
  at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
     (vendor/symfony/runtime/Runner/Symfony/HttpKernelRunner.php:35)
  at Symfony\Component\Runtime\Runner\Symfony\HttpKernelRunner->run()
     (vendor/autoload_runtime.php:29)
  at require_once('/var/www/html/vendor/autoload_runtime.php')
     (public/index.php:5)

To Reproduce
Composer: moneyphp/money: v4.5.0
MoneyType is my Docrine Mapper between DB and Money object.
Entity:

#[ORM\Column(type: MoneyType::TYPE, nullable: true)]
    private ?Money $absoluteDiscount = null;

public function getAbsoluteDiscount(): ?Money
    {
        return $this->absoluteDiscount;
    }

    public function setAbsoluteDiscount(?Money $absoluteDiscount): static
    {
        $this->absoluteDiscount = $absoluteDiscount;

        return $this;
    }

CrudController:

yield EAField\MoneyField::new('absoluteDiscount', 'Discount')
            ->setCurrency('CZK');

It accepts int which is fine, it shouldn't relay on some 3rd party libraries tbh.

You could add some conversion between int and the Money object to your entity and be fine I think:

public function getAbsoluteDiscountInt(): ?int
{
    if (!$this->absoluteDiscount) {
       return null;
    }
   
     return (int)$this->absoluteDiscount->getAmount()
}

public function setAbsoluteDiscountInt(?int $absoluteDiscount ): self
{
    if (!$absoluteDiscount) {
       return $this;
    }

    $this->absoluteDiscount = Money::CZK($absoluteDiscount);
   
     return $this;
}

and then use that in the CRUD:

yield EAField\MoneyField::new('absoluteDiscountInt', 'Discount')
            ->setCurrency('CZK');

That probably should fix it (provided your money values do not exceed Max. 64 bit int)

Troi commented

It accepts int which is fine, it shouldn't relay on some 3rd party libraries tbh.

You could add some conversion between int and the Money object to your entity and be fine I think:

I can but it makes entities quite mess and defeats the purpose to have encapsulated inner logic.

I understand you don't want to rely on 3rd party, but optional dependency would be possible too. Or do you link my repo if I make small toolbox/extensions?

I think this good candidate to ready to go solution. Prices as int in application are antipattern and making a lot of mess in case of adding new currencies to application (and it happened on every project I worked on)

@Troi the way to make it work is by using the property path syntax:

// baseAmount is my Money instance exposed with a `getBaseAmount(): Money` method in my entity class

MoneyField::new('baseAmount.amount', 'Base Amount')
    ->setNumDecimals(2)
    ->setStoredAsCents()
    ->setCurrencyPropertyPath('baseAmount.currency.code'),