I want to compare two attribute on form SYMFONY4 - php

I have this formtype:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('ageMin', NumberType::class),
->add('ageMax', NumberType::class,[
'mapped' => false
]);
}
My problem, is i want to validate this expression before submitting the form: ageMin<=ageMax
i HAVE tried this code, but is not work
$resolver->setDefaults([
'data_class' => User::class,
'constraints' => [
new Assert\Expression([
'expression' => 'value["ageMax] >= value["ageMin"]'
]),
]
]);
If there's another solutions?

You can easily solve this by using any one of the following
Constraints Expression
Custom Validation Constraint

'constraints' => [
new Assert\Expression([
'expression' => '
(this["ageMin"].getData() <= this["ageMax"].getData() )
)
',
'message' => ''
]),

You can use a Class Constraint Validator:
class UserAgeValidator extends ConstraintValidator
{
public function validate($user, Constraint $constraint)
{
if ($user->getAgeMax() >= $user->getAgeMin()) {
$this->context->buildViolation($constraint->message)
->atPath('ageMin')
->addViolation();
$this->context->buildViolation($constraint->message)
->atPath('ageMax')
->addViolation();
}
}
}
Reference to documentation:
https://symfony.com/doc/current/validation/custom_constraint.html#class-constraint-validator

Related

Symfony, constraint for User

