Save Form from another Action - php

I have Two Action, GetAllPost and newComment
I have a page with many Post and each Post have commentForm
PostController
public function getPostAction () {
return array(
);
}
Twig
{% for post in app.user.posts %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ render(controller("ADVCommentBundle:Comment:newComment" ,{ 'id': post.id,'redirect':'get_post' } )) }}
<hr>
{%endfor%}
CommentController
public function newCommentAction (Request $request, Post $post) {
$em = $this->getEm();
$comment = new Comment();
$form = $this->createForm(new CommentType(), $comment);
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
}
return array(
'post' => $post,
'form' => $form->createView(),
);
}
TwifFormController
{{ form(form, {'action': path('new_comment',{'id': post.id})})}}
When I insert a new comment I have redirect to new_comment even if my value isn't valid.
How can I redirect to GeTAllPost and show the correct Error or the new Comment?
I tried to use
return $this->redirect($this->generateUrl('get_post',array('error',$form->getErrors())));
and 'error_bubbling' => true,, but each time request a get_post ( GetAllPost ) I do a new render of my Form and I don't see the errors
For Example i'd like to use newCommentAction in several scenario.
For example i GetAllPost for each post, but even in GetSpecificPost, where I Have A specific post, where I Can insert a new comment, but the save ( and the Action ) is the same.
Do I have create a Service ?
UPDATE
After Bonswouar's answer. This is my Code
PostController
/**
* #Route("/",name="get_posts")
* #Template()
*/
public function getPostsAction () {
$comment = new Comment();
return array(
'commentForms' => $this->createCreateForm($comment),
);
}
private function createCreateForm (Comment $entity) {
$em = $this->getEm();
$posts = $em->getRepository('ADVPostBundle:Post')->findAll();
$commentForms = array();
foreach ($posts as $post) {
$form = $this->createForm(new CommentType($post->getId()), $entity);
$commentForms[$post->getId()] = $form->createView();
}
return $commentForms;
}
/**
* #Method({"POST"})
* #Route("/new_comment/{id}",name="new_comment")
* #Template("#ADVPost/Post/getPosts.html.twig")
* #ParamConverter("post", class="ADVPostBundle:Post")
*/
public function newCommentAction (Request $request, Post $post) {
$em = $this->getEm();
$comment = new Comment();
//Sometimes I Have only One Form
$commentForms = $this->createCreateForm($comment);
$form = $this->createForm(new CommentType($post->getId()), $comment);
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
} else {
$commentForms[$post->getId()] = $form->createView();
}
return array(
'commentForms' => $commentForms,
);
}
And I Don't have any Render.
But, I want to re-use newCommentAction also in Single Post, and i Want to create Only one Form. I don't want use $commentForms = $this->createCreateForm($comment);, because i Want just one form,and I have to change template even. How can I do ?

