Sequently validate two different constraints with Symfony - php

I'm using Symfony 4.4 and I have form like this:
<?php
declare(strict_types=1);
namespace App;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class MainForm extends AbstractType
{
public const GROUP_CREATE = 'create';
public const GROUP_UPDATE = 'update';
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('zone', TargetingZoneForm::class, [
'targeting_validation_groups' => [
MainForm::GROUP_CREATE,
MainForm::GROUP_UPDATE,
],
])
;
}
}
class TargetingZoneForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('is_excluded', CheckboxType::class, [
'constraints' => [
new \Symfony\Component\Validator\Constraints\Type([
'type' => 'bool',
'groups' => $options['targeting_validation_groups'],
]),
],
])
->add('list', CollectionType::class, [
'entry_type' => IntegerType::class,
'entry_options' => [
'constraints' => [
new \Symfony\Component\Validator\Constraints\Range([
'min' => 1,
'max' => 2147483647,
'groups' => $options['targeting_validation_groups'],
]),
],
],
'allow_add' => true,
'allow_delete' => true,
'error_bubbling' => false,
'constraints' => [
new \Symfony\Component\Validator\Constraints\Count([
'max' => \App\Model\Zone::ZONE_LIMITATIONS_MAX,
'maxMessage' => 'You can use up to {{ limit }} zones',
'groups' => $options['targeting_validation_groups'],
]),
new \App\Constraints\Zone([
'groups' => $options['targeting_validation_groups'],
]),
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
parent::configureOptions($resolver);
$resolver->setRequired(['targeting_validation_groups']);
$resolver->setAllowedTypes('targeting_validation_groups', 'array');
}
}
My payload is like this:
{
"zone": {
"list": [
"1",
"26606653111701",
"3"
],
"is_excluded": false
}
}
I want to validate all of the elements of the collection for Range constraint, and only if it is valid - fire my custom \App\Constraints\Zone constraint after if (because inside I will have all valid IDs and send a single DB query). I failed to do so with GroupSequence.
I can't use Sequentially because I'm using Symfony 4.4.

It seems like you need the All Constraint. You can apply it on arrays with same values, like your array of integers in the list property of your form. You can then pass any constraints you need, and they will be applied to each element of the array.
Judging by the documentation, it should be available in Symfony 4.4.

Related

How to use a custom type correctly in Symfony?

I am trying to use all the power of Symfony to build a site. I already have 2 years of experience in Symfony but for the first time, I am trying to use the concept of custom form type to create a specific registration form. I already have a registration form for basic users but now, I want to create another one for managers whom operate hotels.
I have already created a form type named RegistrationFormType and another one named HotelType. My thing is that I want to use both of these form types in one form. But I keep getting an exception which says
Neither the property "user" nor one of the methods "getUser()",
"user()", "isUser()", "hasUser()", "__get()" exist and have public
access in class "App\Entity\User"
I have followed whatever was said in the Symfony official documentation about custom form types.
I made this form type RegistrationHotelFormType which is made up of another form RegistrationFormType used elsewhere as a form for basic users to create accounts and HotelType generated by Symfony itself.
<?php
namespace App\Form;
use App\Entity\User;
use App\Form\HotelType;
use App\Form\RegistrationFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Validator\Constraints\File;
class RegistrationHotelFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user', RegistrationFormType::class, ['mapped' => false])
->add('hotel', HotelType::class, ['mapped' => false])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
This above use to work but in the Symfony documentation, I don't need to set a mapped parameter to false knowing that I want form values to be assigned to objects fields which are Hotel and User. How can I get this done? (https://symfony.com/doc/current/form/create_custom_field_type.html#creating-form-types-created-from-scratch).
RegistrationFormType:
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Validator\Constraints\File;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class, ['error_bubbling'=>true, 'label' => 'Email', 'attr' => ['type'=>'text','class'=>'form-control', 'placeholder'=>'Email', 'aria-label'=>"Username", 'aria-describedby'=>"basic-addon1"]])
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
'label' => 'Mot de passe',
'attr' => ['error_bubbling'=>true,
'type'=>'text',
'class'=>'form-control',
'placeholder'=>'Mot de passe',
'aria-label'=>"Username",
'aria-describedby'=>"basic-addon1"],
'mapped' => false,
'constraints' => [
new NotBlank([
'message' => 'Mot de passe requis',
]),
new Length([
'min' => 8,
'minMessage' => 'Le mot de passe doit avoir au moins {{ limit }} caractères',
// max length allowed by Symfony for security reasons
'max' => 128,
'maxMessage' => 'Le mot de passe ne doit pas dépasser {{ limit }} caractères',
]),
// new Regex([
// 'pattern' => '/^\S*(?=\S*[a-z])(?=\S*[A-Z])(?=\S*[\d])\S*$/',
// 'message' => 'Le mot de passe doit contenir au moins 1 lettre et 1 chiffre',
// ])
],
])
->add('firstname', TextType::class, ['error_bubbling'=>true, 'label' => 'Prénom', 'attr' => ['type'=>'text','class'=>'form-control', 'placeholder'=>'Prénom', 'aria-label'=>"Username", 'aria-describedby'=>"basic-addon1"]])
->add('surname', TextType::class, ['error_bubbling'=>true, 'label' => 'Nom', 'attr' => ['type'=>'text','class'=>'form-control', 'placeholder'=>'Nom', 'aria-label'=>"Username", 'aria-describedby'=>"basic-addon1"]])
->add('phoneNumber', TextType::class, ['error_bubbling'=>true, 'label' => 'Téléphone', 'attr' => ['type'=>'text','class'=>'form-control', 'placeholder'=>'Téléphone', 'aria-label'=>"Username", 'aria-describedby'=>"basic-addon1"]])
->add('city', TextType::class, ['error_bubbling'=>true, 'label' => 'Ville', 'attr' => ['type'=>'text','class'=>'form-control', 'placeholder'=>'Ville', 'aria-label'=>"Username", 'aria-describedby'=>"basic-addon1"]])
->add('termsAccepted', CheckboxType::class, [
'error_bubbling'=>true,
'mapped' => false,
'constraints' => new IsTrue(),
'attr' => ['type'=>"checkbox", 'class'=>"form-check-input"]
])
->add('resumePDF', FileType::class, [
'error_bubbling'=>true,
'label' => 'CV (Fichier PDF)',
'mapped' => false,
'required' => true,
'constraints' => [
new File([
'maxSize' => '1024k',
'mimeTypes' => [
'application/pdf',
'application/x-pdf',
],
'mimeTypesMessage' => 'Téléchargez un fichier PDF valide',
])
],
'attr' => ['type'=>"file", 'class'=>"custom-file-input"]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
HotelType
<?php
namespace App\Form;
use App\Entity\Hotel;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class HotelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('address');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Hotel::class,
]);
}
}
RegistrationController.php
/**
* #Route("/inscription-hotel", name="app_register_hotel")
*/
public function registerHotel(Request $request, UserPasswordEncoderInterface $passwordEncoder, \Swift_Mailer $mailer): Response
{
$session = new Session();
// $session->start();
$user = new User();
$form = $this->createForm(RegistrationHotelFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
}
return $this->render('registration/register-hotel.html.twig', [
'registrationForm' => $form->createView(),
]);
}
Thanks in advance for your help
Cerad diagnosed why the error is triggered (the second argument to createForm will be used and the form will try to access the user and hotel property on it), the simplest solution follows:
I would remove the data_class from the combined form, and just omit the second parameter to createForm in your controller. The return value of $form->getData() would be an array:
['user' => {User object}, 'hotel' => {Hotel object}]
(you can also provide a similarly structured array to your createForm call as the second parameter)
of course, you can create a new class that contains both the user and the hotel...