Good day! Just started learning Symfony on my own.
I'm making a news portal. The administrator can download news from an Excel file. I am converting a file to an associative array. For example:
[ 'Title' => 'Some title',
'Text' => 'Some text',
'User' => 'example#example.com',
'Image' => 'https://loremflickr.com/640/360'
]
Next, I want to send this array to the form and use 'constraints' to validate it.
There are no problems with the fields 'Title', 'Text', 'Image'. I don't know how to properly check the 'User' field. The user in the file is submitting an Email, but I want to check that a user with that Email exists in the database.
NewsImportType
class NewsImportType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'constraints' =>
[
new NotBlank(),
new Length(['min' => 256])
],
])
->add('text', TextareaType::class, [
'constraints' =>
[
new NotBlank(),
new Length(['max' => 1000])
],
])
->add('user', TextType::class, [
'constraints' =>
[
new NotBlank(),
new Email(),
],
->add('image', TextType::class, [
'constraints' =>
[
new NotBlank(),
new Length(['max' => 256]),
new Url()
],
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'allow_extra_fields' => true,
'data_class' => News::class,
]);
}
}
The entities User and News are connected by a One-to-Many relationship.
I was thinking about using ChoiceType and calling UserRepository somehow, but I don't understand how to apply it correctly.
Please tell me how to correctly write 'constraint' for the 'user' field.
thank!
Create a Custom Constraint. This way it is reusable in any other form you would like to check for a user.
Create a new folder in your project src/Validator then put these 2 files in there.
The Constraint
// src/Validator/userAccountExists.php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
class UserAccountExists extends Constraint
{
public $message = 'User account does\'t exists. Please check the email address and try again.';
}
The Validator
// src/Validator/userAccountExistsValidator.php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\User;
class UserAccountExistsValidator extends ConstraintValidator
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function validate($email, Constraint $constraint)
{
if (!$constraint instanceof UserAccountExists) {
throw new UnexpectedTypeException($constraint, UserAccountExists::class);
}
if (null === $email || '' === $email) {
return;
}
if (!is_string($email)) {
throw new UnexpectedValueException($email, 'string');
}
if (!$this->userExists($email)) {
$this->context->buildViolation($constraint->message)->addViolation();
}
}
private function userExists(string $email): bool
{
$user = $this->entityManager->getRepository(User::class)->findOneBy(array('email' => $email));
return null !== $user;
}
}
In your form you can now use the validator
->add('user', TextType::class, [
'constraints' =>
[
new NotBlank(),
new Email(),
new UserAccountExists(),
],
Remember to add use App\Validator\UserAccountExists; to your form.

How to add dynamic error for form validation symfony using createnamebuilder?

I create a my own form builder using createNamebuilder(). Just like this
$builder = $this->formFactory->createNamedBuilder($formName, $phoneNumberType, $data, $formOptions);
I have PhoneNumberType. inside of it I add to builder countryNumber and number.
$builder->add('countryNumber', CountryCodeType::class);
$builder->add('number', NumberType::class);
How to add constraint in number that need to be required? I try to use this
public function buildForm(FormBuilderInterface $builder, array $options)
{
$contactNumber = $builder->getData();
$countryCode = null;
if ($contactNumber instanceof ContactNumber) {
$countryCode = $contactNumber->getCountryNumber();
}
$builder->add('countryNumber', CountryCodeType::class, ['data' => $countryCode]);
$builder->add('number', NumberType::class, [
'required' => true,
'constraints' => [new NotBlank(['message' => 'Phone number is required.'])]
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ContactNumber::class,
'error_bubbling' => true
]);
}
When the time I submit my form. It doesn't show the error message.
You are trying to add form after posting,
You need to use events
https://symfony.com/doc/current/form/dynamic_form_modification.html
or you can add constraints to entity after submitting empty data form will return errors.
use Symfony\Component\Validator\Constraints as Assert;
class ContactNumber
{
/**
* #Assert\NotBlank()
* ..
*/
private $number
}
and framework.yaml
framework:
validation: { enable_annotations: true }

Convert Symfony 2 FormType with choice_list closure to Symfony 3

I migrated some Sf2 form types to Sf3 without any problem... but this one is causing me some trouble.
class StaticEntityType extends AbstractType
{
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'function' => 'getAll',
'choice_label' => 'name',
'group_by' => null,
'choice_list' => function (Options $options) {
return new ObjectChoiceList(
call_user_func(array($options['class'], $options['function'])),
$options['choice_label'],
$options['preferred_choices'],
$options['group_by'],
'id'
);
}
]
);
$resolver->setRequired('class');
$resolver->setDefined(['function', 'choice_label', 'group_by']);
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'static_entity';
}
}
The option list should be created relative to options "class" and "function", like this:
$formBuilder->add('myField', StaticEntityType::class, ['class' => '\Path\To\MyStaticEntity', 'function' => 'getAll'])
But since choice_list option and ObjectChoiceList no longer exists, I don't find how to do.
Doc says that we should either provide 'choices' or 'choice_loader'. But 'choices' don't accept closure, and 'choice_loader' don't receive the form options list.
Any help would be welcome.
As described in the migration guide:
The
Symfony\Component\Form\Extension\Core\ChoiceList\ObjectChoiceList
class has been removed in favor of
Symfony\Component\Form\ChoiceList\ArrayChoiceList.
And
The choice_list option of ChoiceType was removed.
Simply use choice.
So try as follow:
$resolver->setDefaults(
[
'function' => 'getAll',
'choice_label' => 'name',
'group_by' => null,
'choice' => function (Options $options) {
return new ArrayChoiceList(
call_user_func(array($options['class'], $options['function'])),
$options['choice_label'],
$options['preferred_choices'],
$options['group_by'],
'id'
);
}
]
);
Other resources here in the doc.
Hope this help
I think I finally found a solution!
In fact, the OptionResolver class allows to set a Normalizer, which will be called at runtime when getting an option.
So I removed the choice_list option, and instead of adding the choices option, I added a Normalizer:
$resolver->setNormalizer(
'choices',
function (Options $options) {
return call_user_func([$options['class'], $options['function']]);
}
);

Symfony 2 persiting 2 entities from form using ID generated from first entity

