how to map violation to Symfony form manually? - symfony

OrderType
$builder
->add('items', FormTypes\CollectionType::class, [
'entry_type' => OrderItemType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => 'acme.form.order.items',
])
->add('channel', ChannelSelectType::class, [
'required' => true,
'label' => 'acme.form.order.channel',
])
OrderItemType
$builder
->add('service', ServiceSelectType::class, [
'label' => 'acme.form.order_item.service',
])
->add('product', ProductSelectType::class, [
'label' => 'acme.form.order_item.product',
])
->add('quantity', FormTypes\IntegerType::class, [
'label' => 'acme.form.order_item.quantity',
]);
How to map the error to OrderItemType product field?
the order item is valid when a product is applied to a specific channel. however we have no way to get the submited channel in OrderItemType,
because child form type is submmited before its parent. so $event->getForm()->getParent()->getData()->getChannel() is empty. the only way I have
is to validate order item in OrderType, or create a validator which is added to Order class. the problem is how can I map the error to OrderItemType product field.
$orderItems = $order->getItems();
$channel = $order->getChannel();
foreach($orderItems as $index => $orderItem) {
$product = $orderItem->getProduct();
if (!$this->isProductAvailableForChannel($channel, $product)) {
$message = sprintf('product %ss is not available for channel "%s"', $product->getName(), $channel->getName());
}
if (null !== $message) {
$this->context
->buildViolation($this->constraint->message)
->setParameter($message)
->atPath("items.children[$index].product") // this doesn't work, the error will be added to root form.
->addViolation()
;
}
}

You are able to get the channel on the pre submit event (Symfony\Component\Form\FormEvents::PRE_SUBMIT)
And there you can add your channel based validation for the order item
$addItems = function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
$options = [];
if (is_array($data) && array_key_exists('channel', $data)) {
$options['constraints'] = [
new OrderItemConstraint(['channel' => $data['channel']])
];
}
$form->add('items', FormTypes\CollectionType::class, [
'entry_type' => OrderItemType::class,
'entry_options' => $options,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'label' => 'acme.form.order.items',
]);
};
$builder->addEventListener(FormEvents::PRE_SET_DATA, $addItems);
$builder->addEventListener(FormEvents::PRE_SUBMIT, $addItems);

Related

Symfony - Error with the data parameter on a form

Context of the problem :
I created a symfony form.
Each tool has a collection of modules.
The user has a collection of modules of any tool.
What I want :
I want for each tool there are checkboxes corresponding to the tool's modules. The module checkboxes that the user owns are checked.
([] = checkbox)
Tool1 : []Module1 [x]Module2 [x]Module3
Tool2 : []Module4 [x]Module5
Tool3 : [x]Module6 []Module7
What I currently have:
For each tool, there are checkboxes corresponding to the tool's modules. But I have a problem to tick the checkboxes of user's modules. I get an error on the data parameter.
The form field :
$user = $options['user'];
$tools = $options['tools'];
foreach ($tools as $tool) {
$name = 'profile_'.str_replace(array('-', ' ', '.'), '', $tool->getLibelle());
$builder
->add($name, ChoiceType::class, [
'label' => $tool->getLibelle(),
'choices' => $tool->getModules(),
'choice_value' => 'id',
'choice_label' => function (?Module $module) {
return $module ? $module->getName() : '';
},
'data'=> $user->getModules(), // ERROR HERE
'expanded' => true,
'multiple' => true,
'mapped'=>false
])
;
}
[...]
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
'user'=> null,
'category'=> null,
'tools'=> null,
]);
}
The error :
My question :
Why do I have this error? How can I use the data parameter correctly to achieve the expected result?
You are on the good way, try to dump what is $user->getModules() returning, it has to be an array. May be is not returning an array, check te relation.
I did a little test and it works perfectly.
$name = 'name_field';
$builder->add($name,ChoiceType::class, array(
'choices' => array('Yes', 'No'),
'data' => array('Yes', false),
'mapped' => false,
'expanded' => true,
'multiple' => true
));
Here the solution :
Seems to me that $user->getModules() returns a collection. I managed to find another solution and that works (I changed the type of the field to EntityType)
foreach ($tools as $tool) {
$name = 'acces_'.str_replace(array('-', ' ', '.'), '', $tool->getLibelle());
$builder
->add($name, EntityType::class, [
'class'=> Module::class,
'label' => $tool->getLibelle(),
'data' => $user->getModules(),
'choices'=> $tool->getModules(),
'choice_value' => 'id',
'choice_label' => 'name',
'expanded' => true,
'multiple' => true,
'required' => true,
'mapped'=>false,
])
;
}
ChoiceType: data parameter need array
EntityType: data parameter need collection
Thanks for the help !

How to set min value for field in form?