Symfony 4 Form Type translations

I've Symfony 4.4 and the Symfony/translation component.
I'm trying to extract the translations, and everything works fine, except that the form labels are not extracted.
This is a example of one of the Form Types.
namespace App\Form;
use App\Entity\ClassifiedAd;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ClassifiedAdType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('description')
->add('price', MoneyType::class, [
'required' => false,
'currency' => "EUR"
])
->add('type', ChoiceType::class, [
'required' => true,
'expanded' => true,
'multiple' => false,
'label' => "classifiedad.form.type",
'choices' => [
'classifiedad.form.type_'.ClassifiedAd::SELLING => ClassifiedAd::SELLING,
'classifiedad.form.type_'.ClassifiedAd::SEARCHING => ClassifiedAd::SEARCHING,
'classifiedad.form.type_'.ClassifiedAd::SWAPPING => ClassifiedAd::SWAPPING,
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ClassifiedAd::class,
]);
}
}

Symfony bug with forms

I am experiencing the following form error in Symfony:
Neither the property "email" nor one of the methods "email()", "getemail()"/"isemail()"/"hasemail()" or "__call()" exist and have public access in class "Symfony\Component\Form\FormView".
This is what my form looks like:
My App\Form\LostPasswordType:
namespace App\Form;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use App\Entity\LostPassword;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\AbstractType;
class LostPasswordType extends AbstractType
{
public function builder(FormBuilderInterface $builder, array $options)
{
$builder
->setAction('/forgotpw')
->setMethod('POST')
->add('mail', EmailType::class, [
'required' => true,
'label' => false,
'attr' => [
'autofocus' => false,
'class' => 'span8',
'placeholder' => 'example#example.com'
]
])
->add('submit', SubmitType::class, [
'label' => 'Reset Password',
'attr' => ['class' => 'btn btn-primary btn-green']
])
;
}
public function configureOption(OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => LostPassword::class]);
}
}
And this is my controller code:
$formReset = $this->createForm(
LostPasswordType::class,
$forgotpass,
array('csrf_protection' => false)
);
$formReset->handleRequest($request);
Does anyone know why I am receiving this error?
I fixed the problem by changing the method function name in form builder, it was mistake there.

