Symfony 6 Custom NumberType Field

33 views Asked by At

I'm trying to "autofill" a NumberType Field. I thought I needed to make a custom FieldType to do this, but this approach is not working. I'm using Easy Admin 4. Here's the scenario:

I have a Sale class with a form. A user may select items to put on the sale. I'd like numberFields like "subtotal" and "total" to repopulate as items are added.

In the sale crud controller I have:

yield NumberField::new('saleAmount')
                ->setFormTypeOption(
                        'disabled',
                        true
                    );

That's using what's built in. Can I get the data I need to populate this field straight from the crud controller, use a function to total up the amounts and then used something like 'data => $subtotal' ? Or, do I need a custom field type? I'm really not sure of the correct approach for this so I would appreciate all the feedback I can get.

Update I've gotten further

<?php

namespace App\Form;

use App\Entity\Sale;
use App\Entity\SaleLineItems;
use App\Entity\Inventory;
use App\Form\SaleItemType;
use App\Form\DataTransformer\SaleToStringTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\AssociationType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\ArrayType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\ChoiceList\ChoiceList;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\CallbackTransformer;
use Doctrine\ORM\EntityManagerInterface;

class SubtotalType extends AbstractType
{

    public function __construct(private EntityManagerInterface $entityManager, private SaleToStringTransformer $transformer,)
    {
        $this->entityManager = $entityManager;
        $this->transformer = $transformer;
    }

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('saleAmount', NumberType::class, ['empty_data' => ''])
                ->add('save', SubmitType::class, ['label' => 'Update Subtotal']);
        
        $builder->get('saleAmount')->addModelTransformer($this->transformer);

        $builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) use ($user): void {
            if ($event->getForm()->getParent()->getData()->getSaleLineItems()['elements'] !== null) {
                // we don't need to add the friend field because
                // the message will be addressed to a fixed friend
                $subtotal = 0;
                //dd($event->getForm()->getParent()->getData()->getSaleLineItems());
                $items = $event->getForm()->getParent()->getData()->getSaleLineItems();
                dd($items['elements']);
                $price = $rawPrice/100; 
                    
                $form = $event->getForm();
                $item = $event->getData()->getItem();
                $formOptions = [
                    'empty_data' => $subtotal,
                ];

                // create the field, this is similar the $builder->add()
                // field name, field type, field options
                    $form->remove('saleAmount');
                    $form->add('saleAmount', NumberType::class, $formOptions);
                
            }
            else{
                $subtotal = 0;
                $form = $event->getForm();
                $formOptions = [
                    'empty_data' => $subtotal,
                ];
                
                $form->add('saleAmount', NumberType::class, $formOptions);
            }
        });
        $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event): void {
            $sale = $event->getForm()->getParent()->getData();
            $form = $event->getForm();
    
            // checks whether the user has chosen to display their email or not.
            // If the data was submitted previously, the additional value that is
            // included in the request variables needs to be removed.
            if ($sale->getsaleLineItems()['elements'] !== []) {
                $items = $sale->getsaleLineItems()['elements'];
                $subtotal = 0;
                foreach($items as $item){
                    $price = $item->getPrice();
                    $subtotal = $subtotal + $price;
                };

                $form->get('saleAmount')->setData($subtotal);

            } else {
                $subtotal = 0;
                $form->get('saleAmount')->setData($subtotal);
            }
        });

        $builder->get('saleAmount')->addEventListener(
            FormEvents::POST_SUBMIT,
            function(FormEvent $event) {
                $form = $event->getForm();
                $subtotal = $event->getForm()->getData();
                $form->get('saleAmount')->setData($subtotal);
            }
        );

    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => Sale::class
        ]);
    }
}

But this gives me an error: "The form's view data is expected to be a "App\Entity\Sale", but it is a "string". You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms "string" to an instance of "App\Entity\Sale"."

I tried setting the data class to null and I have a view transformer, which I called

<?php

// src/Form/DataTransformer/IssueToNumberTransformer.php
namespace App\Form\DataTransformer;

use App\Entity\Sale;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;

class SaleToStringTransformer implements DataTransformerInterface
{
    public function __construct(
        private EntityManagerInterface $entityManager,
    ) {
    }

    /**
     * Transforms an object (sale) to a string (number).
     *
     * @param  Sale|null $sale
     */
    public function transform($sale): string
    {
        if (null === $sale) {
            return 0;
        }
        
        return $sale->getSaleAmount();
    }

    /**
     * Transforms a string (number) to an object (sale).
     *
     * @param  string $saleNumber
     * @throws TransformationFailedException if object (sale) is not found.
     */
    public function reverseTransform($saleNumber): ?Sale
    {
        // no issue number? It's optional, so that's ok
        if (!$saleNumber) {
            return null;
        }

        $sale = $this->entityManager
            ->getRepository(Sale::class)
            // query for the issue with this id
            ->find($saleNumber)
        ;

        if (null === $sale) {
            // causes a validation error
            // this message is not shown to the user
            // see the invalid_message option
            throw new TransformationFailedException(sprintf(
                'A sale with saleAmount "%s" does not exist!',
                $saleNumber
            ));
        }

        return $sale;
    }

}
0

There are 0 answers