If I'm not mistaking, your problem is that you're posting on new_comment, which is a "sub action".
You actually don't need this Twig render.
You could just generate all the forms you need in the main Action, with something like this :
foreach ($posts as $post) {
$form = $this->createForm(new CommentType($post->getId()), new Comment());
$form->handleRequest($request);
if ($form->isValid()) {
//...
// Edited : to "empty" the form if submitted & valid. Another option would be to redirect()
$form = $this->createForm(new CommentType($post->getId()), new Comment());
}
$commentForms[$post->getId()] = $form->createView();
}
return array(
'posts' => $posts,
'commentForms' => $commentForms,
);
Not forgetting to set a dynamic Name in your Form class :
class CommentType extends AbstractType
{
public function __construct($id) {
$this->postId = $id;
}
public function getName() {
return 'your_form_name'.$this->postId;
}
//...
}
And then just normally render your forms in your Twig loop. You should get the errors.
{% for post in app.user.posts %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ form(commentForms[post.id]) }}
<hr>
{%endfor%}
If I didn't miss anything that should do the job.
UPDATE :
After seeing your update, this might be the controller you want (sorry if I didn't understand properly or if I did some mistakes):
/**
* #Route("/",name="get_posts")
* #Template()
*/
public function getPostsAction () {
$em = $this->getEm();
$posts = $em->getRepository('ADVPostBundle:Post')->findAll();
$commentForms = array();
foreach ($posts as $post) {
$commentForms[$post->getId()] = $this->createCommentForm($post);
}
return array(
'commentForms' => $commentForms
);
}
private function createCommentForm (Post $post, $request = null) {
$em = $this->getEm();
$form = $this->createForm(new CommentType($post->getId()), new Comment());
if ($request) {
$form->handleRequest($request);
if ($form->isValid()) {
try {
$em->beginTransaction();
$comment->setPost($post);
$em->persist($comment);
$em->flush();
$em->commit();
} catch (\Exception $e) {
$em->rollback();
throw $e;
}
$form = $this->createForm(new CommentType($post->getId()), new Comment());
}
}
return $form;
}
/**
* #Method({"POST"})
* #Route("/new_comment/{id}",name="new_comment")
* #Template("#ADVPost/Post/getPosts.html.twig")
* #ParamConverter("post", class="ADVPostBundle:Post")
*/
public function newCommentAction (Request $request, Post $post) {
return array(
'commentForm' => $this->createCommentForm($post, $request);
);
}

What about using a flash message to set your error messages? http://symfony.com/doc/current/components/http_foundation/sessions.html#flash-messages
EDIT: Modifying based on your comments. In your controller you could do this:
foreach ($form->getErrors() as $error) {
$this->addFlash('error', $post->getId().'|'.$error->getMessage());
}
or
$this->addFlash('error', $post->getId().'|'.(string) $form->getErrors(true, false));
What this will do is allow you to tie the error to the particular post you want, as you are passing it a string like 355|This value is already used. If you need to know the field you could add another delimiter for $error->getPropertyPath() in your flash message, or you could overwrite the error name in the entity itself.
Then in your controller you could parse out the flash messages, and add them to an array that your twig template would check:
$errors = array();
foreach ($this->get('session')->getFlashBag()->get('error', array()) as $error)
{
list($postId, $message) = explode('|', $error);
$errors[$postId][] = $message;
}
return array('errors' => $errors, ...anything else you send to the template)
Now your twig template can check for the existence of errors on that particular form:
{% for post in app.user.posts %}
{% if errors[post.id] is defined %}
<ul class="errors">
{% for error_message in errors[post.id] %}
<li>{{ error_message }}</li>
{% endfor %}
</ul>
{% endif %}
<p>{{ post.id }} - {{ post.description }} </p>
{{ render(controller("ADVCommentBundle:Comment:newComment" ,{ 'id': post.id,'redirect':'get_post' } )) }}
<hr>
{%endfor%}

Related

Symfony2 form: create entity on Ajax success

I am working with Symfony2 and I am trying to create new entity Collection without page reload. My controller works fine, but I have issues with Ajax, I am NOT well familiar with it. Below is the code I already have. When I press submit button, new entity is saved in db, but entity doesn't appear on page.
CollectionController
/**
* #Route("/create-collection", name="collection_create_collection")
* #Template()
*/
public function createAction(Request $request)
{
$collection = new Collection();
$form = $this->createForm(
new CollectionType(),
$collection,
array('method' => 'POST',
'action' => $this->generateUrl('collection_create_submit'),
)
);
return array('collection'=>$collection, 'form' => $form->createView());
}
/**
* #Route("/collection_create_submit", name="collection_create_submit")
*/
public function createSubmitAction(Request $request)
{
$collection = new Collection();
$user = $this->getUser();
$form = $this->createForm(
new CollectionType(),
$collection,
array('method' => 'POST',
)
);
$form->handleRequest($request);
if ($form->isValid() && $form->isSubmitted()) {
$colname = $form["name"]->getData();
$existing = $this->getDoctrine()->getRepository('CollectionBundle:Collection')->findBy(['name' => $colname, 'user' => $user]);
if ($existing) {
return new JsonResponse(['error' => 'Collection with such name already exists']);
}
$em = $this->getDoctrine()->getManager();
$em->persist($collection);
$em->flush();
return new JsonResponse(array(
'success' => $collection
));
}
}
create.html.twig
{% include 'CollectionBundle:Collection:collectionJS.html.twig' %}
<div class="collection-create">
<h3 id="create-collection">Create a collection</h3>
<a class="close-reveal-modal" aria-label="Close">×</a>
{{ form_start(form, {'attr' : {'action' : 'collection_create_collection', 'id': 'create-collection-form'}}) }}
{{ form_widget(form) }}
<a class="button custom-close-reveal-modal" aria-label="Close">Cancel</a>
<button type="submit" value="create" class="right" onclick="createCollection();">Create</button>
{{ form_end(form) }}
</div>
<script type="application/javascript">
$('a.custom-close-reveal-modal').on('click', function(){
$('#externalModal').foundation('reveal', 'close');
});
</script>
collectionJS.html.twig
function createCollection(){
var $form = $('#create-collection-form');
$($form).submit(function(e) {
e.preventDefault();
$.ajax({
type: "POST",
url: $form.attr('action'),
data: $form.serialize()
}).done(function( data ) {
if(data.error)
{
console.log(data.error);
} else if(data.success) {
var collection = data.success;
$('.griditems').prepend('<p>' + collection + '</p>');
$('#externalModal').foundation('reveal', 'close');
}
});
});
}
UPD
The submit is triggered, but now I get undefined instead of entity. Probably, I am sending wrong json response.
UPD
I tried to encode collection entity.
In createSubmitAction I changed return to
$jsonCollection = new JsonEncoder();
$jsonCollection->encode($collection, $format = 'json');
return new JsonResponse(array(
'success' => $jsonCollection
));
How do I get this response in Ajax?
JsonResponse cannot transform your entities into JSON. You need to use a serializer library like JMSSerializerBundle or implement a serializing-method inside your entity.
Check the answers provided here.
How to encode Doctrine entities to JSON in Symfony 2.0 AJAX application?
You have to return a simple $collection object as the standard doctrine entity objects are too large in size. What I recommend is:
return new JsonResponse(array(
'success' => $collection->toArray()
));
and adding a new method to your entity class:
public function toArray(){
return array(
'id' =>$this->getId(),
'name'=>$this->getName(),
// ... and whatever more properties you need
); }

symfony2 edit more entities in same page

How can edit more products entities in same page (no 1 to many).
In My editaction :
$entities=$em->getRepository('MyBundle:Product')->findAll();
$editForm=array();
$deleteForm=array();
foreach ($entities as $product )
{
$editForm [$port->getId()]= $this->createEditForm($product);
$deleteForm[$port->getId()] = $this->createDeleteForm($product->getId());
}
return $this->render('MyBundle:Product:edit.html.twig', array(
'entities' => $entities,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
I have this error :
Error: Call to a member function createView() on a non-object
And how update the edit.thml.twig to show all form update as table with only one submit ?
This is not about Symfony2, but about PHP, you are calling a method onto an array...
Consider something like this :
$entities = $em->getRepository('MyBundle:Product')->findAll();
$editForms = array();
$deleteForms = array();
foreach ($entities as $product)
{
$editForms[$port->getId()] = $this->createEditForm($product)
->createView();
$deleteForms[$port->getId()] = $this->createDeleteForm($product->getId())
->createView();
}
return $this->render('MyBundle:Product:edit.html.twig', array(
'entities' => $entities,
'edit_forms' => $editForms,
'delete_forms' => $deleteForms,
));
With a template like this one :
{% for form in edit_foms %}
{{ form(form) }}
{% endfor %}
{% for form in delete_foms %}
{{ form(form) }}
{% endfor %}
I have fix this :
change the name of form:
public $name;
/**
* #return string
*/
public function getName()
{
return (string)'port_'.$this->name;
}
public function __construct($name=0) {
$this->name=$name;
}
and in my controller
editAction :
$entities = $em->getRepository('InfraProductBundle:InfraPortDdf')->findAll();
foreach ($entities as $port )
{
$editForm [$port->getId()]= $this->createEditForm($port);
$edit_view[$port->getId()]=$editForm[$port->getId()]->createView();
}
return $this->render('......:edit.html.twig', array(
'entities' => $entities,
'edit_form' => $edit_view,
));
in update action
foreach ($array_id as $key=>$id){
if(!is_numeric(str_replace('port_','',$id)))
continue;
$entity = $em->getRepository('InfraProductBundle:InfraPortDdf')->find(str_replace('port_','',$id));
if (!$entity) {
throw $this->createNotFoundException('Unable to find InfraPortDdf entity.');
}
$editForm = $this->createEditForm($entity);
if($editForm->submit($request->request->get($id)))
$em->flush();
}
in my edit.html.twig:
{% for key, edit in edit_form %}
...
{{form_widget(edit.description,{name:'['~key~'][description]', 'attr': { 'name' : '['~key~'][description]' } } ) }}
....
{%endfor%}

Search form with multiple criteria , Symfony2

I'm trying to create a search form with multiple criteria but it doesn't work, and I think the controller does not receive the form because when I click Button Submit the page reloads only.
This is the Controller
/**
* #ParamConverter("agence", options={"mapping": {"agence_slug":"slug"}})
*/
public function indexAction(Agence $agence, Request $request)
{
$form = $this->createForm(new SearchTravelType());
$request = $this->getRequest();
$em = $this->getDoctrine()->getManager();
if ($request->getMethod() == 'POST')
{
$form->bind($request);
if ($form->isValid())
{
$criteria = $form->getData();
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->getListBy($criteria, $agence);
}
}
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->findByAgence($agence);
return $this->render('ProjectDashboardBundle:Travel:index.html.twig',
array(
'listTravels' => $listTravels,
'agence' => $agence,
'form' => $form->createView() ,
));
}
and this is the queryBuilder
class TravelRepository extends EntityRepository
{
public function getListBy($criteria, $agence)
{
$qb = $this->createQueryBuilder('t');
$qb = $this->whereAgence($qb, $agence);
$qb ->leftJoin('t.airport', 'a')
->addSelect('a');
foreach ($criteria as $field => $value) {
if (!$this->getClassMetadata()->hasField($field)) {
// Make sure we only use existing fields (avoid any injection)
continue;
}
$qb ->andWhere($qb->expr()->eq('t.'.$field, ':t_'.$field))
->setParameter('t_'.$field, $value);
}
return $qb->getQuery()->getResult();
}
public function whereAgence (\Doctrine\ORM\QueryBuilder $qb, $agence)
{
$qb->where('a.agence = :agence')
->setParameter('agence', $agence);
return $qb;
}
}
and this is the page twig and the form
<form class="form-horizontal" role="form" method="post" >
<td>{{ form_widget(form.id) }}</td>
<td class="center">-</td>
<td>{{ form_widget(form.title) }}</td>
<td>{{ form_widget(form.country) }}</td>
<td>{{ form_widget(form.destination) }}</td>
<td>{{ form_widget(form.airport) }}</td>
<td>{{ form_widget(form.departureDate) }}</td>
<td>{{ form_widget(form.returnDate) }}</td>
<td>{{ form_widget(form.price) }}</td>
<td>{{ form_widget(form.enabled) }}</td>
<td><span class="input-group-btn">
<button type="submit" class="btn btn-purple btn-sm">Search
<i class="icon-search icon-on-right bigger-110"></i></button>
</span>
</form>
I think the first problem is that form is not recovered in the controller, I tried with
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->find(1);
instead
getListBy($criteria, $agence)
to see what gives. the controller ignores the form and it goes directly to the following queries
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->findByAgence($agence);
You can remove $request = $this->getRequest(); from action as you are passing REquest object to this action;
You need to add action to your form,
like this:
$this->createForm(new SearchTravelType(), null, [
'action' => $this->generateUrl('ROUT')
]);
or like this:
<form class="form-horizontal" role="form" method="post" action="{{ path('ROUT') }}" >
EDIT:
We create a filter form with GET method (you should use POST method for inserting to DB, not for fetching results), disable CSRF protection. We create a controller action:
public function indexAction(Agence $agence, Request $request)
{
$form = $this->createForm(new SearchTravelType(), null, [
'action' => $this->generateUrl('ROUTE'),
'method' => 'GET'
]);
$form->handleRequest($request);
if ($form->isValid())
{
$em = $this->getDoctrine()->getManager();
$criteria = explode('&', $request->query->get($form->getName())); // I didn't test this line, you should check it..
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->getListBy($criteria, $agence);
// Terminate the request
return $this->render('ProjectDashboardBundle:Travel:index.html.twig',
array(
'listTravels' => $listTravels,
'agence' => $agence,
'form' => $form->createView() ,
));
}
$listTravels = $em->getRepository('ProjectTravelBundle:Travel')->findByAgence($agence);
return $this->render('ProjectDashboardBundle:Travel:index.html.twig',
array(
'listTravels' => $listTravels,
'agence' => $agence,
'form' => $form->createView() ,
));
}
You can try this bundle:
https://github.com/petkopara/PetkoparaMultiSearchBundle
Provides form or service for multi criteria search in Doctrine Entity.
In your case the form solution will solve your issue:
First in your form type just only add the MultiSearchType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('search', MultiSearchType::class, array(
'class' => 'ProjectTravelBundle:Travel'));
}
Then in your Controller add the following code
public function indexAction(Request $request)
{
$search = $request->get('search');
$em = $this->getDoctrine()->getManager();
$queryBuilder = $em->getRepository('ProjectTravel:Travel')->createQueryBuilder('e');
$filterForm = $this->createForm('ProjectTravelBundle\Form\TravelType');
// Bind values from the request
$filterForm->handleRequest($request);
if ($filterForm->isValid()) {
// Build the query from the given form object
$queryBuilder = $this->get('petkopara_multi_search.builder')->searchForm($queryBuilder, $filterForm->get('search'));
}
..
}
At the last render your Travel form in your template
{{form_render(filter_form)}}

Handle form errors in controller and pass it to twig

I'm trying to populate $errors['field_name'] = 'Error message'; in my controller so that I can pass the variable to twig for further processing. How can I loop thru the errors and create my own array variable?
I've checked and applied these but didn't get the exact answer, or maybe I missed.
Accessing and Debugging Symfony Form Errors
Symfony2 : How to get form validation errors after binding the
request to the form (Works but is it reliable?)
Symfony2 – Getting All Errors From a Form in a Controller (Overkill)
FORM TYPE
->add('name', 'text', array('label' => 'Name', 'error_bubbling' => true))
->add('origin', 'text', array('label' => 'Origin', 'error_bubbling' => true))
TWIG
{% if errors is defined %}
<ul>
{% for field, message in errors %}
<li>{{ field ~ ':' ~ message }}</li>
{% endfor %}
</ul>
{% endif %}
CONTROLLER
public function submitAction(Request $request)
{
$form = $this->createForm(new BrandsType(), new Brands());
$form->handleRequest($request);
if ($form->isValid() !== true)
{
$errors['field_name'] = 'Error message';
return $this->render('CarBrandBundle:brands.html.twig',
array('errors' => $errors, 'form' => $form->createView()));
}
}
Try a method like this:
public function getErrorMessages(FormInterface $form)
{
$errors = array();
//this part get global form errors (like csrf token error)
foreach ($form->getErrors() as $error) {
$errors[] = $error->getMessage();
}
//this part get errors for form fields
/** #var Form $child */
foreach ($form->all() as $child) {
if (!$child->isValid()) {
$options = $child->getConfig()->getOptions();
//there can be more than one field error, that's why implode is here
$errors[$options['label'] ? $options['label'] : ucwords($child->getName())] = implode('; ', $this->getErrorMessages($child));
}
}
return $errors;
}
This method will return what you want, which is associative array with form errors.
The usage of it would be in your case (controller):
if ($form->isValid() !== true)
{
$errors = $this->getErrorMessages($form);
return $this->render('CarBrandBundle:brands.html.twig',
array('errors' => $errors, 'form' => $form->createView()));
}
This usage assumes you have getErrorMessages method in your controller, however a better idea would be creating some class with this method and registering it as a service (you might want to reuse it in other controllers)

"The CSRF token is invalid" error in symfony 2 even using form_rest(form) function

I've been trying to create a simple form in symfony but each time I try to submit I get the following error:
ERROR: The CSRF token is invalid. Please try to resubmit the form.
After surfing on the Internet and reducing the code to almost empty. I still get that error. Most of the people who I've seen asking for that solved the error using the following twig code
{{ form_rest(form) }}
The problem is that I'm using it, it's like when I bind the request it doesn't do it correctly. I don't know what else can I do.
This is my small twig template:
<div><h2>Insert new activity</h2></div>
<div>
<form id="new-activity" action="{{ path('create') }}" method="post" {{ form_enctype(form) }}>
{{ form_rest(form) }}
<p>
<button type="submit">Submit</button>
</p>
</form>
</div>
As you can see it is pretty small code. This is my form render code:
/**
* Displays a form to create a new Activity entity.
*
* #Route("/new", name="sucr_new")
* #Template()
*/
public function newAction() {
$initialConfig = new SucrConfiguration();
$finalConfig = new SucrConfiguration();
$activity = new SucrActivity();
$data = array("activity" =>$activity,
"initialConfig" => $initialConfig,
"finalConfig" => $finalConfig);
$form = $this->createForm(new ActivityType(), $data);
return array(
'data' => $data,
'form' => $form->createView()
);
}
And this is the code that should handle the submition:
/**
* Displays a form to create a new Activity entity.
*
* #Route("/create", name="create")
* #Method("post")
* #Template("EusocSucrBundle:Sucr:new.html.twig")
*/
public function createAction() {
$initialConfig = new SucrConfiguration();
$finalConfig = new SucrConfiguration();
$activity = new SucrActivity();
$data = array("activity" =>$activity,
"initialConfig" => $initialConfig,
"finalConfig" => $finalConfig);
$form = $this->createForm(new ActivityType(), $data);
if ($this->getRequest()->getMethod() == 'POST') {
$form->bindRequest($this->getRequest());
if ($form->isValid()) {
return $this->redirect($this->generateUrl('sucr_show', array('id' => 1)));
}
var_dump($form->getErrorsAsString());
}
return array(
'data' => $data,
'form' => $form->createView()
);
}
Also note that I can see the hidden token in my browser.
Any ideas?

Categories