Symfony 4 - Multiple forms of same type with dynamic display fields

I would like to use my UserType form class to register a user and to edit the user profile. As I want to the admins to register a user while setting up their roles, I don't want the user to modify that options. So I would like to use 2 different forms but for the same type User.
Can I create 2 different buildForm() functions in one FormType class ?
Or do I need to create another type ?
Here is my UserType class (main purpose was to register a user) :
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstname', TextType::class)
->add('lastname', TextType::class)
->add('birthdate', BirthdayType::class, array(
'placeholder' => '-'))
->add('email', EmailType::class)
->add('username', TextType::class)
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'first_options' => array('label' => 'password'),
'second_options' => array('label' => 'repeat-password'),
))
->add('roles', ChoiceType::class, [
'multiple' => true,
'expanded' => true, // render check-boxes
'choices' => [
'Administrateur' => 'ROLE_ADMIN',
'Direction' => 'ROLE_MANAGER',
'Comptabilite' => 'ROLE_ACCOUNTING',
'Commercial' => 'ROLE_MARKETING',
'Docteur' => 'ROLE_DOCTOR',
'Client' => 'ROLE_CLIENT',
],
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => User::class,
));
}
}
You can use a variable to dynamically add the roles to the formType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->is_admin = $options['is_admin'];
// [...]
if ($this->is_admin)
$builder
->add('roles', ChoiceType::class, [
'multiple' => true,
'expanded' => true,
'choices' => [
'Administrateur' => 'ROLE_ADMIN',
'Direction' => 'ROLE_MANAGER',
'Comptabilite' => 'ROLE_ACCOUNTING',
'Commercial' => 'ROLE_MARKETING',
'Docteur' => 'ROLE_DOCTOR',
'Client' => 'ROLE_CLIENT',
],
])
// [...]
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
// [...]
'is_admin' => false,
]);
}
Then pass the variable like
$form = $this->createForm(UserType::class, $user, array(
'is_admin' => $this->isGranted('ROLE_ADMIN'),
);
you can easily handle this using array $optionsin controller set choices of roles and add them to $optionand in userType do something like this:
->add('roles', ChoiceType::class, [
'multiple' => true,
'expanded' => true, // render check-boxes
'choices' => $options['choices'],
])

A2LiX Translation on change language tab get language in form

I need language on change of A2LiX Translation tabs. If I click on "FR" then need to get Fr and on click of "NL" need Nl language in form builder ('Need Language Here').
For querybuilder in one field.
Is it possible to get the language in form type when tab change ?
My Form class MixCampaignTranslationType :
<?php
namespace BackEndBundle\Form;
use BackEndBundle\Validator\Constraints\CampaignSlugDuplicate;
use BackEndBundle\Validator\Constraints\MaxMixCampaign;
use Doctrine\ORM\EntityRepository;
use SurveyBundle\Entity\Survey;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;
class MixCampaignTranslationType extends AbstractType
{
public $campaignId = null;
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (null !== $options['campaignId']) {
$this->campaignId = $options['campaignId'];
}
$builder
->add('surveys', EntityType::class, [
'class' => Survey::class,
'required' => false,
'multiple' => true,
'mapped' => true,
'attr' => [
'class' => 'select2',
],
'constraints' => new MaxMixCampaign(),
])
->add('startDate', DateTimeType::class, [
'widget' => 'single_text',
'format' => 'yyyy-MM-dd',
'constraints' => new NotBlank(),
'attr' => [
'class' => 'datepicker',
],
])
->add('endDate', DateTimeType::class, [
'widget' => 'single_text',
'format' => 'yyyy-MM-dd',
'constraints' => new NotBlank(),
'attr' => [
'class' => 'datepicker',
],
])
->add('slug', TextType::class, [
'constraints' => [new NotBlank(), new Type('string'), new CampaignSlugDuplicate($this->campaignId, $options['locale'], 'mix')],
])
->add('isClosed');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'SurveyBundle\Entity\MixCampaignTranslation',
'locale' => 'fr',
'campaignId' => null
]);
}
public function getName()
{
return 'back_end_bundle_mix_campaign_translation_type';
}
}
You can create your custom TranslationFormType and then pass the language of loop as current language, Please check as below.
/**
* #param \Symfony\Component\Form\FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventSubscriber($this->translationsListener);
$formsOptions = $this->translationForm->getFormsOptions($options);
foreach ($options['locales'] as $locale) {
if (isset($formsOptions[$locale])) {
$builder->add($locale, $options['form_type'], [
'currentLanguage' => $locale,
'campaignId' => $formsOptions[$locale]['campaignId']
]
);
}
}
}
and you can use currentLanguage to filter data in query_builder in your form_type.
Hope it will help you.

Categories