I have a form that collects data from two related entities 'Jobs' and 'Scope'. Jobs data adds successfully to the database, but i can't get the scope entity to add which also requires the unique ID that was created from the jobs entry. I'm using doctrine by the way.
Here is jobsType:
class jobsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('jobNumber', null, array('label' => 'Job Number','attr' => array('placeholder'=>'Code used for job')))
->add('description', null, array('label' => 'Job Description',))
;
$builder->add('scopes', new scopeType())
;
}
public function getName()
{
return 'jobs';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Baker\TimeSlipBundle\Entity\Jobs',
));
}
}
Here is scopeType
class scopeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', 'entity', array('label' => 'Scope', 'required' => false,
'class' => 'BakerTimeSlipBundle:Scope',
'query_builder' => function(EntityRepository $er){
return $er->createQueryBuilder('s')
->orderBy('s.description', 'asc');
},
'property' => 'description',
'multiple' => false,
'expanded' => false,
'empty_value' => false,
'attr' => array('class' => 'form-inline'),
'label_attr' => array('class' => 'required')
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Baker\TimeSlipBundle\Entity\Scope',
));
}
public function getName()
{
return 'scope';
}
}
Here is my controller, i think the problem here is that i'm not passing the scope() instance to the form builder. Which i'm not sure how to do. The entity is mapped correctly.
class JobsController extends Controller
{
public function indexAction($limit, Request $request)
{
$jobs= new Jobs();
$scope = new Scope();
$fname=$limit;
$form = $this->createForm(new JobsType(),$jobs, array(
'method' => 'POST',
));
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($jobs);
$em->persist($scope);
$em->flush();
return $this->redirectToRoute('company',array ('limit' =>'Job Submitted Successfully'));
}
return $this->render(
'BakerTimeSlipBundle:Jobs:index.html.twig',
array('fname' => $fname,
'form' => $form->createView()
)
);
}
}
}
Any help here would be greatly appreciated. Below is listed the error message when I try to enter the scope:
An exception occurred while executing 'INSERT INTO Scope (description, jobsId, jobsid) VALUES (?, ?, ?)' with params [null, null, null]:
SQLSTATE[42000]: Syntax error or access violation: 1110 Column 'jobsId' specified twice
The problem here is the $jobs object you link to your form has no relation with the $scope object. So when you try to persist $jobs, Doctrine has no idea $scope it had to persist.
Just try this :
$jobs= new Jobs();
$scope = new Scope();
$jobs->setScope($scope);
Then, in your Jobs entity, on the annotation above $scopes, make sure you have cascade={"persist"} (but you must already have it because Doctrine tries to persist, and that's where the error is thrown), and you should be good to go.
Hope this helps

symfony 2 how to pass array collection to input select

Hi i am tying pass array collection (method getProjects() returns it) to form (select input) and fail. This code returns exception - A "__toString()" method was not found on the objects of type "Tasker\WebBundle\Entity\Project" passed to the choice field.
Can anybody help? Is needed transformer? Or what is right way?
Controller:
/**
* #Route("/pridaj", name="web.task.add")
* #Template()
*/
public function addAction(Request $request)
{
$task = new Task;
/** #var User $loggedUser */
$loggedUser = $this->get('security.token_storage')->getToken()->getUser();
$form = $this->createForm(new AddTaskType(), $task, ['user' => $loggedUser]);
if ($form->handleRequest($request) && $form->isValid()) {
// some stuff
}
return [
'form' => $form->createView()
];
}
Form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('project', 'entity', [
'label' => 'Projekt:',
'class' => 'TaskerWebBundle:Project',
'choices' => $options['user']->getProjects(),
'placeholder' => 'Označte projekt',
])
// ....
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'user',
));
$resolver->setDefaults(array(
'user' => null,
));
}
just add __ToString() to your Project class
Tasker\WebBundle\Entity\Project
class Project
{
....
function __toString() {
return $this->getName(); //or whatever string you have
}
}
I wanted to add another answer, because you do not have to add __toString() to your Project class. The Symfony entity field type allows you to specify which property/field to use for displaying. So instead of __toString() you could specify the property in the form configuration like so:
$builder
->add('project', 'entity', [
'label' => 'Projekt:',
'class' => 'TaskerWebBundle:Project',
'choices' => $options['user']->getProjects(),
'placeholder' => 'Označte projekt',
'property' => 'name'
])
If you check this part of the Symfony documentation you will see that __toString() is automatically called only if you do not specify the property.

Categories