I have form with integer field - price.
$builder->add('list',
CollectionType::class,
[
'required' => false,
'allow_add' => true,
'error_bubbling' => true,
])
->add('price',
IntegerType::class,
[
'required' => false,
'error_bubbling' => true,
]);
How to set, for example, for validation i need min value for price 0 or greater?
I tried this one, but it's not work:
'constraints' => [
new GreaterThanOrEqual(50)
],
Thanks for all help.
controller Action
public function getProductAction(Request $request)
{
$variables = $request->get('list');
$price = $request->get('price');
$form = $this->createForm(ProductForm::class, null, ['csrf_protection' => false, 'allow_extra_fields' => true]);
$form->submit(['variables' => $variables, 'prices' => $price]);
if(!$form->isValid()) {
$errors = '';
foreach ($form->getErrors() as $error) {
$errors = $error->getMessage();
}
return new JsonResponse([
'errors' => $errors
],Response::HTTP_BAD_REQUEST);
} else {
$product = $this->getDoctrine()
->getRepository(Product::class)
->findByListAndPrice($list, $price);
if (!$product) {
return new JsonResponse([
'errors' => 'Product not found.'
],Response::HTTP_BAD_REQUEST);
}
return new JsonResponse($product);
}
}
Form not validate, and don't show errors, $form->isValid() === true
According to https://github.com/symfony/symfony/issues/3533 you can use min and max for the IntegerType even though the documentation might not mention this.
$builder->add('list',
CollectionType::class,
[
'required' => false,
'allow_add' => true,
'error_bubbling' => true,
])
->add('price',
IntegerType::class,
[
'required' => false,
'error_bubbling' => true,
/*'min' => 50*/
'attr' => [
'min' => 50
]
]);
EDIT: According to the documentation the 'min' property has to be inside 'attr' tag. This will add the min inside the input in the HTML.
You can use RangeType instead of IntegerType.
use Symfony\Component\Form\Extension\Core\Type\RangeType;
// ...
$builder->add('list',
CollectionType::class,
[
'required' => false,
'allow_add' => true,
'error_bubbling' => true,
])
->add('price',
RangeType::class,
[
'required' => false,
'error_bubbling' => true,
'min' => 0,
'max' => 50
]);

Generate data attribute to each checkbox in Symfony type

->add('research_subject', EntityType::class, array(
'mapped' => false,
'class' => Subject::class,
'label' => 'Research Subject',
'expanded' => true,
'multiple' => true,
'query_builder' => function (EntityRepository $er) {
$db = $er->createQueryBuilder('w');
$db ->where($db->expr()->andX(
$db->expr()->isNotNull('w.pid')
));
$db->orderBy('w.pid', 'ASC');
return $db;
},
'choice_label' => 'name_system',
))
I need to add to each check box data attribute. Is that is possible?
I need that for extra sort of checkboxes in twig latter. I need to group checkboxes by pid value in separate div section.
'choice_attr' => function($val, $key, $index) {
return ['data-pid' => $val->getPid()];
},
I had use this as solution, like https://stackoverflow.com/users/4224384/yceruto
Suggest.
You would use the "choice_attr" option for this:
[...]
'expanded' => true,
'multiple' => true,
'choice_attr' => function($val, $key, $index) {
return ['data' => $key];
},
[...]

Custom option in adding element of type

I have form and have two methods like this
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Justart\InvoiceBundle\Entity\InvoiceElement',
'validation_groups' => 'invoiceElementValidation',
'translation_domain' => 'JustartInvoiceBundle',
'invoice_revision' => false,
));
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (!empty($options['invoice_revision'])) {
$view->vars['invoice_revision'] = $options['invoice_revision'];
}
}
form name is InvoiceElementType
and I have form named InvoiceType which has elements so I wrote:
$builder->add('invoice_elements', CollectionType::class, array(
'entry_type' => InvoiceElementType::class,
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
'label' => false,
'entry_options' => array(
'label' => false,
),
'required' => true,
'constraints' => array(
new NotBlank(array('groups' => array('STANDARD','PROFORM','FINISH','ADVANCE'))),
),
'invoice_revision' => $this->isRevision,
))
when I run it i get error
The option "invoice_revision" does not exist. Defined options are:
"action" ...
So what I do wrong?
And how can I send a variable from controller to type? I tried :
$form = $this->createForm(new Form\InvoiceType(1), $invoice);
construct of InvoiceType require integer value but I get error that createForm except string in first parameter
Try to put invoice_revision option under entry_options key:
'entry_options' => array(
'label' => false,
'invoice_revision' => $this->isRevision,
),

Sonata Admin and Dynamic Form

I made several search but I still have a problem...
I want to make a dynamic form. I want to hydrate a select in function of an other select.
This is my configureFormFields:
protected function configureFormFields(FormMapper $formMapper)
{
$emIndustry = $this->modelManager
->getEntityManager('*\*\*\*\Entity\Industry')
;
$query = $emIndustry->getRepository(*:Industry')
->getFindAllParentsQueryBuilder()
;
$formMapper
->add('company')
->add('industry', 'sonata_type_model', [
'attr' => [
'onchange' => 'submit()',
],
'query' => $query,
'required' => false,
])
->add('subIndustry', 'sonata_type_model', [
'choices' => [],
'required' => false,
])
;
$builder = $formMapper->getFormBuilder();
$factory = $builder->getFormFactory();
$subject = $this->getSubject();
$modelManager = $this->getModelManager();
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use($formMapper, $subject, $emIndustry, $modelManager, $factory) {
$form = $event->getForm();
if(!is_null($subject->getIndustry())) {
$query = $emIndustry->getRepository('*:Industry')
->getFindChildrenByParentQueryBuilder($subject->getIndustry())
;
$form
->add(
$factory->createNamed('subIndustry', 'sonata_type_model', null, [
'class' => '*\*\*\*\Entity\Industry',
'query' => $query,
'required' => false,
])
)
;
}
});
}
When I change the value of the select Industry, no problem my form is submited. But nothing happend in second select subIndustry because : all attributes of my $subject object is null...
Have you any idea why ? Is there a best way to make a dynamic form ?
Thank's for your help.
AlexL

Resources