I use the symfony 2.7.6 full stack framework.
When i submit a form, in the controller i get all submitted data, but in the view there is nothing, an empty form is rendered,something is wrong, i should display the submitted form with errors. but no data and no errors are shown in the twig form...
After hours of debuging of symfony, i can not resolve the problem.
Here is the form Type which is decalred as service:
<?php
namespace Skonsoft\ModelBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Doctrine\Common\Persistence\ObjectManager;
use Skonsoft\UserBundle\Entity\User;
class ConversationType extends AbstractType
{
/**
* #var \Doctrine\Common\Persistence\ObjectManager
*/
private $om;
/**
* #var TokenStorageInterface
*/
protected $tokenStorage;
/**
* Constructor
*
* #param ObjectManager $om
* #param TokenStorageInterface $tokenStorage
*/
public function __construct(ObjectManager $om,
TokenStorageInterface $tokenStorage)
{
$this->om = $om;
$this->tokenStorage = $tokenStorage;
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('message')
->add('advert', 'entity_hidden',
array(
'class' => 'SkonsoftModelBundle:Advert',
'data_class' => null,
'data' => $options['data']->getAdvert()
))
;
$addUser = function(FormBuilderInterface $builder, User $user, $call) {
$builder->addEventListener(FormEvents::PRE_SET_DATA,
function(FormEvent $event) use ($user, $call) {
$entity = $event->getData();
$entity->$call($user);
});
};
$from = $options['data']->getFrom();
if (!$from) {
$from = $this->om->getRepository('SkonsoftUserBundle:User')->find($this->tokenStorage->getToken()->getUser()->getId());
}
$to = $options['data']->getTo();
if (!$to) {
$advert = $this->om->getRepository('SkonsoftModelBundle:Advert')->find($options['data']->getAdvert());
$to = $advert->getUser();
}
$addUser($builder, $from, 'setFrom');
$addUser($builder, $to, 'setTo');
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Skonsoft\ModelBundle\Entity\Conversation'
));
}
/**
* #return string
*/
public function getName()
{
return 'ss_conversation';
}
}
An this is the controller action:
/**
* Creates a new Conversation entity.
*
* #Route("/{id}", name="conversation_create")
* #Method("POST")
* #ParamConverter("advert", class="SkonsoftModelBundle:Advert")
* #Template("SkonsoftUserBundle:Conversation:new.html.twig")
*/
public function createAction(Request $request, Advert $advert)
{
$entity = new Conversation();
$entity->setAdvert($advert);
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
$t = $this->get('translator');
if ($form->isValid()) {
$manager = $this->get('skonsoft_model.conversation_manager');
$manager->persist($entity);
$this->addFlash('success', $t->trans('message.send.success'));
return $this->redirect($request->headers->get('referer'));
}
$this->addFlash('danger', $t->trans('message.send.error'));
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
/**
* Creates a form to create a Conversation entity.
*
* #param Conversation $entity The entity
*
* #return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(Conversation $entity)
{
$form = $this->createForm('ss_conversation', $entity);
return $form;
}
and this is the twig:
{{form_start(form, {'action': path('conversation_create', {'id': entity.advert.id}), 'method': 'POST', 'class':'form-horizontal', 'role':'form' } )}}
<div class="form-group row">
<div class="col-sm-12 col-xs-12">
{{ form_widget(form.message, {'attr': {'class': 'form-control pull-right', 'placeholder':"message.text_holder" | trans, 'rows':'6' } }) }}
</div>
<div class="widget_error col-sm-12 col-xs-12">
{{ form_errors(form.message) }}
</div>
</div>
<div class="form-group row">
<button type="submit" class="btn btn-success btn-lg {{'css.pull.right'|trans}}">{{ "Send" | trans }} </button>
</div>
{{form_end(form)}}
When i dump the FormView in the controller, i get the submitted data and their errors... but not in the wiew
Any Body has any idea what's wrong ?
Thank you
I'm sorry, it was my mistake, not related to symfony.
So, here is the problem:
With this configuration, everything works fine, just i used the render_esi helper to render the form and i forget that render_esi is a new request to make, so when i submit the form, first master request get and process the form, but i lose all form's data after calling the render_esi helper.
Related
I'm currently working on something like a survey/quiz in Symfony. To do so, I have a Question-Entity with an Description, and a Quiz-Entity, which has nothing special unity know except of a submit date.
The important thing is that I want to generate a dynamic form with a random dataset of questions from the db. My current FormType looks like this:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) {
$questions = $event->getData();
$form = $event->getForm();
$i = 0;
foreach($questions as $question) {
$form
->add('question'.$i, TextareaType::class,[
'label' => $question['description'],
'mapped' => false,
])
;
$i++;
}
$form->add('submit', SubmitType::class);
});
}
My controller:
/**
* #Route("/new", name="new")
*/
public function newQuiz(Request $request)
{
$result = $this->quizQuestionRepository->getRandomQuestion();
$form = $this->createForm(QuestionFormType::class, $result);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
dd($data);
}
return $this->render('question/new.html.twig', [
'form' => $form->createView()
]);
}
The Question-Entity:
/**
* #ORM\Entity()
* #ORM\Table(name="questions")
*/
class Question
{
/**
* #ORM\Id()
* #ORM\Column(type="uuid", unique=true)
*/
private string $id;
/**
* #ORM\Column(type="text")
*/
private string $description;
/**
* #ORM\Column(type="string")
*/
private string $category;
public function __construct()
{
$this->id = Uuid::uuid4()->toString();
}
// GETTERS AND SETTERS - NOTHING SPECIAL
}
But this doesn't work. For some reason, I get the questions objects returned, not the insertions from the textareas. Can someone help me out?
Here is my entity:
<?php
namespace App\Entity\Contact;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="contact_contact")
*/
class Contact
{
/**
* #ORM\Id
* #ORM\Column(type="integer", options={"unsigned":true})
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #Assert\NotBlank
* #ORM\Column(type="string", length=40, nullable=true)
*/
private $fname;
/**
* #ORM\Column(type="string", length=40, nullable=true)
*/
private $lname;
public function getId(): ?int
{
return $this->id;
}
public function getFname(): ?string
{
return $this->fname;
}
public function setFname(string $fname): self
{
$this->fname = $fname;
return $this;
}
public function getLname(): ?string
{
return $this->lname;
}
public function setLname(?string $lname): self
{
$this->lname = $lname;
return $this;
}
}
Here is the edit controller action code:
/**
* #Route("/{id}/edit", name="contact_contact_edit", methods={"GET","POST"})
*/
public function edit(Request $request, Contact $contact): Response
{
$form = $this->createForm(ContactType::class, $contact);
$form->handleRequest($request);
if ($form->isSubmitted()) {
if ($form->isValid()) {
$this->getDoctrine()->getManager()->flush();
}
}
return $this->render('contact/contact/edit.html.twig', [
'contact' => $contact,
'form' => $form->createView(),
]);
}
When I post the form but leave the fname (first name) field empty...I get this error (Symfony\Component\PropertyAccess\Exception\InvalidArgumentException)
Expected argument of type "string", "null" given at property path
"fname".
When creating the entity, the #Assert works as expected and the message says so...but if I leave it blank and update post...bzzzt error.
What am I missing?
EDIT | Here is the form class incase thats doing something?
<?php
namespace App\Form\Contact;
use App\Entity\Contact\Contact;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ContactType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fname', TextType::class, ['label' => 'First Name'])
->add('lname', TextType::class, ['label' => 'Last Name']);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Contact::class,
'min_entry' => false,
// NOTE: Must be added to every form class to disable HTML5 validation
'attr' => ['novalidate' => 'novalidate']
]);
$resolver->setAllowedTypes('min_entry', 'bool');
}
}
That's one of the reasons you should avoid allowing the form component changing your entities directly. It will set the data, and then validate it. So it's totally possible for the entity to be in an invalid state after the form has been submitted.
Anyway, you can specify what an empty value should be:
->add('fname', TextType::class, ['label' => 'First Name', 'empty_data' => ''])
I have the follow problem i send this form to the twig but to obtain the request from the view in the controller i must put two "get", how can fix and do better the code in the controller.
//I have this builder RolType.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nombreRol','entity',
array('class' => 'gdrgdrBundle:Rol',
'choice_label' => 'nombreRol',
'required' => false,
)) ;
}
//My controller mainController.php
$rol = new Rol();
$formRol = $this->createForm(new RolType(), $rol);
$em = $this->getDoctrine()->getManager();
if ($request->isMethod('POST')) {
$formRol->handleRequest($request);
if ($formRol->isSubmitted() && $formRol->isValid()) {
$rolResult = $rol->getNombreRol()->getNombreRol()));
}
}
//View Rol.html.twig
<form action="{{ path("rol")}}" method="post" role="form">
{{ form_errors(nuevoRolForm) }}
{{ form_widget(nuevoRolForm._token) }}
{{ form_rest(nuevoRolForm) }}
</form>
//Entity Rol.php
class Rol
{
private $id;
private $nombreRol;
/**
* Set nombreRol
*
* #param string $nombreRol
* #return Rol
*/
public function setNombreRol($nombreRol)
{
$this->nombreRol = $nombreRol;
return $this;
}
/**
* Get nombreRol
*
* #return string
*/
public function getNombreRol()
{
return $this->nombreRol;
}
}
I am having problem with creating new Collection entity with Form.
I want to create new Collection entity with form and then to be redirected to collections page with 'collection_user_collections' route, and be able to see new collection in user's collections list. But instead when I press submit button on form, I get following error:
No route found for "POST /profile/": Method Not Allowed (Allow: GET, HEAD)
Below is my code:
class Collection{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
private $name;
private $url;
private $type;
const STATUS_PRIVATE = 0;
const STATUS_PUBLIC = 1;
/**
* #ORM\ManyToOne(targetEntity="MyMini\UserBundle\Entity\User", inversedBy="collections")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
private $date_created;
private $date_modified;
/* getters and setters are here*/
}
I am using CollectionType to build form:
class CollectionType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name','text')
->add('type', 'choice', array('choices' => array(
Collection::STATUS_PRIVATE => 'Private',
Collection::STATUS_PUBLIC => 'Public',
)))
->add('save', 'submit', array('label' => 'Create Collection'))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyMini\CollectionBundle\Entity\Collection'
));
}
public function getName()
{
return 'mymini_collectionbundle_collection';
}
}
This is createAction, here I tried to insert current user's username and date when entity was created. I am using FOSUserBundle to manage app users:
/**
* #Route("/create-collection/", name="collection_create_collection")
* #Template()
*/
public function createAction(Request $request)
{
$collection = new Collection();
$user = $this->get('security.token_storage')->getToken()->getUser();
$username = $user->getUsername();
$form = $this->createForm(new CollectionType(), $collection);
$form->handleRequest($request);
if ($form->isValid() && $form->isSubmitted()) {
$em = $this->getDoctrine()->getManager();
$collection->setUser($user);
$collection->setDateCreated(new \DateTime());
$em->persist($collection);
$em->flush();
return $this->redirectToRoute('collection_user_collections', array('username' => $username));
}
return array('collection'=>$collection, 'form' => $form->createView());
}
Twig for form:
<div class="collection-create">
<h3 id="create-collection">Create a collection</h3>
<a class="close-reveal-modal" aria-label="Close">×</a>
{{ form(form) }}
</div>
The exception you're receiving is expected. You are calling the createForm method without passing all necessary arguments. The right way to create a form is:
$this->createForm(
new CollectionType(),
$collection,
array(
'action' => $this->generateUrl('collection_create_collection') ),
'method' => 'PUT', // or 'POST'
)
);
What should happen here is this:
Only dd/mm/yyyy format will be accepted for DOB.
If different format given then "The DoB field must have a valid format." message should read on the screen BUT this message should be coming from the ENTITY, not form TYPE set with 'invalid_message' attribute.
JFYI: I can define $dob as 'string' in the entity and as 'text' in the form type to make whole process work but it is not good practise. The reason is I don't want varchar field for $dob in database, I want date field.
PERSON ENTITY (Note: This is the only place I want form validation takes place):
namespace Se\HirBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* Person
*
* #ORM\Entity
* #ORM\Table(name="person")
* #ORM\HasLifecycleCallbacks
*/
class Person
{
/**
* #var integer $id
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string $firstname
*
* #Assert\NotBlank(message = "The Firstname field should not be blank.")
* #Assert\Length(max = "100", maxMessage = "The Firstname field cannot be longer than {{ limit }} characters length.")
*
* #ORM\Column(type = "string", length = 100)
*/
protected $firstname;
/**
* #var date $dob
*
* #Assert\NotBlank(message = "The DoB field should not be blank.")
* #Assert\Regex(pattern = "/^(0[1-9]|[12][0-9]|3[01])[\/\-](0[1-9]|1[012])[\/\-]\d{4}$/", message = "The DoB field must have a valid format.")
*
* #ORM\Column(type = "date", length = 10)
*/
protected $dob;
/**
* #var datetime $created
*
* #ORM\Column(type="datetime")
*/
protected $created;
/**
* #var datetime $updated
*
* #ORM\Column(type="datetime", nullable = true)
*/
protected $updated;
/**
* Gets triggered only on insert
*
* #ORM\PrePersist
*/
public function onPrePersist()
{
$this->created = new \DateTime("now");
}
/**
* Gets triggered every time on update
*
* #ORM\PreUpdate
*/
public function onPreUpdate()
{
$this->updated = new \DateTime("now");
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstname
*
* #param string $firstname
* #return Person
*/
public function setFirstname($firstname)
{
$this->firstname = $firstname;
return $this;
}
/**
* Get firstname
*
* #return string
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* Set dob
*
* #param string $dob
* #return Person
*/
public function setDob($dob)
{
$this->dob = $dob;
return $this;
}
/**
* Get dob
*
* #return string
*/
public function getDob()
{
return $this->dob;
}
}
Form TYPE file:
namespace Se\HirBundle\Form\Type\Person;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class CreateType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAction($options['action'])
->setMethod('POST')
->add('firstname', 'text',
array('label' => 'Firstname', 'error_bubbling' => true))
->add('dob', 'date',
array('label' => 'DoB', 'widget' => 'single_text',
'format' => 'dd/MM/yyyy', 'input' => 'datetime', 'error_bubbling' => true))
->add('create', 'submit',
array('label' => 'Create Person'));
}
public function getName()
{
return 'personcreate';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Se\HirBundle\Entity\Person'));
}
}
CONTROLLER:
namespace Se\HirBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Se\HirBundle\Entity\Person;
use Se\HirBundle\Form\Type\Person\CreateType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class CrudController extends Controller
{
public function createAction()
{
$person = new Person();
$form = $this->createForm(new CreateType(), $person,
array('action' => $this->generateUrl('create_submit')));
return $this->render('SeHirBundle:Default:create.html.twig',
array('page' => 'Create', 'form' => $form->createView()));
}
public function createPersonAction(Request $request)
{
if ($request->getMethod() != 'POST')
{
return new Response('Only POST method is accepted');
}
$person = new Person();
$form = $this->createForm(new CreateType(), $person,
array('action' => $this->generateUrl('create_submit')));
$form->handleRequest($request);
if ($form->isValid())
{
$submission = $form->getData();
$em = $this->getDoctrine()->getManager();
$person = new Person();
$person->setFirstname($submission->getFirstname());
$person->setDob($submission->getDob());
$em->persist($person);
$em->flush();
$this->get('session')->getFlashBag()->add('message', 'Person successfully created!');
return $this->redirect($this->generateUrl('message'));
}
return $this->render('SeHirBundle:Default:create.html.twig',
array('page' => 'Create', 'form' => $form->createView()));
}
}
TWIG:
{% extends '::base.html.twig' %}
{% block title %}{{ page }}{% endblock %}
{% block body %}
<b>{{ page|upper }}</b>
<hr />
{{ form_start(form, {attr: {novalidate:'novalidate'}}) }}
{% if form_errors(form) != '' %}
<div>{{ form_errors(form) }}</div>
{% endif %}
<div>
{{ form_label(form.firstname) }}
{{ form_widget(form.firstname) }}
</div>
<div>
{{ form_label(form.dob) }}
{{ form_widget(form.dob, {'type':'text'}) }}
</div>
<br />
<div>
{{ form_widget(form.create) }}
</div>
{{ form_end(form)}}
{% endblock %}
Here is a solution based on the Symfony2 documentation:
Controller
Start of the file
use Symfony\Component\HttpFoundation\Response;
Function in the controller
This controller will display the dob field, since this is a Datetime object it requires to use format() in order to display it. This is just an example to show that Symfony2 recognizes the date and transform it internally.
Uncommenting the lines starting with // would be sufficient to persist the Person entity with the dob.
public function testAction(Request $request)
{
$person = new Person();
$form = $this->createFormBuilder($person)
->add('dob', 'date',
array(
'label' => 'DoB',
'widget' => 'single_text',
'format' => 'dd/MM/yyyy',
'invalid_message' => 'Validation error goes here',
'error_bubbling' => true,
'input' => 'datetime' # return a Datetime object (*)
)
)
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
# perform some action, such as saving the task to the database
//$em = $this->getDoctrine()->getManager();
//$em->persist($person);
//$em->flush();
return new Response($person->getDob()->format('d-m-Y'));
}
return $this->render(
'YourBundle:Default:test.html.twig',
array(
'form' => $form->createView()
)
);
}
(*): http://symfony.com/fr/doc/master/reference/forms/types/date.html#input
Twig file
{{ form(form) }}
routing.yml
test:
pattern: /test/
defaults: { _controller: YourBundle:Default:test }
SOLUTION in Controller:
$dob = date('Y-m-d', strtotime(str_replace('/', '-', $submission->getDob())));
$person->setDob($dob);