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();
Related
I'm using Symfony 4.2.11 on PHP 7.2 with this code:
<?php
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\LessThan;
class FormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('startDate', DateType::class, [
"required" => true,
'input' => 'datetime_immutable',
/*'constraints' => [
new LessThan([
'propertyPath' => 'parent.all[endDate].data'
]),
]*/
]);
$builder->add('endDate', DateType::class, [
"required" => true,
'input' => 'datetime_immutable',
}
}
$form = $this->formFactory->create(FormType::class);
//$form->submit($formData);
$form->submit([
"startDate" => new \DateTimeImmutable(),
"endDate" => new \DateTimeImmutable()
]);
if (!$form->isValid()) {
echo("Form invalid");
}
However when I run this code it prints out "Form Invalid". How do I fix this code so it accepts all DateTimeImmutable objects?
I don't really know which title to give this thread but, i'm working on a Symfony project in v3.1.6 and using the select2 plugin in field of my form with ajax.
I want to use the event (submit and pre_set_data) of the form component for creation and editing to change the field dynamically like this part of the symfony doc. Everything work fine until when i submit the form and give an error Notice: Undefined variable: options
My form type code here
namespace Xxx\ArticleBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Xxx\ArtistBundle\Repository\ArtistRepository;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Ivory\CKEditorBundle\Form\Type\CKEditorType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Doctrine\ORM\EntityManager;
class ArticleType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
//$em = $options['em']; //this give me same error
$builder
->add('artist', EntityType::class, array(
'class' => 'XxxArtistBundle:Artist',
'choice_label' => 'artist_name',
'multiple' => false,
'expanded' => false))
->add('title', TextType::class)
->add('categories', EntityType::class, array(
'class' => 'XxxArticleBundle:Category',
'choice_label' => 'name',
'multiple' => true,
'expanded' => true))
->add('image', ChoiceType::class, array(
'expanded' => false,
'multiple' => false))
->add('intro', CKEditorType::class)
->add('content', CKEditorType::class);
$formModifier = function (FormInterface $form, $image) {
$listImages = $options['em']->getRepository('XxxAppBundle:Image')->find($image)); //this line give me the error
if (!$listImages) {
return; //i will add a FormError in the field
}
$listImages = array();
die(var_dump($listImages));
$form->add('image', EntityType::class, array(
'class' => 'XxxAppBundle:Image',
'choices' => $listImages,
));
};
$builder->get('image')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$image = $event->getForm()->getData();
//die(var_dump($image)); //select2 field, returned null (but it's not the question
//die(var_dump($options)); returned error 500 Notice: Undefined variable: options
$formModifier($event->getForm()->getParent(), $image);
}
);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Xxx\ArticleBundle\Entity\Article',
));
$resolver->setRequired('em');
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'xxx_articlebundle_article';
}
}
And i also want to say the goal is to have a UI similar w/ Wordpress when create a post from the dashboard, for set an image in an article and when image is selected throw it in a tag to the user and without entityType because it will have thousand so i choose an choiceType to use with the select2 plugin but if someone got a better solution i'm on it.
Thx in advance for the help
When you're using a variable from a parent scope inside a closure, you should pass it to 'use' language construct.
$formModifier = function (FormInterface $form, $image) use ($options) {
$listImages = $options['em']->getRepository('XxxAppBundle:Image')->find($image)); //this line give me the error
if (!$listImages) {
return; //i will add a FormError in the field
}
$listImages = array();
die(var_dump($listImages));
$form->add('image', EntityType::class, array(
'class' => 'XxxAppBundle:Image',
'choices' => $listImages,
));
};
Following the Symfony 3.0 manual on testing Form Types, I followed the example for testing forms with dependencies as follows;
<?php
namespace Tests\AppBundle\Form;
use AppBundle\Form\Type\Database\OfficeAssignType;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
class OfficeTypeTest extends TypeTestCase
{
private $entityManager;
protected function setUp()
{
// mock any dependencies
$this->entityManager = $this->getMock('Doctrine\Common\Persistence\ObjectManager');
parent::setUp();
}
protected function getExtensions()
{
// create a type instance with the mocked dependencies
$office = new OfficeType($this->entityManager);
return array(
// register the type instances with the PreloadedExtension
new PreloadedExtension(array($office), array()),
);
}
public function testSubmitValid()
{
$formData = array(
'name' => 'new test office',
'address1' => 'Test new office address',
'city' => 'Test office city',
'phone' => 'testoffice#stevepop.com',
'country' => '235',
'currrency' => '1',
);
// Instead of creating a new instance, the one created in
// getExtensions() will be used
$form = $this->factory->create(OfficeType::class);
// submit data to form
//$form->submit($formData);
//$this->assertTrue($form->isSynchronized());
}
}
When I run the tests, I get the following error
Argument 1 passed to Symfony\Bridge\Doctrine\Form\Type\DoctrineType::__construct() must be an instance of Doctrine\Common\Persistence\ManagerRegistry, none given, called in /www/stevepop.com/symfony/vendor/symfony/symfony/src/Symfony/Component/Form/FormRegistry.php on line 85 and defined
The OfficeType is below;
namespace AppBundle\Form\Type\Database;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class OfficeType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', TextType::class, array(
'required' => true,
));
$builder->add('address1', TextType::class, array(
'required' => true,
));
$builder->add('country', EntityType::class, array(
'class' => 'AppBundle:Country',
'attr' => array(
'class' => 'input-sm',
),
'required' => true,
'placeholder' => "Non selected",
));
$builder->add('currency', EntityType::class, array(
'class' => 'AppBundle:Currency',
'attr' => array(
'class' => 'input-sm',
),
'required' => true,
'placeholder' => "Non selected",
));
}
I am not sure what this means to be honest and will appreciate help from anyone who has done FormType unit tests in Symfony 2.8/3.0.
Thank you.
Try to adding this to your tests:
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
use Symfony\Component\Form\Extension\Core\CoreExtension;
class OfficeType extends AbstractType {
/**
* #var \Doctrine\ORM\EntityManager
*/
private $em;
public function setUp()
{
$this->em = DoctrineTestHelper::createTestEntityManager();
parent::setUp();
}
protected function getExtensions()
{
$manager = $this->createMock('Doctrine\Common\Persistence\ManagerRegistry');
$manager->expects($this->any())
->method('getManager')
->will($this->returnValue($this->em));
$manager->expects($this->any())
->method('getManagerForClass')
->will($this->returnValue($this->em));
return array(
new CoreExtension(),
new DoctrineOrmExtension($manager),
);
}
// your code...
}
I'm on a problem related to a Symfony2 application which i'm building. The problem concerns an article (News) linked to one or many pictures (Illustration). It seems pretty simple. But i'm stacking on the controller which should persist the News, the Illustration and uploading the picture file.
My News form type:
namespace Fcbg\NewsBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class NewsType extends AbstractType
{
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->add('date', 'date')
->add('titre', 'text')
->add('contenu', 'textarea')
->add('publication', 'checkbox', array('required' => false))
->add('type', 'entity', array( 'class' => 'FcbgNewsBundle:Illustration', 'property' => 'value', 'multiple' => true ));
}
public function getName()
{
return 'News';
}
}
My picture(s) form type:
namespace Fcbg\NewsBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class IllustrationType extends AbstractType
{
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->add('file', 'file');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Fcbg\NewsBundle\Entity\Illustration',
'cascade_validation' => true,
));
}
public function getName()
{
return 'News';
}
}
My controller action:
public function addAction()
{
//link works properly I think
$news = new News();
$illustration = new Illustration();
$illustration->setNews($news);
$news->addIllustration($illustration);
$form = $this->createForm(new NewsType(), $news);
$request = $this->get('request');
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
$doctrine = $this->getDoctrine();
$newsManager = $doctrine->getManager();
$newsManager->persist($news);
$newsManager->persist($illustration);
$newsManager->flush();
return $this->redirect(...);
}
}
return $this->render('base.html.twig',
array(
'content' => 'FcbgNewsBundle:Default:formulaireNews.html.twig',
'form' => $form->createView(),
'name' => "add a news"
)
);
}
The error i get on the execution:
Entities passed to the choice field must be managed. Maybe persist them in the entity manager?
The problem here is that my entity gets a method called "getIllustrations()" which returns logically an arrey of Illustration. So i'm not able to understand this error/question. I assume that my "illustration should be a file field and not a choice field...
Any idea about how i can go further? thx a lot!
I think the problem is that you are using the 'entity' form field here:
->add('type', 'entity', array( 'class' => 'FcbgNewsBundle:Illustration', 'property' => 'value', 'multiple' => true ));
and this type of form field acts as a choice and it's used to work with elements created in the database. You can see this in http://symfony.com/doc/current/reference/forms/types/entity.html
A possible solution could be use the "prototype" like here http://symfony.com/doc/current/cookbook/form/form_collections.html#cookbook-form-collections-new-prototype
where you could have:
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->add('date', 'date')
->add('titre', 'text')
->add('contenu', 'textarea')
->add('publication', 'checkbox', array('required' => false))
->add('type', 'collection', array(
'type' => new IllustrationType(),
'allow_add' => true,
));
}
I hope that this be useful for you.
Kind regards.
So, the code which I was talking about:
public function buildForm( FormBuilderInterface $builder, array $options )
{
$builder
->add('date', 'date')
->add('titre', 'text')
->add('contenu', 'textarea')
->add('publication', 'checkbox', array('required' => false))
->add('illustrations', 'collection', array(
'type' => new IllustrationType(),
'allow_add' => true
));
}
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.