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.
Related
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
How can I validate (server side) a form created and submitted from Vue?
I have a simple contact form, and I want to validate it both client and server side. For the client mode I'm using vee-validate (and it works), for the server side mode I want to use the Symfony's form validation.
So in this "special" case, the form is not rendered using the methods from Symfony, the form in this case is used only for validation. I have already created the form (it isn't linked to an Entity object), but when I send a http post request from my Vue's component, the validation on server side doesn't work. Seems that doesn't "read" the constraints created in the FormTypeclass.
My function:
public function sendContactUsEmailAction(Request $request, Mailer $mailer, TranslatorInterface $translator)
{
try {
$form = $this->createForm(ContactUsType::class);
$form->submit($request->request->all());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$emailParams = new ContactUsParams();
$emailParams->setName($data['name']);
$emailParams->setEmail($data['email']);
$emailParams->setSubject($data['subject']);
$emailParams->setMessage($data['message']);
$email = new ContactUsMail($mailer, $emailParams);
$email->send();
return new JsonResponse($translator->trans('send_contact_us_email_response'));
}
return new JsonResponse($this->getFirstFormError($form), 400);
} catch (\Exception $e) {
return new JsonResponse($e->getMessage(), 500);
}
}
Edited
This is the dump of my request data:
array:4 [
"name" => "Example"
"email" => "foo#example.com"
"subject" => "Test subject"
"message" => "Test message"
]
This is my FormType class. To test it I added for the name property the constraint "Email", but from the request I didn't send a valid email.
class ContactUsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, array(
'constraints' => array(
new NotBlank(),
new Email(),
),
))
->add('email', EmailType::class, array(
'constraints' => array(
new NotBlank(),
new Email(),
),
))
->add('subject', TextType::class, array(
'constraints' => new NotBlank(),
))
->add('message', TextareaType::class, array(
'constraints' => new NotBlank(),
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => false,
'validation_groups' => false,
]);
}
}
I've found the solution. The problem was caused by the option "validation_groups" setted to false. I removed it and now works!
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
I've built a form with the silex framework that contains some checkboxes and a textfield. The user has to check at least one checkbox or write something in the textfield. Now I don't know how to validate such a dependency or better where to put the validation logic. I could add constraints to the single fields but how can I implement the dependency that either the checkboxes are validated or the textfield?
This is my validation code in the controller class.
public function validateAction(Request $request, Application $app)
{
$form = $app['form.factory']->create(new ApplicationForm());
$form->bind($request);
if ($form->isValid()) {
return $app->json(array(
'success' => true,
));
}
}
The ApplicationForm class looks like this (simplified):
class ApplicationForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('crafts', 'choice', array(
'choices' => array(
array('drywall' => 'Drywall'),
array('painter' => 'Painter'),
array('plasterer' => 'Plasterer'),
array('carpenter' => 'Carpenter'),
array('electrician' => 'Electrician'),
array('plumber' => 'Plumber'),
array('tiler' => 'Tiler'),
array('bricklayer' => 'Bricklayer'),
),
'multiple' => true,
'expanded' => true,
'required' => false,
))
->add('craftsOther', 'text', array(
'attr' => array('class' => 'textinput', 'placeholder' => 'Other', 'maxlength' => 256),
'constraints' => array(
new Assert\Length(array('max' => 256, 'maxMessage' => $this->_errorMessages['crafts_other_max'])),
),
'required' => false,
));
}
}
Any ideas how to do this in an elegant way?
You can add constraints to the form itself, not just individual fields. You can pass an array of constraints in the options:
Here is a working example that checks both fields are not equal when submitted.
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Silex\Application;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Constraints as Assert;
$app = new Application();
$app['debug'] = true;
$app->register(new \Silex\Provider\FormServiceProvider());
$app->register(new Silex\Provider\TranslationServiceProvider(), array('translator.domains' => array(),));
$app->register(new Silex\Provider\ValidatorServiceProvider());
$app->register(new Silex\Provider\TwigServiceProvider());
class ExampleFormType extends AbstractType
{
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder, array $options)
{
$builder
->add('apple')
->add('banana')
->add('submit', SubmitType::class);
}
public function configureOptions(\Symfony\Component\OptionsResolver\OptionsResolver $resolver)
{
$resolver->setDefaults([
'constraints' => [
new Assert\Callback(function (array $data, ExecutionContextInterface $context) {
if ($data['apple'] === $data['banana']) {
$context
->buildViolation('Apple and Banana cannot be the same')
->atPath('apple')
->addViolation();
}
}),
],
]);
}
}
$app->match('/', function (Request $request, Application $app) {
$form = $app['form.factory']->create(ExampleFormType::class)->handleRequest($request);
if ($form->isValid()) {
echo "Valid Submission";
}
return $app['twig']->createTemplate('{{ form(form) }}')->render([
'form' => $form->createView(),
]);
})->method('GET|POST');
$app->run();
I'm using Symfony2 form component to build and validate forms. Now I need to setup validator groups based on a single field value, and unfortunately it seems that every example out there is based on entities - which im not using for several reasons.
Example:
If task is empty, all constraint validators should be removed, but if not, it should use the default set of validators (or a validator group).
In other words, what I'm trying to achieve is making subforms optional, but still be validated if a key field is populated.
Can someone possible give me an example how to configure it?
<?php
namespace CoreBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints as Assert;
use CoreBundle\Form\Type\ItemGroupOption;
class ItemGroup extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', 'text', array(
'label' => 'Titel',
'attr' => array('class' => 'span10 option_rename'),
'required' => false
));
$builder->add('max_selections', 'integer', array(
'label' => 'Max tilvalg',
'constraints' => array(new Assert\Type('int', array('groups' => array('TitleProvided')))),
'attr' => array('data-default' => 0)
));
$builder->add('allow_multiple', 'choice', array(
'label' => 'Tillad flere valg',
'constraints' => array(new Assert\Choice(array(0,1))),
'choices' => array(0 => 'Nej', 1 => 'Ja')
));
$builder->add('enable_price', 'choice', array(
'label' => 'Benyt pris',
'constraints' => array(new Assert\Choice(array(0,1))),
'choices' => array(0 => 'Nej', 1 => 'Ja'),
'attr' => array('class' => 'option_price')
));
$builder->add('required', 'choice', array(
'label' => 'Valg påkrævet',
'constraints' => array(new Assert\Choice(array(0,1))),
'choices' => array(0 => 'Nej', 1 => 'Ja')
));
$builder->add('options', 'collection', array(
'type' => new ItemGroupOption(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false
)
);
$builder->add('sort', 'hidden');
}
public function getName()
{
return 'item_group';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
global $app;
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) use ($app) {
// Get submitted data
$data = $form->getData();
if (count($app['validator']->validateValue($data['title'], new Assert\NotBlank())) == 0) {
return array('TitleProvided');
} else {
return false;
}
},
));
}
}
If you are using 2.1 you may want to have a look at "Groups based on submitted data".
Update
Example using the demo contact page /demo/contact in the default AcmeDemoBundle provided with Symfony Standard Edition :
The form type with conditional validation groups :
namespace Acme\DemoBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints as Assert;
class ContactType extends AbstractType
{
// Inject the validator so we can use it in the closure
/**
* #var Validator
*/
private $validator;
/**
* #param Validator $validator
*/
public function __construct(Validator $validator)
{
$this->validator = $validator;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('email', 'email');
$builder->add('message', 'textarea', array(
// Added a constraint that will be applied if an email is provided
'constraints' => new Assert\NotBlank(array('groups' => array('EmailProvided'))),
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
// This is needed as the closure doesn't have access to $this
$validator = $this->validator;
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) use ($validator) {
// Get submitted data
$data = $form->getData();
$email = $data['email'];
// If email field is filled it will not be blank
// Then we add a validation group so we can also check message field
if (count($validator->validateValue($email, new Assert\NotBlank())) == 0) {
return array('EmailProvided');
}
},
));
}
public function getName()
{
return 'contact';
}
}
Don't forget to inject the validator service in the form type :
<?php
namespace Acme\DemoBundle\Controller;
//...
class DemoController extends Controller
{
// ...
public function contactAction()
{
$form = $this->get('form.factory')->create(new ContactType($this->get('validator')));
// ...
}
}
As you can see validation of the message field will be triggered only if the email field is filled.
Use a diff tool to catch the differences.