TheBigBrainsCompany/TbbcMoneyBundle

Symfony From on object without setter throw "Could not determine access type for property"

Closed this issue · 2 comments

I have a form and entity:

class WarehouseProductNewType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class, [
                'label' => 'Nazwa produktu',
                'constraints' => [
                    new NotBlank(),
                    new Type('string')
                ]
            ])
            ->add('initialPurchaseNetValue', SimpleMoneyType::class, [
                'label' => 'Inicjalizująca jednostkowa cena zakupu netto',
                'amount_options' => [
                    'label' => false
                ]
            ])
            ->add('sellingNetValue', SimpleMoneyType::class, [
                'label' => 'Jednostkowa cena sprzedaży netto',
                'amount_options' => [
                    'label' => false
                ]
            ])
            ->add('submit', SubmitType::class, [
                'label' => 'Zapisz'
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => WarehouseProduct::class,
            'empty_data' => function (FormInterface $form) {
                return new WarehouseProduct(
                    $form->get('name')->getData(),
                    $form->get('initialPurchaseNetValue')->getData(),
                    $form->get('sellingNetValue')->getData()
                );
            }
        ]);
    }
}

/**
 * @ORM\Entity
 * @ORM\Table(name="warehouse_product")
 */
class WarehouseProduct
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(name="id", type="integer")
     */
    private ?int $id;

    /**
     * @ORM\Column(name="name", type="string")
     */
    private string $name;

    /**
     * @ORM\Column(name="initial_purchase_net_value", type="integer")
     */
    private string $initialPurchaseNetValue;

    /**
     * @ORM\Column(name="selling_net_value", type="integer")
     */
    private string $sellingNetValue;

    public function __construct(
        string $name,
        Money $initialPurchaseNetValue,
        Money $sellingNetValue
    ) {
        $this->name = $name;
        $this->initialPurchaseNetValue = $initialPurchaseNetValue->getAmount();
        $this->sellingNetValue = $sellingNetValue->getAmount();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getInitialPurchaseNetValue(): Money
    {
        if (!$this->initialPurchaseNetValue) {
            return Money::PLN(0);
        }

        return Money::PLN($this->initialPurchaseNetValue);
    }

    public function getSellingNetValue(): Money
    {
        if (!$this->sellingNetValue) {
            return Money::PLN(0);
        }

        return Money::PLN($this->sellingNetValue);
    }
}

Class WarehouseProduct::class is a entity that has only constructor and getters.
When form is submitted from frontend and Symfony does handleRequest and throws an exception:

Could not determine access type for property "initialPurchaseNetValue" in class "App\Entity\Warehouse\Product\WarehouseProduct".
Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException

The only solution I found is that form implements interface Symfony\Component\Form\DataMapperInterface...
Methods forced by the interface do not have to do anything at all and everything works!...

class WarehouseProductNewType extends AbstractType implements DataMapperInterface
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class, [
                'label' => 'Nazwa produktu',
                'constraints' => [
                    new NotBlank(),
                    new Type('string')
                ]
            ])
            ->add('initialPurchaseNetValue', SimpleMoneyType::class, [
                'label' => 'Inicjalizująca jednostkowa cena zakupu netto',
                'amount_options' => [
                    'label' => false
                ]
            ])
            ->add('sellingNetValue', SimpleMoneyType::class, [
                'label' => 'Jednostkowa cena sprzedaży netto',
                'amount_options' => [
                    'label' => false
                ]
            ])
            ->add('submit', SubmitType::class, [
                'label' => 'Zapisz'
            ])
            ->setDataMapper($this)
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => WarehouseProduct::class,
            'empty_data' => function (FormInterface $form) {
                return new WarehouseProduct(
                    $form->get('name')->getData(),
                    $form->get('initialPurchaseNetValue')->getData(),
                    $form->get('sellingNetValue')->getData()
                );
            }
        ]);
    }

    public function mapDataToForms($viewData, iterable $forms): void
    {
        //do nothing
    }

    public function mapFormsToData(iterable $forms, &$viewData): void
    {
        //do nothing
    }
}

This solution does not satisfy me, is there any other way to do it?

@hanishsingla I am a bit lost with the form implementation (didn't use it myself). Any idea about this one or comments here?

@GSwidnicki Please reopen if this is still an issue on the latest version, closing this now