Symfony doesn't validate form - php

I'm writing an API. And I need to validate the form. Other forms validate correctly, but this one does not validate at all.
This is my form type class:
public function buildForm(FormBUilderInterface $builder, array $options){
$builder
->add('passwordConfirmation', RepeatedType::class, [
'required' => true,
'invalid_message' => 'Passwords must match!',
'type' => PasswordType::class
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOption(OptionsResolver $resolver){
$resolver->setDefaults(array(
'data_class' => RepeatPassword::class,
'csrf_protection' => false
))
}
public function getName() {
return 'repeatPassword';
}
My entity:
class RepeatedPassword{
/**
* #var string
* #Assert\Length(
* min = 8,
* minMessage = "Your password must be at least {{ limit }} characters long")
* )
* #Assert\NotBlank()
*/
private $passwordConfirmation;
/**
* #return mixed
*/
public function getPasswordConfirmation() {
return $this->passwordConfirmation;
}
/**
* #param mixed $passwordConfirmation
*/
public function setPasswordConfirmation($passwordConfirmation): void{
$this->passwordConfirmation = $passwordConfirmation;
}
}
Method where I try validate:
public function resetPassword(Request $request): View{
$form = $this->createForm(RepeatPasswordType::class);
$form->handleRequest($request);
if ($form->isValid()) {
$this->userService->setPassword($this->getUser(), $form->getData()->getPasswordConfirmation());
return View::create([], Response::HTTP_OK);
}
return View::create($form->getErrors(), Response::HTTP_BAD_REQUEST);
}
My config.yml file:
validation: { enabled: true, enable_annotations: true }
serializer: { enable_annotations: true }
Data I send and server response with status 400:

I've seen an issue before where $form->handleRequest was behaving differently to $form->submit. I was able to debug more effectively by manually calling submit.
$form = $this->createForm(RepeatPasswordType::class);
if ($request->getMethod() === 'post') {
$form->submit($request->request->get($form->getName()));
if (!$form->isValid()) {
// etc
}
}

Maybe it helps:
add following to your form
'constraints' => [
new NotNull(),
new NotBlank(),
],

All I can see for now is that you have an extra "," at the end of your minMessage Assert. Have you tried removing it ?

Related

Symfony 6 - Fail to inject data into a dynamic formtype on POST_SUBMIT

I have a problem with a nested form. I can't get the values I want to pass.
Here is a simple example to reproduce my problem, I would like to pre-fill a form about a user according to the selected house in my form.
Here are the files, if you want to test. I would like to inject the values of roger and billy the good way but my user fields are always empty
The models
class Test
{
/**
* #var string|null
*/
private $house;
/**
* #var TestUser|null
*/
private $user;
// Getters & Setters of course...
}
class TestUser
{
/**
* #var string|null
*/
private $name;
/**
* #var int|null
*/
private $age;
// Getters & Setters again...
}
The main form
class TestType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('house', ChoiceType::class, [
'choices' => [
'first' => 1,
'second' => 2,
],
]
);
$builder->get('house')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'addUser']);
}
function addUser(FormEvent $event)
{
$form = $event->getForm()->getParent();
$house = $event->getForm()->getData();
if (!$house) {
return;
}
// here is the part where I choose the user I want to use
// for the rest of the example (which does not work)
$testUser = $house === 1
? (new TestUser())->setName('roger')->setAge(65)
: (new TestUser())->setName('billy')->setAge(22);
$builder = $form->getConfig()->getFormFactory()->createNamedBuilder('user', TestUserType::class, $testUser, [
'auto_initialize' => false,
]);
$form->add($builder->getForm());
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Test::class,
]);
}
}
The user form type
class TestUserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class, ['label' => 'username'])
->add('age', IntegerType::class, ['label' => 'age']);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => TestUser::class,
]);
}
}
The controller
public function testForm(Request $request): RedirectResponse|Response
{
// ...
$test = new Test();
$form = $this->createForm(TestType::class, $test);
$form->handleRequest($request);
// ...
}
The view
{{ form_start(formtest) }}
<p>
<button class="btn btn-primary" type="submit">test go</button>
</p>
{{ form_end(formtest) }}
all help is welcome
Is setter actually returning $this?
When ->setAge(65) is executed.
Because it's not clear, it's not in your code you provided here.
you need to link the $user $age $house and $name to the input fields you have.
The reason why you always get an empty output is do to the face that non of the variables refer to any data source.

Testing Form symfony with construct

