I'm trying to create galleries in my Symfony2 Web App.
Each post may or may not have a gallery. My galleries are text mapping types, under the Post entity/class:
#Post.orm.yml
MyProject\MyProjectBundle\Entity\Post:
type: entity
table: post
repositoryClass: MyProject\MyProjectBundle\Entity\PostRepository
id:
id:
type: integer
generator: { strategy: AUTO }
fields:
# ...
gallery:
type: text
nullable: true
#...
As there are many images in a gallery, I figured it would have made sense to separate each image with a comma via my data fixture:
image1.png, image2.jpg, examplename-3rdimage.gif, 4thandfinal.jpg
However, I want the gallery to be output like this when it's viewed:
<li>image1.png</li>
<li>image2.jpg</li>
<li>examplename-3rdimage.gif</li>
<li>4thandfinal.jpg</li>
My controller then calls for the Post Entity:
/* PostController.php */
public function postshowAction($id)
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('MPMPBBundle:Post')->find($id);
$gallery = $em->getRepository('MPMPBBundle:Post')->getGallery();
if (!$entities) {
throw $this->createNotFoundException('Unable to find Post entity.');
}
return $this->render('MPMPBBundle:Post:postshow.html.twig', array(
'entities' => $entities,
'gallery' => $gallery
));
}
As you may have noticed, I reference the function: getGallery() from my repository class: PostRepository:
/* PostRepository.php */
class PostRepository extends EntityRepository
{
public function getGallery()
{
$postGallery = $this->createQueryBuilder('e')
->select('e.gallery')
->getQuery()
->getResult();
$gallery = array();
foreach ($postGallery as $postGal)
{
$gallery = array_merge(explode(",", $postGal['gallery']), $gallery);
}
foreach ($gallery as &$gal)
{
$gal = trim($gal);
}
return $gallery;
}
}
Finally, my twig file: postshow.html.twig, looks like this:
{% for gallery in gallery %}
<li>{{ gallery }}</li>
{% endfor %}
To clarify, what I am looking to achieve is this:
# mysite.com/post/post-1
<li>image1.png</li>
<li>image2.jpg</li>
<li>examplename-3rdimage.gif</li>
<li>4thandfinal.jpg</li>
# mysite.com/post/post-2
<li>image5.png</li>
<li>image11.jpg</li>
<li>examplename-18thimage.gif</li>
<li>22ndandfinal.jpg</li>
Each post displays it's respective gallery.
With what's written above, what is achieved is that ALL the Gallery items from every Post is output, whereas I only need the gallery item for individual posts:
# mysite.com/post/post-1
<li>image1.png</li>
<li>image2.jpg</li>
<li>examplename-3rdimage.gif</li>
<li>4thandfinal.jpg</li>
<li>image5.png</li>
<li>image11.jpg</li>
<li>examplename-18thimage.gif</li>
<li>22ndandfinal.jpg</li>
# mysite.com/post/post-2
<li>image1.png</li>
<li>image2.jpg</li>
<li>examplename-3rdimage.gif</li>
<li>4thandfinal.jpg</li>
<li>image5.png</li>
<li>image11.jpg</li>
<li>examplename-18thimage.gif</li>
<li>22ndandfinal.jpg</li>
The gallery field is in the Post table, so you dont need to query again in the db, just get the gallery from the current entitie and explode() it like this:
/* PostController.php */
public function postshowAction($id)
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('MPMPBBundle:Post')->find($id);
$gallery = null;
if (null !== $entities->getGallery())
{
$gallery = explode(",", $entities->getGallery());
$gallery = array_map("trim", $gallery);
}
if (!$entities) {
throw $this->createNotFoundException('Unable to find Post entity.');
}
return $this->render('MPMPBBundle:Post:postshow.html.twig', array(
'entities' => $entities,
'gallery' => $gallery
));
}
Related
I am a laravel beginner and I would like to display custom data on the website because I am creating a REST-API. I wish it were something like this
success: true
posts:
1:
post_id: 436523
title: "Post Title"
message: "Post Message"
author: "Post Author"
meta: [...]
and that it would be possible to customize meta / links and choose whether they should all be displayed or, for example, the meta itself without links
My Code:
class NewsController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$posts = News::all();
$i = 1;
$data = [];
foreach($posts as $post){
$data[$i] = [
'post_id'=> $post['post_id'],
'icon'=> $post['icon'],
'date'=> $post['created_on'],
'title'=> $post['title'],
'message'=> $post['message'],
'author'=> $post['author'],
];
$i++;
}
$filter = new NewsFilter();
$filterItems = $filter->transform($request);
$news = News::where($filterItems);
if(count($filterItems) == 0){
return ['success'=> true, 'Posts Count'=> News::count(), 'posts'=> $data];
}else{
$news = new NewsCollection($news->paginate()->appends($request->query()));
return ['success'=> true, $news];
}
}
}
I don't think there's any way to remove the contents of the links and meta arrays unless you overwrite the core functions that generate them.
Anyway, when it comes to adding data, you can use the with() function to append any extra information.
I tried to look up on Google but didn't find anyone with such a problem. I think I did everything like the documentation guides but I guess I'm missing something
So I have a form with checkbox like this:
$builder->add(
'productTypes',
EntityType::class,
array(
'label' => 'Available for products',
'class' => 'ShopBundle:ProductType',
'choice_label' => 'name',
'multiple' => true,
'expanded' => true,
'by_reference' => false,
)
);
When I'm editing everything goes smooth, I can edit existing entry and check or uncheck this checkbox, it saves properly, but when I try to add new Object I get error:
PHP Fatal error: Call to a member function add() on null in
C:\xampp\htdocs\uniacar-sf\src\ShopBundle\Entity\ProductAttribute.php
on line 188
This is my controller action:
public function editAction(Request $request, $id = null)
{
$this->setMenuTab('cars', 'admin');
$productTypes = new ArrayCollection();
if (!empty($id)) {
$attribute = $this->getRepo(ProductAttribute::class)->find($id);
$this->setTitle('admin.cars.attributes.edit');
foreach ($attribute->getProductTypes() as $value) {
$productTypes->add($value);
}
} else {
$attribute = new ProductAttribute();
$this->setTitle('admin.cars.attributes.new');
}
$form = $this->createForm(ProductAttributeForm::class, $attribute);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$attribute = $form->getData();
foreach ($productTypes as $productType) {
if (false === $attribute->getProductTypes()->contains($productType)) {
$productType->getAttributes()->removeElement($attribute);
$this->db()->persist($productType);
}
}
$this->db()->persist($attribute);
$this->db()->flush();
return $this->redirectToRoute('carAdmin', array('tab' => 'attributes'));
}
$this->setVariables(
array(
'form' => $form->createView(),
'attribute' => $attribute,
)
);
return $this->response();
}
$this->db() is my shortcut for $this->getDoctrine()->getManager()
And this is definition part of ProductAttribute that relates to ProductType:
/**
* Constructor
*/
public function __construct() {
$this->productTypes = new ArrayCollection();
}
/**
* Many Attributes have Many ProductTypes
* #ORM\ManyToMany(targetEntity="ProductType", mappedBy="attributes", cascade={"persist"})
*/
private $productTypes;
/**
* #param ProductType $productType
*/
public function addProductType(ProductType $productType)
{
$this->productTypes->add($productType);
$productType->addProductAttribute($this);
}
/**
* #param ProductType $productType
*/
public function removeProductType(ProductType $productType)
{
$this->productTypes->removeElement($productType);
}
Also there is part of ProductType Entity that relates to ProductAttribute:
/**
* Constructor
*/
public function __construct() {
$this->attributes = new ArrayCollection();
}
/**
* Many ProductTypes have Many Attributes
* #ORM\ManyToMany(targetEntity="ProductAttribute", inversedBy="productTypes")
* #ORM\JoinTable(name="product_type_to_attribute")
*/
private $attributes;
/**
* #param ProductAttribute $attribute
*/
public function addProductAttribute(ProductAttribute $attribute)
{
if (!$this->attributes->contains($attribute)) {
$this->attributes->add($attribute);
}
}
public function removeProductAttribute(ProductAttribute $attribute)
{
$this->attributes->removeElement($attribute);
}
I tried to follow Symfony Embed Form Tutorial (How to Embed a Collection of Forms)
I know that in this case there is no embeded collection (I have another field in this Entity, that is embeded collection of forms and it works just fine) but from what I understand relations are the same in this case, it's many to many so I have to tell the Symfony how to treat relations, add and remove objects.
I dumped data that comes in POST but it's the same as for edition - productType is there. Any ideas why do I get this error?
It fires in ProductAttribute Entity in the line $this->productTypes->add($productType);
EDIT:
I updated the controller code, I messed up the logic about unlinking ProductType from ProductAttribute. But it doesn't have any impact on the problem, still the same 500 error when I try to save new object.
EDIT2:
I can't get stack trace from Symfony because I get ordinary browser 500 error (probably because it's Fatal Error, I found it in apache logs). The line in controller which creates error is $form->handleRequest($request);.
This is not a Collection of Forms, but you are using collection specific method, this is not a good practice, however, you don't need this below code when you create a new object.
foreach ($productTypes as $value) {
if (false === $attribute->getProductTypes()->contains($value)) {
$attribute->getProductTypes()->removeElement($value);
}
}
So, I haven't found solution to the problem but I solved it somehow by fixing file structure of my project (moved bundle's Resources from general Resources folder to Bundle's Resources folder). I have no idea why this fixed the issue and what is even the connection between working but not proper folder structure and submitting forms but now it works, so I will mark the question as answered.
Let's suppose I have an entity class and a generic FormType corresponding to the entity
class Entry {
protected $start_time;
protected $end_time;
protected $confirmed; // Boolean
// ... Other fields and getters and setters
}
On the CRUD of this entity, I don't want to allow any modification on start_time or end_time if the entity has been confirmed or in the above case when $confirmed === true
On the twig file, I disable the fields I want to restrict, like the following:
{% if entity.approved == true %}
{{ form_row(entity.start_time), { 'attr' : { 'disabled' : 'disabled' } }) }}
{% endif %}
{# Sameway for another field #}
Now the problem is that this is a front end resolution which can be tampered very easily using web developer tools in web browsers now. But regardless what I am trying to achieve is not have those two fields changed once the entity in confirmed.
So, one way I tried was after the form was submitted, I check if the entity was confirmed and if it was was, I fetch the earlier state of the entity and set the value of the new one (which is about to be persisted) with the values from old one.
On Controller:
$confirmed = $entity->getConfirmed();
$form->handleRequest($request);
if($form->isSubmitted() && $editForm->isValid()) {
// The form was submitted
if($confirmed === true) { // if the entity was confirmed previously
$oldEntity = $em->getRepository('...')->find($entity->getId());
$entity->setStartTime($oldEntity->getStartTime());
$entity->setEndTime($oldEntity->getEndTime());
}
$em->persist($entity);
$em->flush();
}
The problem here was $oldEntity was exactly same as $entity. My guess is doctrine picked up that it already has the entity that is being asked and just returned me with the same object. Anyways, my attempt to solve this problem failed.
Any idea how to restrict/revert changes on selected properties while allowing changes on rest of the properties of the entity?
Update:
Modifying the form type to disable the field is not an option because I only want them to be read-only/disabled only if entity is confirmed and rest of time I want the form to be as it is.
You must add attribute 'disabled' => true in form builder, not only in twig.
If you don't want a user to modify the value of a field, you can set the disabled option to true. Any submitted value will be ignored.
Reference: http://symfony.com/doc/current/reference/forms/types/form.html#disabled
If you wish modify dynamically, use form events, example:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$entity = $event->getData();
// if confirmed, disable start_time field
if ($entity->getConfirmed()) {
$config = $form->get('start_time')->getConfig();
$options = $config->getOptions();
// set disabled option to true
$options['disabled'] = true;
// replace origin field
$form->add(
'start_time',
$config->getType()->getName(),
$options
);
}
});
I think I understand your problem now #Starx, I didn't read very carefully at first, and your update helped.
Maybe you need to detach your Entity?
Check this link about (Entities in Session)[http://doctrine-orm.readthedocs.io/projects/doctrine-orm/en/latest/cookbook/entities-in-session.html]. Maybe storing the Entity in a session will work. Detaching as a separate Entity might work, and do a comparison on the detached Entity to your updated Entity.
I found two ways to solve this:
You can retrieve original data of the entity. It returns an array with old data of an entity which can be used to reset the data.
if($form->isSubmitted() && $editForm->isValid()) {
// The form was submitted
if($confirmed === true) { // if the entity was confirmed previously
$oldData = $em->getUnitOfWork()->getOriginalEntityData($entity);
$entity->setStartTime($oldData['start_time']);
$entity->setEndTime($oldData['end_time']);
}
$em->persist($entity);
$em->flush();
}
Using Form Events
The following is a Symfony 3 Solution, try maximkou's answer for Symfony 2.
class EntityType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ....
// ....
// Your form fields
$builder->addEventListener(FormEvents::POST_SET_DATA, array($this, 'onPreSetData'));
}
public function onPreSetData(FormEvent $event) {
/** #var YourEntity $entity */
$entity = $event->getData();
$form = $event->getForm();
if($entity instanceof YourEntity) {
if ($entity->getTimesheetEntry()->getTimeApproved() === true) {
$config = $form->get('start_time')->getConfig();
$options = $config->getOptions();
$options['disabled'] = true;
$form->add('start_time', get_class($config->getType()->getInnerType()), $options);
$config = $form->get('end_time')->getConfig();
$options = $config->getOptions();
$options['disabled'] = true;
$form->add('end_time', get_class($config->getType()->getInnerType()), $options);
}
}
}
}
Source
I have problem saving entity trough form with ManyToMany relations.
I can not save fields that are on "mappedBy" side of relation.
Code below is not saving anything to database and not trowing any errors:
// Entity/Pet
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Customer", mappedBy="pet", cascade={"persist"})
*/
private $customer;
/**
* Set customer
*
* #param \AppBundle\Entity\Customer $customer
* #return Pet
*/
public function setCustomer($customer)
{
$this->customer = $customer;
return $this;
}
// Entity/Customer
/**
* #var Pet
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Pet", inversedBy="customer", cascade={"persist"})
* #ORM\JoinTable(name="customer_pet",
* joinColumns={
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="pet_id", referencedColumnName="id")
* }
* )
*/
private $pet;
// PetType.php
$builder->add('customer', 'entity',
array(
'class' => 'AppBundle:Customer',
'property' => 'firstname',
'empty_value' => 'Choose owner',
'multiple' => true
));
It is working the other way around. So if I am saving something from CustomerType everything works.
EDIT:
Solution below worked for me but after couple days I found a problem with that solution. If form will be submitted with value that has been already saved in the database then Symfony will trow an error. To prevent that I had to check if given customer has been already assigned to the pet.
Checking of currently assigned customers had to be done on the beginning of function and not after form submission because for some reason after submission Pet() object contains submitted values not only those present in the db.
So on the beginning I've putted all already assigned customers in to the array
$em = $this->getDoctrine()->getManager();
$pet = $em->getRepository('AppBundle:Pet')->find($id);
$petOriginalOwners = array();
foreach ($pet->getCustomer() as $petCustomer)
{
$petOriginalOwners[] = $petCustomer->getId();
}
And after form submission I've checked if submitted ID's are in the array
if ($form->isValid())
{
foreach ($form['customer']->getData()->getValues() as $v)
{
$customer = $em->getRepository('AppBundle:Customer')->find($v->getId());
if ($customer && !in_array($v->getId(), $petOriginalOwners) )
{
$customer->addPet($pet);
}
}
$em->persist($pet);
$em->flush();
return $this->redirect($this->generateUrl('path'));
}
In Symfony2 the entity with the property with the inversedBy doctrine comment is the one that is supposed to EDIT THE EXTRA TABLE CREATED BY THE MANYTOMANY RELATION. That is why when you create a customer it inserts the corresponding rows in that extra table, saving the corresponding pets.
If you want the same behavior to happen the other way around, I recommend:
//PetController.php
public function createAction(Request $request) {
$entity = new Pet();
$form = $this->createCreateForm($entity);
$form->submit($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
foreach ($form['customer']->getData()->getValues() as $v) {
$customer = $em->getRepository('AppBundle:Customer')->find($v->getId());
if ($customer) {
$customer->addPet($entity);
}
}
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('pet_show', array('id' => $entity->getId())));
}
return $this->render('AppBundle:pet:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
private function createCreateForm(Pet $entity) {
$form = $this->createForm(new PetType(), $entity, array(
'action' => $this->generateUrl('pet_create'),
'method' => 'POST',
));
return $form;
}
These two are but standard Symfony2 CRUD-generated actions in the controller corresponding to Pet entity.
The only tweak is the foreach structure inserted in the first action, that way you forcibly add the same pet to each customer you select in the form, thus getting the desired behavior.
Look, it is highly probable THIS is not the RIGHT WAY, or the PROPER WAY, but is A WAY and it works. Hope it helps.
In my case with a services <-> projects scenario, where services has "inversedBy" and projects has "mappedBy" I had to do this in my project controller's edit action so that when editing a project the services you checked would be persisted.
public function editAction(Request $request, Project $project = null)
{
// Check entity exists blurb, and get it from the repository, if you're inputting an entity ID instead of object ...
// << Many-to-many mappedBy hack
$servicesOriginal = new ArrayCollection();
foreach ($project->getServices() as $service) {
$servicesOriginal->add($service);
}
// >> Many-to-many mappedBy hack
$form = $this->createForm(ProjectType::class, $project);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
// << Many-to-many mappedBy hack
foreach ($servicesOriginal as $service) {
if (!$project->getServices()->contains($service)) {
$service->removeProject($project);
$em->persist($service);
}
}
foreach ($project->getServices() as $service) {
$service->addProject($project);
$em->persist($service);
}
// >> Many-to-many mappedBy hack
$em->persist($project);
$em->flush();
return; // I have a custom `redirectWithMessage()` here, use what you like ...
}
return $this->render("Your-template", [
$form => $form->createView(),
$project => $project,
]);
}
This works for both adding and removing entities in the many-to-many from the "mappedBy" side, so EntityType inputs should work as intended.
What's going on here is we're first building an "original" collection containing all of the service entities already linked to for this project. Then when the form is saving we're ensuring:
First that any unchecked services (those in the original collection but not the project object) have the project removed from their internal collection, then persisted.
Second that any newly checked services each add the project to their internal collection, then persisted.
Important: This depends on your entity's addService() and addProject() methods respectively check that each others' collections do not contain duplications. If you don't do this you'll end up with an SQL level error about a duplicate record insertion.
In the service entity I have:
/**
* Add project
*
* #param Project $project
*
* #return Service
*/
public function addProject(Project $project)
{
if (!$this->projects->contains($project)) {
$this->projects->add($project);
}
if (!$project->getServices()->contains($this)) {
$project->getServices()->add($this);
}
return $this;
}
In the project entity I have:
/**
* Add service
*
* #param Service $service
*
* #return Project
*/
public function addService(Service $service)
{
if (!$this->services->contains($service)) {
$this->services->add($service);
}
if (!$service->getProjects()->contains($this)) {
$service->getProjects()->add($this);
}
return $this;
}
You could alternatively check this in your controller instead, but makes sense if the model validates this itself when possible, as the model would break anyway if there were duplicates from any source.
Finally in your controller's create action you'll likely need this bit too just before $em->persist($project). (You won't need to work with an "original" collection as none will exist yet.)
// << Many-to-many mappedBy hack
foreach ($project->getServices() as $service) {
$service->addProject($project);
$em->persist($service);
}
// >> Many-to-many mappedBy hack
I just had the same problem and I solved it differently.
Changing the code in the controller is not the better way to do it.
In my case I have a GenericController that handle all my CRUDs so I can't put in it specific code.
The best way to do it is by adding in your PetType a listener like this :
// PetType.php
$builder->add('customer', 'entity',
array(
'class' => 'AppBundle:Customer',
'property' => 'firstname',
'empty_value' => 'Choose owner',
'multiple' => true
))
->addEventListener( FormEvents::SUBMIT, function( FormEvent $event ) {
/** #var Pet $pet */
$pet = $event->getData();
foreach ( $pet->getCustomers() as $customer ) {
$customer->addPet( $pet );
}
} );
That way you'll keep the mapping logic in the same place.
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%}