I have this form :
class RegistrationFormType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class, [
'constraints' => [
new NotBlank(),
]
])
->add('username')
->add('password')
;
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'app_user_register';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
'allow_extra_fields' => true,
'csrf_protection' => false,
]);
}
}
My api in controller :
/**
* #Route("/api/register")
* #Method("POST")
* #param Request $request
*
* #return JsonResponse
*/
public function register(
UserService $userService,
Request $request
)
{
try {
return $userService->register(json_decode($request->getContent(), true));
} catch (\Exception $e) {
return $this->json([
'status' => Response::HTTP_INTERNAL_SERVER_ERROR,
'result' => $e->getMessage()
]);
}
}
And my function in service :
public function register($formData)
{
$user = new User();
$form = $this->formFactory->create(RegistrationFormType::class, $user);
$form->submit($formData);
if ($form->isSubmitted() && $form->isValid()) {
$this->entityManager->persist($user);
$this->entityManager->flush();
return new JsonResponse([
'status' => Response::HTTP_OK,
'result' => true
]);
}
return new JsonResponse([
'status' => Response::HTTP_BAD_REQUEST,
'result' => FormErrorFormatter::getErrorsFromForm($form)
]);
}
When I tried to call the api /api/register in postman with
{
"username": "test1",
"email": "test1",
"password": "123456"
}
I get 200 code, but normally should drop an error because the email is not valid, as I put in form creation that the field email should be in the email format, even if I put an empty string in email I get the 200 code. So seems the validations is not working.
EmailType, as far as I can tell, has no default constraints. However, you override the constraints by demanding it's NotBlank which is definitely not the same as the Email constraint. the Form does add type="email" to the html, which the browser will enforce (which is technically unreliable, because the user can just turn it into a text field).
Solution is probably to use the Email constraint and set the required property to true.
Try :
->add('email', EmailType::class, [
'constraints' => [
new NotBlank(),
new Email(),
]
])
and add to your Entity :
/**
* #Assert\Email(
* message = "The email '{{ value }}' is not a valid email.",
* checkMX = true
* )
*/
protected $email;
Related
This question already has answers here:
Symfony form - Choicetype - Error "Array to string covnersion"
(3 answers)
Closed 1 year ago.
I'm having trouble solving this problem, when i get in the page for creating a user in my Symfony web app, the user controller complains about the createForm() function and the error only says "Notice: Array to string conversion".
The form is the default created with the crud, actually everything is almost default so I can´t think on a single reason this thing doesn´t work as intended.
I've tried to modify the form to use a multiple selection but that didn´t work either.
And I tried to modify the data type in the entity but that just didn´t even worked because it throwed an error in the file immediately.
Here is the UserType.php (form generator)
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
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;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('roles', ChoiceType::class, [
'label' => 'Tipo de usuario',
'attr' => [
'multiple' => true,
'class'=>'form-control myselect'
],
'choices' => [
'Admin' => 'ROLE_ADMIN',
'Usuario' => 'ROLE_USER'
],
])
->add('plainPassword', RepeatedType::class, array(
'type' => PasswordType::class,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password'),
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
The user controller in the breackpoint where Symfony shows the error:
/**
* #Route("/new", name="user_new", methods={"GET","POST"})
*/
public function new(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response
{
$user = new User();
$form = $this->createForm(UserType::class, $user); // <-- This is the highlighted part.
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user->setPassword(
$passwordEncoder->encodePassword(
$user,
$form->get('plainPassword')->getData()
)
);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
return $this->redirectToRoute('user_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('user/new.html.twig', [
'user' => $user,
'form' => $form->createView(),
]);
}
And the user entity in the fragments where i declare the role variable and the getter and setter:
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
$roles is an array, you can see it in your configuration :
/**
* #ORM\Column(type="json")
*/
private $roles = [];
Even if we have type="json", it will be used as an array but in your database it will be a regular json.
ChoiceType here is a string so you can't use it in $roles.
You can add a data transformer (see similar response) to convert the value when displaying it in your select or when using it with your entity.
The simple way to do it is using a CallBackTransformer directly in your form type :
use Symfony\Component\Form\CallbackTransformer;
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
fn ($rolesAsArray) => count($rolesAsArray) ? $rolesAsArray[0]: null,
fn ($rolesAsString) => [$rolesAsString]
));
Or if your are not using PHP 7.4+
$builder->get('roles')
->addModelTransformer(new CallbackTransformer(
function ($rolesAsArray) {
return count($rolesAsArray) ? $rolesAsArray[0]: null;
},
function ($rolesAsString) {
return [$rolesAsString];
}
));
I am working on a simple Checkout Form for Symfony5 and want to do an creation of a user. I have the following code in my Router:
/**
* #Route("/register", name="app_register", methods={"POST","GET"})
* #param Request $request
* #param UserPasswordEncoderInterface $passwordEncoder
* #param GuardAuthenticatorHandler $guardHandler
* #param TokenAuthenticator $authenticator
* #param AuthenticationUtils $authenticationUtils
* #return Response
*/
public function register(
Request $request,
UserPasswordEncoderInterface $passwordEncoder,
GuardAuthenticatorHandler $guardHandler,
TokenAuthenticator $authenticator,
AuthenticationUtils $authenticationUtils
): Response {
$user = new Customer();
$customerAddress = new CustomerAddress();
$user->addCustomerAddress($customerAddress);
$form = $this->createForm(RegistrationFormType::class, $user, [
'action' => $this->generateUrl('app_register'),
'method' => Request::METHOD_POST
]);
$error = $authenticationUtils->getLastAuthenticationError();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// encode the plain password
$user->setPassword(
$passwordEncoder->encodePassword(
$user,
$form->get('plainPassword')->getData()
)
);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
// do anything else you need here, like send an email
return $guardHandler->authenticateUserAndHandleSuccess(
$user,
$request,
$authenticator,
'main' // firewall name in security.yaml
);
}
return $this->render('registration/register.html.twig', [
'userForm' => $form->createView(),
'error' => $error
]);
}
But everytime I submit the form, it does not go into the if. But I can dump $_POST and see the variables (but in an assoc array instead of being in the $_POST directly as Key => Value pairs. Also, $request does not contain the POST variables unless I do a manual call on $request::createFromGlobals(). But still in this case the form does not catch its validation. I think it has something to do with the naming of the form. How could I get this form working?
I'm sharing a sample code with you that I've tested it by myself make sure your form is working properly and data_class has been set in your form.
Also check what are the responses of $form->isSubmitted() and $form->isValid() and try to look to form validation errors It will help you to detect the problem quickly.
// Get Form errors
$form->getErrors(true);
Controller :
/**
* #param Request $request
* #return Response
*/
public function __invoke(Request $request): Response
{
$form = $this->createForm(Registration::class);
$form->handleRequest($request);
if ($request->isMethod('POST')) {
if ($form->isSubmitted() && $form->isValid()) {
/**
* #var $formData User
*/
$formData = $form->getData();
$formData->setPassword($this->passwordEncoder->encodePassword($formData, $formData->getPassword()));
$this->entityManager->persist($formData);
$this->entityManager->flush();
// TODO Redirect to success page
$this->redirectToRoute('register_success');
} else {
$form = $form->createView();
}
} else {
// Creating Registration form
$form = $this
->createForm(Registration::class)
->createView();
}
return $this->render('pages/register.html.twig', ['form' => $form]);
}
From Class :
class Registration extends AbstractType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('firstName', TextType::class, [
'required' => true,
])->add('lastName', TextType::class, [
'required' => true,
])->add('email', EmailType::class, [
'required' => true,
])->add('password', RepeatedType::class, [
'type' => PasswordType::class,
'first_options' => [
'label' => 'Password'
],
'second_options' => [
'label' => 'Repeat Password'
]
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
'error_bubbling' => false
]);
}
}
I'm trying to validate my form by using $form->isValid(). But even if my form is correct it returns false. I already tried to dump my errors with $form->getErrors(true) but then my request times out.
My CreateController.php:
class CreateController extends Controller
{
/**
* #Method({"POST"})
* #Route("/api/v1/matches", name="api_v1_matches_create")
*/
public function index(Request $request, EntityManagerInterface $em): JsonResponse
{
$data = json_decode($request->getContent(), true);
$match = new Match();
$form = $this->createForm(MatchFormType::class, $match);
$form->submit($data);
if ($form->isValid()) {
$em->persist($match);
$em->flush();
return new JsonResponse(null, 201);
} else {
return new JsonResponse(null, 400);
}
}
}
My Form.php
class MatchFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'heroes',
EntityType::class,
[
'class' => Hero::class,
]
)
->add(
'season',
EntityType::class,
[
'class' => Season::class,
]
)
->add(
'map',
EntityType::class,
[
'class' => Map::class,
]
);
}
public function getName(): string
{
return 'match';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Match::class,
]);
}
}
JSON to POST
{
"map": 1,
"heroes": [
1,
2
],
"season": 1
}
Thanks in advance for your help!
I fixed it by adding 'multiple' => true to my heroes entry, so the form knows it's an array and by disable CSRF protection ('csrf_protection' => false as parameter in $resolver).
I believe you may want to follow the practices described in documentation here https://symfony.com/doc/4.1/forms.html#handling-form-submissions
Dividing article example into steps in your case you may want:
Create a new form object
Handle the http request
Check that form is being submitted & is valid
If previous condition is met, get form data & flush it to the db
I'm having to make changes to collections (Symfony form CollectionType) to respect ordering and entity removal and I'm using a preSubmit event listener to make the changes as per this issue suggestion. However the changes aren't making it back to the controller/persistence.
The data is correct after the preSubmit and being set on the event using $event->setData($data) but the changes are not retained after submitting in $form->getData()
My pre submit abstract class:
abstract class AbstractEntityFormType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'onPreSubmit']);
}
/**
* #param FormEvent $event
*/
public function onPreSubmit(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
$document = $form->getData();
foreach ($form->all() as $field) {
/** #var FormInterface $field */
$fieldName = $field->getName();
if ($field->getConfig()->getType()->getInnerType() instanceof CollectionType) {
// .. DO ALL TRANSFORMATION
}
}
$event->setData($data);
}
}
When I dump $data here, everything is correct.
FormType extending this AbstractEntityFormType class:
class DocumentType extends AbstractEntityFormType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
* #return void
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** ADD THE LISTENER **/
parent::buildForm($builder, $options);
$builder->add('title', TextType::class, [
'required' => true,
'constraints' => [
new NotBlank([
'message' => '"title" is empty',
]),
],
]);
$builder->add('symptoms', CollectionType::class, [
'entry_type' => SymptomType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
]);
$builder->add('tags', CollectionType::class, [
'entry_type' => TagType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
]);
$builder->add('submit', SubmitType::class);
}
/**
* #param OptionsResolver $resolver
* #return void
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Document::class,
]);
}
}
My controller action:
/**
* #param Request $request
* #param int|null $documentId
* #return Response
*/
public function formAction(Request $request, int $documentId = null)
{
if (!$documentId) {
$document = new Document();
} else {
try {
$document = $this->documentService->getDocument($documentId);
} catch (InvalidDocumentException $e) {
return new NotFoundResponse('Document not found');
}
}
$document = $this->setAuthor($document);
$form = $this->formFactory->create(DocumentType::class, $document);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$document = $this->documentService->createDocument($form->getData());
return new RedirectResponse($this->router->generate('ui.document.view', [
'documentId' => $document->getId()
]));
}
return $this->templating->renderResponse(
'#KBS/Document/form.html.twig', [
'form' => $form->createView(),
]
);
}
When I dump $form->getData() before persisting the document with the service, the data is incorrect - it hasn't been affected by the preSubmit. What am I missing?
FYI, this is using Symfony 3.2 base components.
Update
Thanks to #LBT, I'm a bit closer but still facing some issues. Using this approach in the controller, I am able to get the correct data out of the request at the point of submitting the form:
public function formAction(Request $request, int $documentId = null)
{
if (!$documentId) {
$document = new Document();
} else {
try {
$document = $this->documentService->getDocument($documentId);
} catch (InvalidDocumentException $e) {
return new NotFoundResponse('Document not found');
}
}
$document = $this->setAuthor($document);
$form = $this->formFactory->create(DocumentType::class, $document);
if ($request->isMethod('POST')) {
$form->submit($request->request->get($form->getName()));
if ($form->isSubmitted() && $form->isValid()) {
$document = $this->documentService->createDocument($form->getData());
return new RedirectResponse($this->router->generate('ui.document.view', [
'documentId' => $document->getId()
]));
}
}
return $this->templating->renderResponse(
'#KBS/Document/form.html.twig', [
'form' => $form->createView(),
]
);
}
However, $form->submit($request->request->get($form->getName())); is also saving incorrect (original) data before being transformed. Even though the data in the request is correct:
/var/www/html/src/Controller/DocumentController.php:94:
array (size=8)
'title' => string 'Tag test' (length=8)
'symptoms' =>
array (size=1)
0 =>
array (size=3)
'position' => string '0' (length=1)
'title' => string 'The smell is terrible' (length=21)
'description' => string 'pew' (length=3)
'tags' =>
array (size=4)
0 =>
array (size=1)
'title' => string 'Volvo' (length=5)
1 =>
array (size=1)
'title' => string 'Engine' (length=6)
2 =>
array (size=1)
'title' => string 'BMW' (length=3)
3 =>
array (size=1)
'title' => string 'Funky' (length=5)
Feel like I'm getting closer but I don't seem to be able to use this request data as suggested in the documentation for some reason.
I am simply trying to add an email constraint to the unmapped field below and for some reason in the controller action it isn't picking it up. Is there something else I need to be doing?
Form Class
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', null, [
'label' => false,
'attr' => ['placeholder' => 'Name*']
]);
$builder->add('email', null, [
'mapped' => false,
'label' => false,
'attr' => ['placeholder' => 'Email*'],
'constraints' => [
new Email(["message" => "Please enter a valid Email Address"])
]
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Test\AppBundle\Entity\Feedback',
'cascade_validation' => true,
'validation_groups' => function(Form $form)
{
$feedback = $form->getData(); /** #var Feedback $feedback */
if ($feedback->getType() == Feedback::Type_Feedback)
return Feedback::ValidationGroup_Feedback;
else if ($feedback->getType() == Feedback::Type_Link)
return Feedback::ValidationGroup_Link;
throw new \Exception("Couldn't generate a valid Validation Group for FeedbackForm.php");
},
));
}
/**
* #return string
*/
public function getName()
{
return 'site_feedback_form';
}
Controller action
public function feedbackSiteSubmitAction(Request $request)
{
// The form award
$feedback = new Feedback();
// Create the form so we can bind send form values to it
$form = $this->createNewFeedbackForm($feedback);
// Bind form values
$form->handleRequest($request);
// Save
if ($form->isValid())
{
// Add the submission to a member
$email = $form->get('email')->getData();
$email = strtolower($email);
$member = $this->getMemberRepository()->loadByEmail($email);
if (!$member)
$member = $this->generateNewPersistedMember($email);
// Update the mail property
$member->setReceiveEmail(!$form->get('dontReceiveAlerts')->getData());
// Add the feedback to the member
$feedback->setMember($member);
// Persist the Feedback
$this->getEntityManager()->persist($feedback);
// Commit
$this->getEntityManager()->flush();
// Response
return $this->jsonSuccess([
'html' => $this->renderView('TestAppBundle:Site/partials:feedback_form_success.html.twig', [
'typeString' => $feedback->getType() == Feedback::Type_Feedback ? "Feedback" : "Resource suggestion"
])
]);
}
// Return errors
return $this->jsonError($this->getAllFormErrors($form));
}
In Symfony 2.8 upwards you do the following:
$builder->add('email', Symfony\Component\Form\Extension\Core\Type\EmailType::class);
Versions before that use:
$builder->add('email', 'email');
Then you add all your validation criteria via annotations in your Feedback class. See the options for EmailType here.