The problem was too old version of PHPunit
/////
Now I have other problem.I have that test:
class TodoTypeTest extends TypeTestCase
{
private $em;
protected function setUp()
{
$this->em = $this->createMock(EntityManager::class);
parent::setUp();
}
protected function getExtensions()
{
return array(
new PreloadedExtension([
new TodoType($this->em)
], [])
);
}
public function testTodoType()
{
$task = new Todo();
$form = $this->factory->create(TodoType::class, $task, ['locale' => 'en']);
}
}
I get this problem:
Error: Call to a member function getPrioritysInUserLocaleToForm() on null
Problem is here in TodoType class:
class TodoType extends AbstractType
{
/**
* #var EntityManagerInterface
*/
private $em;
/**
* TodoType constructor.
*
* #param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', Type\TextType::class)
->add('content', Type\TextareaType::class)
->add('priority', Type\ChoiceType::class, [ 'choices' => $this->addChoicesInUserLocale($options['locale']) ])
->add('dueDate', Type\DateTimeType::class, [
'widget' => 'single_text',
'attr' => ['class' => 'js-datepicker'],
'html5' => false,
]);
}
/**
* Configure defaults options
*
* #param OptionsResolver $resolver
*/
public function configureOptions( OptionsResolver $resolver )
{
$resolver->setDefaults( [
'locale' => 'en',
] );
}
/**
* Method adds array with choices to ChoiceType in builder
*
* #param string $locale User's locale
*
* #return array All priority in user _locale formatted as array e.g. ['1' => 'low', ...]
*/
private function addChoicesInUserLocale(string $locale): array
{
return $this->em->getRepository('AppBundle:Priority')
->getPrioritysInUserLocaleToForm($locale);
}
}
I do not know why it is not working :/
As the error message tells you, you are trying to call a method on null in this part of the code:
return $this->em->getRepository('AppBundle:Priority')
->getPrioritysInUserLocaleToForm($locale);
That message tells you, that getRepository() returns null.
This happens because the EntityManager you use is a mock, which will always return null for all methods unless specified otherwise. You can fix this by making it return an EntityRepository instead. You can do this in your test method:
public function testTodoType()
{
$repoMock = $this->createMock(PriorityRepository::class);
$repoMock->expects($this->any())
->method('getPrioritysInUserLocaleToForm')
->with('en')
->willReturn([]); // Whatever you want it to return
$this->em->expects($this->any())
->method('getRepository')
->with('AppBundle:Priority')
->willReturn($repoMock);
$task = new Todo();
$form = $this->factory->create(TodoType::class, $task, ['locale' => 'en']);
}
This will make the EntityManager return your Repository-mock which then in turn returns whatever values you want. The expects() calls are assertions and since your test does not have any yet, you might want to check that the repository method is called using $this->atLeastOnce() instead of $this->any(). In any case for the test to be useful you have to make assertions at some point.

How to make Symfony2 form optional field not null on save

I have an image field which is optional. When you upload image, it will save the filename on the database (using events via doctrine ).
The problem is when you edit an already uploaded form and don't add an image, it makes the image field to null.
Is there a way to check / remove the field value setting to null if no image is uploaded?
The Entity, Form code is as below :
class Product
{
/**
* #ORM\Column(type="string", nullable=true)
*
* #Assert\Image
*/
private $image;
}
Form
class ProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('image', FileType::class, [
'required' => !$options['update'],
]);
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$product = $event->getData();
if (null == $form->get('image')->getData()) {
// $form->remove('image');
}
});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Product'
));
}
public function getBlockPrefix()
{
return 'appbundle_product';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired([
'update',
]);
}
}
// In controller
$editForm = $this->createForm(
'AppBundle\Form\ProductType',
$product,
[
'update' => true
]
);
You need the event PRE_SUBMIT, try this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('image', FileType::class, [
'required' => !$options['update'],
]);
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$form = $event->getForm();
$product = $event->getData();
if(empty($product['image'])){
//No new image for existing field was given. Make field disabled so doctrine will not update with empty
$config = $event->getForm()->get('image')->getConfig();
$options = $config->getOptions();
$event->getForm()->add(
'image',
$config->getType()->getName(),
array_replace(
$options,
['disabled' => true]
)
);
}
});
}
Symfony Form has submit() method, which takes two arguments, $submittedData and $clearMissing
/**
* Submits data to the form, transforms and validates it.
*
* #param null|string|array $submittedData The submitted data
* #param bool $clearMissing Whether to set fields to NULL
* when they are missing in the
* submitted data.
*
* #return FormInterface The form instance
*
* #throws Exception\AlreadySubmittedException If the form has already been submitted.
*/
public function submit($submittedData, $clearMissing = true);
$clearMissing parameter is by the default set to true.
If you, in your controller do this: $form->submit($submittedData, false);, your image will not be set to null.
https://symfony.com/doc/current/form/direct_submit.html#calling-form-submit-manually
Similar question: Symfony2 - How to stop Form->handleRequest from nulling fields that don't exist in post data

Symfony Password Reset without FOS Bundle

