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;
}
}