I'm losing a lot of time on a (maybe) basic feature that everyone needs in his project.
The fact is that I'm stuck with form builder (a real daemon in this framework, I don't understand why it's so hard to use).
I got three entities :
Recette
Ingredient
RecettesIngredients
Both Recette and Ingredient do an One-To-Many with RecettesIngredients. It's a many-to-many with extra fields.
I've done a form builder RecetteType that does :
->add('recettesIngredients', CollectionType::class, [
'entry_type' => RecettesIngredientsType::class,
'allow_add' => true,
'allow_delete' => true,
'label' => 'XXX'
])
I've done a form builder (he works fine) RecettesIngredientsType that does :
->add('ingredients', EntityType::class, [
// looks for choices from this entity
'class' => Ingredient::class,
'choice_label' => 'nom',
// used to render a select box, check boxes or radios
'multiple' => true,
'expanded' => true
])
When I try to render it:
{{ form_row(form.recettesIngredients) }}
If I don't give an object to my form builder :
I just got nothing to display but a blank page without error.
If I give an object to my form builder :
I have an error:
"Unable to transform value for property path "ingredients": Expected a Doctrine\Common\Collections\Collection object."
If I remove the "multiple" option, I got a list of ingredients but without checkboxes to select them.
My question is simple :
How can I display a form in a Many-To-Many with extra field relation context properly?
I made your exemple following the symfony documentation.
The Entities following your exemple:
/**
* #ORM\Entity(repositoryClass=RecetteRepository::class)
*/
class Recette
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity=RecetteIngredient::class, mappedBy="recette")
*
*/
private $ingredients;
/**
* #ORM\Entity(repositoryClass=IngredientRepository::class)
*/
class Ingredient
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity=RecetteIngredient::class, mappedBy="ingredient")
*/
private $recette;
/**
* #ORM\Entity(repositoryClass=RecetteIngredientRepository::class)
*/
class RecetteIngredient
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $quantity;
/**
* #ORM\ManyToOne(targetEntity=Recette::class, inversedBy="ingredients")
* #ORM\JoinColumn(nullable=false)
*/
private $recette;
/**
* #ORM\ManyToOne(targetEntity=Ingredient::class, inversedBy="recette")
* #ORM\JoinColumn(nullable=false)
*/
private $ingredient;
My FormTypes are from your exemple.
I created two Ingredient in my DB
And made this controller code to test
$recette = new Recette();
$recette->setName("Crèpes");
$recetteIngredient = new RecetteIngredient();
$recetteIngredient->setQuantity(5);
$recette->addIngredient($recetteIngredient);
$form = $this->createForm(RecetteType::class, $recette);
return $this->render('demo.html.twig',['form'=>$form->createView()]);
The thing that I changed is the render. Since the number of Ingredient is dynamic, Symfony (and php server's code) has no idea the number of related entities you want to add or delete and you can not just do a form_row() on the collection type.
Here's my code grabed from the doc (I didn't changed html attributes name, it's just a demo.
{{ form_start(form) }}
{{ form_row(form.name) }}
<ul id="email-fields-list"
data-prototype="{{ form_widget(form.ingredients.vars.prototype)|e }}"
data-widget-tags="{{ '<li></li>'|e }}"
data-widget-counter="{{ form.ingredients|length }}">
{% for ingredient in form.ingredients %}
<li>
{{ form_errors(ingredient) }}
{{ form_widget(ingredient) }}
</li>
{% endfor %}
</ul>
<button type="button"
class="add-another-collection-widget"
data-list-selector="#email-fields-list">Add another ingredient</button>
{{ form_end(form) }}
<script
src="https://code.jquery.com/jquery-3.6.0.slim.min.js"
integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI="
crossorigin="anonymous"></script>
<script>
jQuery(document).ready(function () {
jQuery('.add-another-collection-widget').click(function (e) {
var list = jQuery(jQuery(this).attr('data-list-selector'));
// Try to find the counter of the list or use the length of the list
var counter = list.data('widget-counter') || list.children().length;
// grab the prototype template
var newWidget = list.attr('data-prototype');
// replace the "__name__" used in the id and name of the prototype
// with a number that's unique to your emails
// end name attribute looks like name="contact[emails][2]"
newWidget = newWidget.replace(/__name__/g, counter);
// Increase the counter
counter++;
// And store it, the length cannot be used if deleting widgets is allowed
list.data('widget-counter', counter);
// create a new list element and add it to the list
var newElem = jQuery(list.attr('data-widget-tags')).html(newWidget);
newElem.appendTo(list);
});
});
</script>
Here's the result
Related
I am checking for friends that has accepted my friend request and thus are my real friends. But when displaying my friends I get myself as friend and not the person I have for friend sometimes. This appears when for example my id is second in the table.
I have tried checking if the userid is in either of the columns. Also tried with having two way friendship like A friend with B but B friend with A too. I couldnt get the repository work for the second method so I sticked to this one.
My repository:
public function personalFriends($userId){
$em = $this->getEntityManager();
$result = $em->createQuery('SELECT friends FROM AppBundle\Entity\Friends friends
INNER JOIN AppBundle\Entity\User myuser WHERE (friends.friendsWithMe = :userId OR friends.afriendof = :userId) AND friends.friends = 1');
$result->setParameter('userId', $userId);
return $result->getResult();
}
My controller:
public function indexAction(Request $request)
{
$user = $this->get('security.token_storage')->getToken()->getUser();
$userId = $user->getId();
$posts = $this->getDoctrine()->getRepository(UserPosts::class)->findUserPosts($userId,1);
$friends = $this->getDoctrine()->getRepository(Friends::class)->personalFriends($userId);
return $this->render('default/index.html.twig',
['formed' => null,
'posts' => $posts,
'posted'=>null,
'search' => null,
'friends' => $friends
]);
}
My view :
<div class="card card-body">
{{ app.user.username|capitalize }}'s friends:
{% if friends %}
<div>
{% for friend in friends %}
{{ friend.afriendof.username }}
{% endfor %}
</div>
{% else %}
<div>
You have no friends.
</div>
{% endif %}
</div>
Friends Entity:
class Friends
{
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="myfriends", fetch="EAGER")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $friendsWithMe;
/**
* #ORM\Id()
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="friendof", fetch="EAGER")
* #ORM\JoinColumn(name="friend_id", referencedColumnName="id", nullable=false)
*/
private $afriendof;
/**
* #var integer
*
* #ORM\Column(name="request_sent", type="smallint")
*/
private $requestSent;
/**
* #var integer
*
* #ORM\Column(name="friends", type="smallint")
*/
private $friends;
I want to get only the friends and not myself. I get that I have to change the friend.afriendof.username with friend.friendswithme.username, but how to know when to change between those two.
This assert is passing Symfony's form validation when uploading any file with VichUploaderBundle:
/**
* #Vich\UploadableField(mapping="product_media", fileNameProperty="path")
* #Assert\File(
* mimeTypes = {"image/jpeg", "image/gif", "image/png", "video/mp4", "video/quicktime", "video/avi"},
* mimeTypesMessage = "Wrong file type (jpg,gif,png,mp4,mov,avi)"
* )
* #var File $pathFile
*/
protected $pathFile;
I cannot see what the problem is with the assert. How can I validate file types with VichUploader?
You can use validation callback to solve this issue.
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\Repository\EntityRepository")
* #ORM\Table(name="entity")
* #Assert\Callback(methods={"validate"})
* #Vich\Uploadable
*/
class Entity
{
/**
* #Assert\File(maxSize="10M")
* #Vich\UploadableField(mapping="files", fileNameProperty="fileName")
*
* #var File $file
*/
protected $file;
/**
* #ORM\Column(type="string", length=255, name="file_name", nullable=true)
*
* #var string $fileName
*/
protected $fileName;
...
/**
* #param ExecutionContextInterface $context
*/
public function validate(ExecutionContextInterface $context)
{
if (! in_array($this->file->getMimeType(), array(
'image/jpeg',
'image/gif',
'image/png',
'video/mp4',
'video/quicktime',
'video/avi',
))) {
$context
->buildViolation('Wrong file type (jpg,gif,png,mp4,mov,avi)')
->atPath('fileName')
->addViolation()
;
}
}
}
For Symfony 4.0 you'll need to import the Validator component
composer require validator
Now in your Entity class you can use the #Assert annotation.
// src/Entity/Author.php
// ...
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* #Assert\NotBlank()
*/
public $name;
}
You might need to add some configuration in your config/packages/framework.yaml file. Anyway, all this is perfectly explained on the official Symfony documentation.
http://symfony.com/doc/current/validation.html
To check the mime type you'll need to use the File constraint http://symfony.com/doc/current/reference/constraints/File.html
Here is a working exemple
/**
* #ORM\Column(type="string", length=255)
* #var string
*/
private $cvFilename;
/**
* #Assert\File(
* maxSize = "2048k",
* mimeTypes = {"application/pdf", "application/x-pdf"},
* mimeTypesMessage = "Please upload a valid PDF"
* )
* #Vich\UploadableField(mapping="cv", fileNameProperty="cvFilename")
* #var File
*/
private $cvFile;
Now it's true that there is a mime and size option inside the #Vich\UploadableField anotation as described here https://github.com/dustin10/VichUploaderBundle/blob/master/Resources/doc/usage.md#step-2-link-the-upload-mapping-to-an-entity
but I couldn't get this to work.
The #Assert annotation will generate Forms errors, that you can retrieve them in Twig to give a feedback.
The key is to use : form_errors(candidature_form.cvFile)
here is a working example :
{% set error_flag = form_errors(candidature_form.cvFile) %}
<label class=" {% if error_flag %}has-error{% endif %}">
Curriculum Vitae (PDF)
</label>
{{ form_widget(candidature_form.cvFile) }}
{% if error_flag %}
<div class="has-error">
{{ form_errors(candidature_form.cvFile) }}
</div>
{% endif %}
For Symfony 4.x, this solution not working. i don't know why assert or event validator never call ...
I've find this solution : Validation doesn't work on relation fields
use Symfony\Component\Validator\Constraints\File;
/* ... */
->add('ba_file', VichFileType::class, [
'label' => 'Bon d\'adhésion (PDF file)',
'required' => false,
'constraints' => [
new File([
'maxSize' => '5M',
'mimeTypes' => [
'image/jpeg',
'image/gif',
'image/png',
]
])
]
])
For Symfony 3.0+, only 2 things need to be done:
Add use statement to import ExecutionContextInterface.
The callback annotation has to be added directly to the method/function instead of the class.
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* #Assert\File(maxSize="2M")
* #Vich\UploadableField(mapping="profile_image", fileNameProperty="avatar")
* #var File
*/
private $imageFile;
/**
* #ORM\Column(length=255, nullable=true)
* #var string $avatar
*/
protected $avatar;
/**
* #Assert\Callback
* #param ExecutionContextInterface $context
*/
public function validate(ExecutionContextInterface $context, $payload)
{
// do your own validation
if (! in_array($this->imageFile->getMimeType(), array(
'image/jpeg',
'image/gif',
'image/png'
))) {
$context
->buildViolation('Wrong file type (only jpg,gif,png allowed)')
->atPath('imageFile')
->addViolation();
}
}
Symfony 6 - PHP 8:
Entity class:
#[Vich\UploadableField(mapping: 'avatars', fileNameProperty: 'avatarName')]
#[Assert\File(maxSize: '1024k', mimeTypes: ['image/jpeg', 'image/png'])]
private ?File $avatarFile = null;
FormType class:
'attr' => [
'accept' => 'image/jpeg, image/png',
],
'constraints' => [
new Assert\File([
'maxSize' => '1024k',
'mimeTypes' => ['image/jpeg', 'image/png'],
]),
],
New Symfony2 User here. I have 2 entities that are related, one to many that is unidirectional. I'm doing it as ManyToMany as the doctrine documentation suggests, Article(one) and Tags(many). I'd like to have checkboxes show up that show the tag names on the article.new page and the article.edit page. On form submission the id of the tag entity is stored in the article_tags side table that the entity generator created for me.
Posting only relevant code.
Tag Entity AppBundle/Entity/Tag.php
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=20)
*/
public $name;
Article Entity AppBundle/Entity/Article.php
/**
* #ORM\ManyToMany(targetEntity="Tag")
* #ORM\JoinTable(
* name="article_tags",
* joinColumns={#ORM\JoinColumn(name="article_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="tag_id", referencedColumnName="id", unique=true)}
* )
*/
protected $tags;
/**
* Add tag
*
* #param \AppBundle\Entity\Tag $tag
*
* #return Article
*/
public function addTag(\AppBundle\Entity\Tag $tag)
{
$this->tags[] = $tag;
return $this;
}
/**
* Remove tag
*
* #param \AppBundle\Entity\Tag $tag
*/
public function removeTag(\AppBundle\Entity\Tag $tag)
{
$this->tags->removeElement($tag);
}
/**
* Get tags
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
Article Form Type AppBundle/Form/ArticleType
$builder->add('title')
->add('body')
->add('author')
->add('tags', 'entity', array(
'class' => 'AppBundle\Entity\Tag',
'property' => 'name',
'expanded' => 'true', ));
ArticleController AppBundle/Controller/ArticleController.php
* #Template()
*/
public function newAction()
{
$entity = new Article();
$tags = new Tag();
$entity->addTag($tags);
$form = $this->createCreateForm($entity);
return array('entity' => $entity,'form' => $form->createView(), );
}
As of now the error I receive is...
Entities passed to the choice field must be managed. Maybe persist
them in the entity manager?
I'm not entirely sure I'm on the right track. I just want to attach tags to articles!
Thanks
In the controller, you create a blank Tag and add it to the new Article before creating the form. That doesn't make sense to me, and I suspect that's where the error is coming from.
If there are any tags in the database, Symfony will automatically get them and display them with a checkbox in the form. If the user checks a checkbox, this Tag will be added to the Article.
Just delete these two lines and you should be fine:
$tags = new Tag();
$entity->addTag($tags);
My Product entity has an "adder" method (explained here) to add entities of type ProductVariant:
/**
* #ORM\OneToMany(targetEntity="ProductVariant", mappedBy="product",
* cascade={"persist"})
* #Assert\Valid()
*
* #var ArrayCollection
*/
protected $variants;
/**
* #param ProductVariant $variant
* #return $this
*/
public function addVariant(ProductVariant $variant)
{
if (!$this->variants->contains($variant)) {
$this->variants->add($variant->setProduct($this));
}
return $this;
}
ProductType form type is adding a collection of ProductVariantType:
$builder->add('variants', 'collection', array(
'type' => new ProductVariantType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false, // ensure addVariant and removeVariant calls
));
Problem #1 (validation): when I add a new variant (using add button/JavaScript) the new embedded form isn't validated. If I left blank all fields (i.e some of them are required) validation passes. Validation occurs only filling some fields.
Problem #2 (addVariant call): addVariant is called with NULL, probably because the problem #1.
Some ProductVariant fields:
/**
* #ORM\Column(unique=true)
* #Assert\NotBlank()
*
* #var string
*/
protected $code;
/**
* #ORM\Column(type="integer")
* #Assert\NotBlank()
* #Assert\Range(min=0, max=2147483647)
*
* #var string
*/
protected $quantity;
I'm using the following JavaScript to add/remove variants:
<script>
$(function () {
$('.btn-add').click(function(e) {
e.preventDefault();
var holder = $('#' + $(this).attr('data-target')),
proto = holder.attr('data-prototype')
.replace(/__name__/g, holder.children().length - 1);
holder.append(proto);
});
});
</script>
And of course, the prototype:
<div class="tab-pane" id="variants"
data-prototype="{{ form_widget(form.variants.vars.prototype)|e }}">
<div class="panel">
<div class="panel-body">
Add variant
</div>
</div>
{% for variant in form.variants %}
{{ form_widget(variant) }}
{% endfor %}
</div>
Finally the (simple) controller code:
public function editAction(Request $request, Product $product)
{
$form = $this->createForm('product', $product)
->handleRequest($request);
if ($form->isValid()) {
$this->getDoctrine()->getManager()
->flush();
return $this->redirect(
$this->generateUrl(
'me_test_product_edit',
array('id' => $product->getId())
)
);
}
return array('form' => $form->createView());
}
In order to ensure nested validation one need's to add the Valid constraint to your property holding the collection. (which you already did)
Further please note:
By default the error_bubbling option is enabled for the collection
Field Type, which passes the errors to the parent form. If you want to
attach the errors to the locations where they actually occur you have
to set error_bubbling to false
(documentation)
Please don't forget to clear your cache (symfony's and your opcode cache additionally) after introducing new annotations.
This is related to my earlier question about embedded forms. As advised, I switched to a twig template and now everything is displaying as expected and the link to add a new empty form is working correctly. The problem is that when I try to save a new record, it doesn't work (although edits to existing entities are saved).
There are several points where I may have gone wrong, so I'm going to ask questions as I go along.
Here's some background:
A study can have many participants (i.e. a Study entity has a OneToMany relationship with the entity Participant). In the database, each Participant record has the foreign key link from the column "study" to the "study_id" column of a record in the Study table, making it the owning side of the relation. The annotation in the classes should reflect this relationship.
Study class:
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* CRUK\MyBundle\Entity\Study
*
* #ORM\Table(name="study")
* #ORM\Entity
*/
class Study
{
/**
* #var integer $id
*
* #ORM\Column(name="study_id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string $studyName
*
* #ORM\Column(name="study_name", type="string", length=50, nullable=false)
*/
private $studyName;
/*
* #ORM\OneToMany(targetEntity="Participant", mappedBy="study", cascade={"persist"})
*
* #var ArrayCollection $participants
*/
protected $participants;
public function __construct()
{
$this->participants = new ArrayCollection();
}
public function setParticipants(ArrayCollection $participants)
{
foreach($participants as $participant) {
$participant->setStudy($this);
}
$this->participants = $participants;
}
/**
* #return ArrayCollection A Doctrine ArrayCollection
*/
public function getParticipants()
{
return $this->participants;
}
}
My Participant class:
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* CRUK\SampleTrackingBundle\Entity\Participant
*
* #ORM\Table(name="participant")
* #ORM\Entity
*/
class Participant
{
/**
* #var integer $id
*
* #ORM\Column(name="participant_id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
...
/**
* #var study
*
* #ORM\ManyToOne(targetEntity="Study", inversedBy="participants")
* #ORM\JoinColumn(name="study", referencedColumnName="study_id")
*
*/
private $study;
//setters and getters...
}
First of all, are these annotations correct? (I'm pretty sure I got the whole owning/inverse many-to-one/one-to many relationship straight in my head, but I could be mistaken)
My controller:
Class StudyController extends Controller
{
...
public function addParticipantsAction($id)
{
$em = $this->getDoctrine()->getEntityManager();
$entity = $em->getRepository('MyBundle:Study')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find Study id='.$id);
}
$participantArray = $em->getRepository('MyBundle:Participant')->findByStudy($id);
//this is supposed to return a Doctrine ArrayCollection, but for some reason, returns an array
// This needs to be converted to an ArrayCollection
$participants = new ArrayCollection();
foreach ($participantArray as $participant) {
$participants->add($participant);
}
$entity->setParticipants($participants);
$form = $this->createForm(new StudyType(), $entity);
$request = $this->getRequest();
if ('POST' === $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$em->persist($entity);
$em->flush();
}
}
return $this->render('MyBundle:Study:addParticipants.html.twig', array(
'form' => $form->createView(),
'entity' => $entity
));
}
...
}
At this point I have to ask why it is neccessary to explicitly fetch the collection of participants and use it to set the collection on the study entity? Before I added that code, $entity->getParticipants() would return null (even when I know there were several participants with the foreign key set for the study). I have two other tables in a many-to-many relationship where the collections seem to come up automatically just by having the correct annotations in the entity classes. Is this a difference between a many-to-many mapping vs. a many-to-one, or have I messed up the annotation somehow?
I'm not sure if the rest of the code will help, but here's some more:
My study form class:
class StudyType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('studyName', null, array('label'=> 'Study Name:'))
->add('participants', 'collection', array(
'type'=> new ParticipantType(),
'allow_add'=>true,
'by_reference'=>false
));
}
public function getName()
{
return 'study';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'MyBundle\Entity\Study',
);
}
}
My embedded form class:
class ParticipantType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('participantId','text', array('label'=>'Participant ID'))
));
}
public function getName()
{
return 'participant';
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'MyBundle\Entity\Participant',
);
}
}
My template:
{% extends 'MyBundle::base.html.twig' %}
{% block body%}
<form action="{{ path('study_addparticipants', { 'id': entity.id }) }}" method="POST" {{ form_enctype(form) }}>
<!-- renders global errors -->
{{ form_errors(form) }}
<h2>Study</h2>
{{ form_label(form.studyName) }}
{{ form_errors(form.studyName) }}
{{ form_widget(form.studyName) }}
<h3>Participants in this study</h3>
<ul class="participants" data-prototype="{{ form_widget(form.participants.get('prototype')) | e }}">
{% for participant in form.participants %}
<li>{{form_row(participant) }}</li>
{% endfor %}
</ul>
{{ form_rest(form) }}
<button type="submit">Save Changes</button>
</form>
{% endblock%}
{% block javascripts %}
{# parent block includes jQuery #}
{{ parent() }}
<script type='text/javascript'>
jQuery(document).ready(function() {
// keep track of how many participant fields have been rendered
var collectionHolder = $('ul.participants');
var $addLink = $('Add new Participant');
var $newLinkLi = $('<li></li>'). append($addLink);
collectionHolder.append($newLinkLi);
$addLink.on('click', function(e) {
e.preventDefault();
addParticipantForm(collectionHolder, $newLinkLi);
});
});
function addParticipantForm(collectionHolder, $newLinkLi) {
// Get the data-prototype we explained earlier
var prototype = collectionHolder.attr('data-prototype');
// Replace '$$name$$' in the prototype's HTML to
// instead be a number based on the current collection's length.
var newForm = prototype.replace(/\$\$name\$\$/g, collectionHolder.children().length);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = $('<li></li>').append(newForm);
$newLinkLi.before($newFormLi);
}
</script>
{% endblock %}
So, the form displays correctly and when I click the "Add new participant" link, an empty participant form is appended. Changes to the Study and exisitng participant records are saved. There are no errors, but any new participants are not saved.
I have read many similar questions to this one and as far as I know, incorporated everything that should make this work. I've obviously missed something, so would appreciate any suggestions on how to put this right.
Many Thanks.
Thanks to #Luke for the advice. I have solved the problem by looping through the pariticpants collection of my study object and saving each one individually in my controller. The new contoller code:
...
if ('POST' === $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$em->persist($entity);
foreach($entity->getParticipants() as $participant) {
$em->persist($participant);
}
// flush once to commit all entities
$em->flush();
}
}
...