I'm trying to make a form that users can easily change their password. I hope my logic is correct, however I'm getting an error as follows;
Expected argument of type "string", "AppBundle\Form\ChangePasswordType" given
Here is my controller;
public function changePasswdAction(Request $request)
{
$changePasswordModel = new ChangePassword();
$form = $this->createForm(new ChangePasswordType(), $changePasswordModel);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// perform some action,
// such as encoding with MessageDigestPasswordEncoder and persist
return $this->redirect($this->generateUrl('homepage'));
}
return $this->render(':security:changepassword.html.twig', array(
'form' => $form->createView(),
));
}
Here is my model;
class ChangePassword
{
/**
* #SecurityAssert\UserPassword(
* message = "Wrong value for your current password"
* )
*/
protected $oldPassword;
/**
* #Assert\Length(
* min = 6,
* minMessage = "Password should by at least 6 chars long"
* )
*/
protected $newPassword;
/**
* #return mixed
*/
public function getOldPassword()
{
return $this->oldPassword;
}
/**
* #param mixed $oldPassword
*/
public function setOldPassword($oldPassword)
{
$this->oldPassword = $oldPassword;
}
/**
* #return mixed
*/
public function getNewPassword()
{
return $this->newPassword;
}
/**
* #param mixed $newPassword
*/
public function setNewPassword($newPassword)
{
$this->newPassword = $newPassword;
}
}
Here is my change password type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ChangePasswordType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('oldPassword', 'password');
$builder->add('newPassword', 'repeated', array(
'type' => 'password',
'invalid_message' => 'The password fields must match.',
'required' => true,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
));
}
}
Here is my viewer;
{{ form_widget(form.current_password) }}
{{ form_widget(form.plainPassword.first) }}
{{ form_widget(form.plainPassword.second) }}
The solution mentioned by #dragoste worked well for me.
I changed the following line
$form = $this->createForm(new ChangePasswordType(), $changePasswordModel);
with this line;
$form = $this->createForm(ChangePasswordType::class, $changePasswordModel);
In recent Symfony releases you can pass only class name in createForm
Change
$form = $this->createForm(new ChangePasswordType(), $changePasswordModel);
to
$form = $this->createForm(ChangePasswordType::class, $changePasswordModel);
Read more about building forms at
http://symfony.com/doc/current/best_practices/forms.html#building-forms

Symfony2 Many to Many relation update

I have a many to many relation that is actually made of two many to ones on a third entity.
Domain ← one-to-many → DomainTag ← many-to-one → Tag
I'm building a Rest API, and I want to add domains to a Tag via a specific route. Everything works fine when the tags has no association.
But when I try to add new domains to a tag, there is an error if the tag already as one (or more) domain(s).
The error is
The form's view data is expected to be of type scalar, array or an instance of \ArrayAccess, but is an instance of class AppBundle\Entity\Domain. You can avoid this error by setting the "data_class" option to "AppBundle\Entity\Domain" or by adding a view transformer that transforms an instance of class AppBundle\Entity\Domain to scalar, array or an instance of \ArrayAccess.
Here are the three entities :
Tag
class Tag
{
…
/**
* #ORM\OneToMany(targetEntity="DomainTag", mappedBy="tag", cascade={"persist", "remove"})
*/
private $domainTags;
…
}
DomainTag
class DomainTag
{
…
/**
* #var Tag
*
* #ORM\ManyToOne(targetEntity="Tag", inversedBy="domainTags")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="tag_id", referencedColumnName="id")
* })
*/
private $tag;
/**
* #var Domain
*
* #ORM\ManyToOne(targetEntity="Domain", inversedBy="domainTags")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="domain_id", referencedColumnName="id")
* })
*/
private $domain;
…
}
Domain
class Domain
{
…
/**
* #ORM\OneToMany(targetEntity="DomainTag", mappedBy="domain", cascade={"persist", "remove"})
*/
private $domainTags;
…
}
Here is the form
class AddDomainTagFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('domainTags', 'collection', array(
'type' => new DomainTagFormType(),
'allow_add' => true,
))
;
}
// BC for SF < 2.7
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Tag',
'csrf_protection' => false,
));
}
public function getName()
{
return 'api_set_domaintag';
}
}
DomainTagFormType
class DomainTagFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('weight')
->add('domain', 'entity', array(
'class' => 'AppBundle:Domain',
'property' => 'id',
'multiple' => false,
'expanded' => true
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\DomainTag',
'csrf_protection' => false,
));
}
// BC for SF < 2.7
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
public function getName()
{
return 'api_domaintag';
}
}
And finally, the controller
/**
* #Rest\Put("/{id}/domains", requirements={"id" = "\d+"})
* #Security("has_role('ROLE_SUPER_ADMIN')")
* #Rest\View
*/
public function setDomainAction(Request $request, $id)
{
$tagManager = $this->get('app.manager.tag');
$domainManager = $this->get('app.manager.domain');
$tag = $tagManager->find($id);
if (!$tag) {
return new Response('Tag not found', 404);
}
$form = $this->createForm(new AddDomainTagFormType(), $tag, array('method' => 'PUT'));
$form->handleRequest($request);
if ($form->isValid()) {
foreach ($tag->getDomainTags() as $dt) {
$dt->setTag($tag);
$tagManager->persist($dt);
}
$tagManager->flush($tag);
return new Response('', 204);
} else {
return $this->view($form, 400);
}
}
Do I have to remove all DomainTags from the Tag and the recreate all associations ? Or is there a way to just add/remove DomainTags ?
Thank you

Categories