I have a form with multiple rows created from one table (no relationships to other tables). When I save the form, every change I've made is saved, but I do have an additional empty row in the database.
See below for (hopefully) all neccessary informations.
PropertyAdditionCostFrequency.php
/**
* #ORM\Entity
* #ORM\Table(name="property_addition_cost_frequency")
*/
class PropertyAdditionCostFrequency
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", nullable=true)
*/
private $label = '';
private $costFrequencyCollection;
public function __construct()
{
$this->costFrequencyCollection = new ArrayCollection();
}
// + the getters and setters
}
PropertyAdditionCostFrequencyLabelType.php
class PropertyAdditionCostFrequencyLabelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label' )
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => PropertyAdditionCostFrequency::class,
));
}
}
PropertyAdditionCostFrequencyForm.php
class PropertyAdditionCostFrequencyForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add( 'cost_frequency_collection', CollectionType::class, array(
'entry_type' => PropertyAdditionCostFrequencyLabelType::class,
'label' => ''
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => PropertyAdditionCostFrequency::class
]);
}
}
AdminCoreDataController.php
public function showCoreDataListAction( Request $request )
{
$PropertyAdditionCostFrequency = new PropertyAdditionCostFrequency();
$repository = $this->getDoctrine()->getRepository('AppBundle:PropertyAdditionCostFrequency');
$cost_frequency = $repository->findAll();
foreach ($cost_frequency as $k => $v) {
$PropertyAdditionCostFrequency->getCostFrequencyCollection()->add($v);
}
$form = $this->createForm( PropertyAdditionCostFrequencyForm::class, $PropertyAdditionCostFrequency);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$propertyAdditionCostFrequency_form = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($propertyAdditionCostFrequency_form);
$em->flush();
$this->addFlash('success', 'successfully changed the data');
return $this->redirectToRoute('admin_core_data');
}
return $this->render('logged_in/content/admin/core_data/core_data.html.twig', [
'propertyCostFrequencyForm' => $form->createView()
]);
}
core_data.html.twig
{{ form_start(propertyCostFrequencyForm) }}
<div class="row" id="cost_frequency_box">
{% for single_frequency in propertyCostFrequencyForm.cost_frequency_collection %}
<div class="row js-single-cost-frequency-box">
<div class="col-sm-1 js-delete-cost-frequency">
<a href="/admin/property/delete/5">
<i class="fa fa-trash"></i>
</a>
</div>
<div class="col-sm-4">
{{ form_widget(single_frequency.label) }}
</div>
</div>
{% endfor %}
</div>
<div class="row">
<div class="col-sm-3">
<button class="col-sm-12 btn btn-success" type="submit">Speichern</button>
</div>
</div>
{{ form_end(propertyCostFrequencyForm) }}
a dump() of $propertyAdditionCostFrequency_form right before '$em->persist($propertyAdditionCostFrequency_form)'
PropertyAdditionCostFrequency {#394 ▼
-id: null
-label: ""
-costFrequencyCollection: ArrayCollection {#395 ▼
-elements: array:4 [▼
0 => PropertyAdditionCostFrequency {#422 ▼
-id: 1
-label: "1"
-costFrequencyCollection: null
}
1 => PropertyAdditionCostFrequency {#424 ▼
-id: 2
-label: "2"
-costFrequencyCollection: null
}
2 => PropertyAdditionCostFrequency {#425 ▼
-id: 47
-label: "3"
-costFrequencyCollection: null
}
3 => PropertyAdditionCostFrequency {#426 ▼
-id: 38
-label: "4"
-costFrequencyCollection: null
}
]
}
}
The reason why this is happening is evident from the dump you have posted: the top level PropertyAdditionCostFrequency has the id set to null, so of course it is going to create a new row for that, so if you were simply wondering why it creates a new row thats your reason :).
On the other hand I can't help but think, you have misunderstood how symfony forms / doctrine works, UNLESS having multiple child PropertyAdditionCostFrequency added to one parent PropertyAdditionCostFrequency is the desired effect.
IF it is not the desired effect and you simply want to create a form which contains a form for every entry of PropertyAdditionCostFrequency in the database(which I would not recommend, because once the DB gets big, it won't be very efficient), you can just use createFormBuilder method inside the controller, add an arbitrary CollectionType field (i.e. $builder->add('frequencies', CollectionType::class), then just create an array and assign the result of findAll() to a key(key name should correspond to the field name, in this case, 'frequencies') and finally set that as the data for the form you have just created (use $builder->getForm() method to get the form, since thats what you want in the controller and you should use the set data method on that :)) (P.S. these are just rough guidelines, I'm not sure, that I haven't missed anything)
Related
I am trying to create a DateTimePickerType to easily add a Bootstrap dateTimePicker to a field, simply by using the type "date_time_picker" in the Form Builder.
Unfortunately I am running into some problems. Specifically, Symfony is giving me this error :
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).children[startDate] = Object(DateTime) - 2016-10-16T10:45:00+0200
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Unable to reverse value for property path "startDate": Expected a string.
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Expected a string.
So apparently my form is sending a DateTime object to my Controller, who has trouble converting it to String. In the database, the field is of type datetime.
Here is my DateTimePickerType :
class DateTimePickerType extends AbstractType
{
/**
* #return string
*/
public function getParent()
{
return 'datetime';
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'empty_data' => new \DateTime(),
'widget' => "single_text",
'attr' => array(
'class' => 'addInput col-xs-12 dateSize noPadding noOutline dateTimePicker',
),
'format' => 'YYYY-MM-DD HH:mm',
'date_format'=>"dd/MM/yyyy hh:mm",
'html5' => false,
)
);
}
/**
* #param FormView $view
* #param FormInterface $form
* #param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars = array_replace($view->vars, array(
'empty_data' => $options['empty_data'],
'widget' => $options['widget'],
'format' => $options['date_format'],
));
}
/**
* #return string
*/
public function getName()
{
return 'date_time_picker';
}
}
I have tried many different options for the resolver. If I remove the "new \DateTime()" in "empty_data", my form now sends a null value, and I get a nice SQL error trying to insert null into the database.
Snippet of form_template.html.twig :
{% block date_time_picker_widget %}
<div class='input-group date' id='date-time-picker-{{ id }}'>
<input type="text" class="form-control" />
<span class="input-group-addon"><i class="fa fa-clock-o"></i></span>
</div>
{% include 'VMSFormTypeBundle:Template:date_time_picker_script.html.twig' %} {# link to the direct js script to make it datetimepicker #}
{% endblock %}
In date_time_picker_script.html.twig :
{% autoescape false %}
<script type="text/javascript">
$(function () {
$('#date-time-picker-{{ id }}').datetimepicker();
});
</script>
{% endautoescape %}
Snippet of my Form Builder :
$builder
->add('startDate','date_time_picker')
Thanks in advance
EDIT : Just noticed the datetime that is sent by the form ignores what I select, and instead sends the current datetime (current day, hour and minute).
Apparently this is because there's a need to convert the time format between PHP and JS. I fixed the problem by using only a DateType (time wasn't that necessary).
This might be useful for those who still need a DateTime, tho I haven't tested it : https://github.com/stephanecollot/DatetimepickerBundle
I have my entity Article and one single table inheritance like this :
/**
* Article
*
* #ORM\Table(name="article")
* #ORM\Entity(repositoryClass="PM\PlatformBundle\Repository\ArticleRepository")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="media", type="string")
* #ORM\DiscriminatorMap({"article" = "Article", "movie" = "Movie", "image" = "Image", "text" = "Text"})
*/
class Article
{
protected $id;
protected $title;
protected $description;
protected $author;
//other attributes and setters getters
}
class Image extends Article
{
private $path;
//getter setter
}
class Movie extends Article
{
private $url;
//getter setter
}
So my article's object type is either Image or movie or text only. Ok now I would like build a form wherein users can post a new article : in this form, the user has to choice between tree type (3 radios button) : image OR movie OR text only and of course the other fields : title and description. How I can do that ? Because with the command
php bin/console doctrine:generate:form myBundle:Article
The form rendered is :
class ArticleType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class)
->add('description', TextareaType::class)
->add('save', SubmitType::class);
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'PM\PlatformBundle\Entity\Article'
));
}
}
I don't know the way to implement my STI relation in this form. Because I have not field in my Article entity/object for the type (only in my table).
I have to add a Custom ChoiceType() field but it require a attribute.
When I try to add this in the form :
->add('path', SearchType::class)
->add('url', UrlType::class)
I got this error :
Neither the property "path" nor one of the methods "getPath()", "path()", "isPath()", "hasPath()", "__get()" exist and have public access in class "PM\PlatformBundle\Entity\Article".
Because I have create an instance of Article, not an instance of Image or Movie. Initially I created a STI thinking a new instance of Article would allow me also to define the "type" of article. But not ? Right ?
You will have to make three forms (one for an Article, one for a Movie and one for an Image). Then, in your controller, you have to options to deal with them:
Either you use one action to handle the three forms (you can check wich one is submitted by using $form->isSubmitted())
You create one action by form, and set the form action URL for each form to the correct controller.
Finally, in your template, you encapsulate your forms in a div, and use the example in my previous post.
{% extends "CoreBundle::layout.html.twig" %}
{% block title %}{{ parent() }}{% endblock %}
{% block btn_scrollspy %}
{% endblock %}
{% block bundle_body %}
<div class="well">
<div class="selector">
<input type="radio" name="form-selector" value="article-form"> Article
<input type="radio" name="form-selector" value="movie-form"> Movie
<input type="radio" name="form-selector" value="image-form"> Image
</div>
<div class="form article-form" style="display: none;">
{{ form(articleForm) }}
</div>
<div class="form movie-form" style="display: none;">
{{ form(movieForm) }}
</div>
<div class="form image-form" style="display: none;">
{{ form(imageForm) }}
</div>
</div>
{% endblock %}
I agree with dragoste in the comments: you can't expect the form to deduce by himself the type of class you want to instantiate based on a value.
Roughly, Image and Movie are the same type as Article, but an Article is not an Image and/or a Movie.
You will have to check that manually. You can do that server side like explained in the comments, with a field using mapped: false to determine the type of entity you need to instantiate, or client side with javascript by using three forms (one for a movie, one for an article, one for an image) and by displaying the correct one based on your radio button.
Edit: How to display the correct form in JS?
I created a JSFiddle to show you how you can do this using jQuery : https://jsfiddle.net/61gc6v16/
With jQuery documentation, you should be able to quickly understand what this sample do, and to adapt it to your needs. :)
#Boulzy I did this and it works:
class ArticleController extends Controller
{
public function addAction(Request $request)
{
$form1 = $this->get('form.factory')->create(TextOnlyType::class);
$form2 = $this->get('form.factory')->create(ImageType::class);
$form3 = $this->get('form.factory')->create(MovieType::class);
return $this->render('PMPlatformBundle:Article:add.html.twig', array(
'ArticleTextform' => $form1->createView(),
'ArticleImageform' => $form2->createView(),
'ArticleMovieform' => $form3->createView(),
));
}
public function addArticleTextAction(Request $request)
{
$ArticleText = new TextOnly;
$form = $this->get('form.factory')->create(TextOnlyType::class, $ArticleText);
if ($request->isMethod('POST') && $form->handleRequest($request)->isValid()) {
$ArticleText->setAuthor(5);
$ArticleText->setLikes(0);
$em = $this->getDoctrine()->getManager();
$em->persist($ArticleText);
$em->flush();
$request->getSession()->getFlashBag()->add('notice', 'Annonce bien enregistrée.');
$listComments = $ArticleText->getComments();
return $this->render('PMPlatformBundle:Article:view.html.twig', array(
'article' => $ArticleText,
'listComments' => $listComments
));
}
return $this->render('PMPlatformBundle:Article:add.html.twig', array(
'form' => $form->createView(),
));
}
public function addArticleImageAction(Request $request)
{
//the same system as TextOnly
}
public function addArticleMovieAction(Request $request)
{
//the same system as TextOnly
}
}
(I override action directly in my template. With POST method.)
addAction is my contoller which is called by route for display the view of three forms. As you can see this code works as long as there is no error on submitted form. Because, in this case (when error occured when a form is submitted) each controller needs to return the initial view with the 3 forms. How I can do that ?
There is a form to create Chain entity.
class ChainType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, array('label' => 'Company name'))
->add('logoImageURL', TextType::class, array('label' => 'Company logo'));
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'CoreBundle\Entity\Chain'
));
}
}
Here is a newAction to create form and save entity
/**
* Creates a new Chain entity.
*
* #Route("/new", name="chain_new")
*/
public function newAction(Request $request)
{
$chain = new Chain();
$form = $this->createForm(ChainType::class, $chain);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($chain);
$em->flush();
return $this->redirectToRoute('chain_show');
}
return $this->render(
'AdminBundle:ChainPanel:new.html.twig',
array('form' => $form->createView())
);
}
Here is a button to create form
<button type="button" class="btn btn-primary">
<a href="{{ path('chain_new') }}">
Add Chain
</a>
</button>
Problem
When I click on 'Add chain' button the form is not created and I m just redirected to 'chain_show' route. What is wrong with my code?
UPDATE - MY SOLUTION
In controller I put newAction before showAction. This fixed problem. However I couldn't find explanation
I'm not sure what are you trying to do, but if you're trying to create a page with ChainType form you should also create a template for that page, like this:
.....
{% block YOUR_BLOCK %}
{{ form(form) }}
{% endblock %}
......
and, also, you don't have to wrap a tag with button tag, just give class="btn btn-primary" to that a tag.
Your issue is that you're using the variable which holds the new Chain object. Please see my example below and this should fix your problem...
public function newAction(Request $request)
{
$chain = new Chain();
$form = $this->createForm(ChainType::class, $chain);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$chain = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($chain);
$em->flush();
return $this->redirectToRoute('chain_show');
}
return $this->render(
'AdminBundle:ChainPanel:new.html.twig',
array('form' => $form->createView())
);
}
You're also creating the submit button incorrectly. To test it's all working properly, just use (in your .twig.html file):
{{ form(form) }}
Interesting though, I guess the HTML is wrong for a link. You shouldn't have button wrapped to an anchor tag. Some browsers doesn't support it. instead use as below :
<a href="{{ path('chain_new') }}" class="btn btn-primary">
Add Chain
</a>
If this doesn't solve your issue, Do you see a new entity being created when you click on the link?
In controller I put newAction before showAction. This fixed problem. However I couldn't find explanation
Situation
I have an entity Item that can has a collection of Items (preconditions) with the same class Item. I used this documentation for making a form with the collection type. I want to make almost the same like in the documentation. But my situation is:
My collecting items are self-referenced to the main item
I want to add already existing items from an existing list, not creating new ones
With prototyping, i'm adding a new choosed collection item with JS with the same HTML structure like the default repopulation from symfony's form does. But naturally with the replaced choosed relation ID.
Persisting the relations is working, but only with a manual hack, and with missing datas into the $form object.
Problems
When i add an relation, with js prototyping and post the data, a new entity with the correct Item class is added in the ArrayCollection. This new entity returns with the posted ID as key as ArrayCollection item, but an empty entity itself as value. (New one is with key 1 below)
#collection: ArrayCollection {#829 ▼
-elements: array:2 [▼
5 => Item {#834 ▼
#id: 5
-title: "foo"
+preconditions: PersistentCollection {#836 ▶}
}
1 => Item {#890 ▼
#id: null
-title: null
+preconditions: ArrayCollection {#886 ▶}
}
]
}
Problem 1: This results on persisting that an unknown new entity want to be inserted (Because entity with ID null doesn't exists in the DB), and then with invalid null datas. It must not be inserted because its actually already existing.
Problem 2: Before persist i must modify the ArrayCollection property by hand, invalid entities i have to refill. So the entries are valid for relation persisting.
Problem 3: When i modify the ArrayCollection this way, i need this datas to be accessable also in twig. And an update into $form will result an Exception that i can’t modify the $form after it was submitted. Then i saw here the suggestion of an event listener, but even with an event listener i cant’ update the $form because either $form is not prepared or is too late to update.
Questions
Is following the right way to perform my goal, or does exist a better variant?
How can the new entities in the ArrayCollection be already be filled correctly after a post?
Code
Controller:
public function newesiAction($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
// For repopulating an existing entity
$item = 0 < $id ? $em->getRepository('AppBundle:Item')->find($id) : new Item();
$form = $this->createForm(new ItemType($this->container), $item);
$form->add('submit', 'submit', array('label' => 'speichern'));
$form->handleRequest($request);
/* After handleRequest, the posted (also new) precondition entities are given. But not as expected. So we update the array collection.
* Not as expected meant: New relation entities becomes new entities with NULL as id. So entities with NULL as id wantet to be INSERTED, what is not our goal.
*/
foreach($item->preconditions as $precondition_entity_id => $precondition_entity) {
$precondition_entity_inner_id = $precondition_entity->getId();
if(null === $precondition_entity_inner_id) {
$item->preconditions[$precondition_entity_id] = $em->getRepository('AppBundle:Item')->find($precondition_entity_id);
}
}
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($item);
$em->flush();
}
return $this->render('AppBundle:Item:edit.html.twig', array(
'form' => $form->createView(),
));
}
ItemType:
<?php
// src/AppBundle/Form/Type/ItemType.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
class ItemType extends AbstractType
{
public function __construct($container) {
$this->container = $container;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title');
$builder->add('preconditions', 'collection', array(
'type' => new PreconditionType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
));
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
// $em = $this->container->get('doctrine')->getManager();
// $form = $event->getForm();
// // Replace the new related entity that have NULL in its ID, with an fully loaded entity
// $entity = $event->getData();
// foreach($entity->preconditions as $precondition_entity_id_posted => $possible_empty_entity) {
// $precondition_entity_id_loaded = $possible_empty_entity->getId();
// if(null === $precondition_entity_id_loaded) {
// $entity->preconditions[$precondition_entity_id_posted] = $em->getRepository('AppBundle:Item')->find($precondition_entity_id_posted);
// }
// }
// $form->get('preconditions')->setData($entity->preconditions);
});
$builder->add('available_items', 'entity', array(
'class' => 'AppBundle\Entity\Item',
'multiple' => true,
'expanded' => false,
'mapped' => false
));
$builder->add('add_item_precondition', 'button', array(
'attr' => array(
'onclick' => "addPreconditionForm('" . $this->getName() . "_available_items')"
)
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Item',
));
}
public function getName()
{
return 'item';
}
}
?>
Precondition type:
<?php
// src/AppBundle/Form/Type/PreconditionType.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PreconditionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('relation', 'hidden', array(
'mapped' => false,
'required' => false
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Item'
));
}
public function getName()
{
return 'precondition';
}
}
?>
I had the possibility to discuss this with an employee of sensiolabs. Its officially not possible to automate this with the built-in tools, you must loop the collection entries and re-get the incomplete entities.
So the answers to my questions:
Yes, it is the correct way go get the missing entities
No, its not possible to get those automatically
Currently in a bit of a pickle. I have 2 entities: Main and Sub, it is a OneToMany relationship (one main can have many subs) I made a collection of form embedded together. I first had a search form where a user can search a Main by one of its attributes, then it sends them to a page where there is a form with the searched Main, its attributes listed on the form but is disabled so users cannot edit them, and the enabled fields are from the embedded Sub form which users need to enter in for submission.
1) User searches Main by its attribute, i.e. "pono" (PO number)
2) User is redirected to a page that shows the row that he/she searched for with the listed (pono), (cano), (bano) - it is disabled so it cannot be edited
3) Enabled fields are empty and users must enter the information that would be submitted into the Sub entity.
In my Main entity
/**
* #var Sub
* #ORM\OneToMany(targetEntity="Sub", mappedBy="mainId")
*/
protected $sub;
public function __construct() {
$this->sub = new ArrayCollection();
}
And my Sub entity:
/**
* #var integer
*
* #ORM\Column(name="main_id", type="integer")
*/
protected $mainId;
/**
* #ORM\ManyToOne(targetEntity="Main", inversedBy="sub", cascade={"persist"})
* #ORM\JoinColumn(name="main_id", referencedColumnName="id")
*/
protected $main;
In my Main form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('pono', 'text', array(
'label' => 'PO: ',
'disabled' => true
))
->add('cano','text', array(
'label' => 'CA: ',
'disabled' => true
))
->add('bano', 'text', array(
'label' => 'BA: ',
'disabled' => true
))
->add('sub', 'collection', array('type' => new SubType()));
}
In my Sub form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('qty','integer', array(
'label' => 'Qty: '
))
->add('location','text', array(
'label' => 'Location: '
))
->add('priority','text', array(
'label' => 'Priority: '
));
}
So on my controller
public function submitItemAction(Request $request, $pono) {
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ItemBundle:Main')
->findOneByPono($pono);
$cano = $entity->getCano();
$bano = $entity->getBano();
$main = new Main();
$main->setPono($pono);
$main->setCano($cano);
$main->setBano($bano);
$sub = new Sub();
$sub->setMain($main);
$main->getSub()->add($sub);
$form = $this->createForm(new MainType(), $main, array(
'method' => 'POST'
))
->add('submit','submit');
$form->handleRequest($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($sub);
$em->flush();
return $this->redirect($this->generateUrl('success'));
}
Now when this is submitted, it's submitting BOTH Main and Sub. It's giving me a duplicate Main and the newly added Sub. I know it is what it's supposed to do, but I need it to only submit the Sub. I tried retrieving the id from Main with a $mainid = $entity->getId(); and putting it into $sub->setMainId($mainid) and I keep getting the error message that main_id cannot be null.
Any takers?
Edit: Twig template:
{{ form_start(form) }}
{{ form_label(form.pono) }}
{{ form_widget(form.pono) }} <br>
{{ form_label(form.cano) }}
{{ form_widget(form.cano) }}<br>
{{ form_label(form.bano) }}
{{ form_widget(form.bano) }} <br>
{% for sub in form.sub %}
{{ form_label(sub.qty) }}
{{ form_widget(sub.qty) }} <br>
{{ form_label(sub.location) }}
{{ form_widget(sub.location) }} <br>
{{ form_label(sub.priority) }}
{{ form_widget(sub.priority) }}<br>
{% endfor %}
{{ form_widget(form.submit) }}
{{ form_end(form) }}
After looking at your code, I think it is possible to make it work, there are a few things you will need to fix. I will edit this answer in a few steps.. Also make a backup/commit of your code before you start changing it.
1) In your MAIN entity (add cascade persist)
/**
* #var Sub
* #ORM\OneToMany(targetEntity="Sub", mappedBy="main", cascade={"persist"})
*/
protected $sub;
2) SUB entity:
Remove protected $mainId; and it's annotation.
Remove cascade={"persist"} from ManyToOne
3) Look at your controller action.
$sub = new Sub();
$sub->setMain($main);
$main->getSub()->add($sub);
Pay attention to the setMain() method. You do not want to do this in controller, but automatically in entity. And also you should add to collection manually, but make a method for it. So you will only have this:
$sub = new Sub();
$main->addSub($sub);
4) In MAIN entity add (you might need to import Sub):
public function addSub(Sub $sub) {
$sub->setMain($this);
$this->sub->add($sub);
return $this;
}
You should also add other methods like removeSub(), removeSub(), getSub(). getSub() returns collection, while the first two will return $this.
5) Controller
Do not persist Sub, but Main. (Doctrine will cascade persistance to Sub)
$em->persist($main);
6) You will need to add 'by_reference' option to sub collection inside you Main Form Type.
->add('sub', 'collection', array('type' => new SubType(), 'by_reference' => false));
This will call the actual addSub() method and not call the add method directly.
7) I do not know why you make a new Main entity below.
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ItemBundle:Main')
->findOneByPono($pono);
$cano = $entity->getCano();
$bano = $entity->getBano();
$main = new Main();
$main->setPono($pono);
$main->setCano($cano);
$main->setBano($bano);
Try to change to:
$em = $this->getDoctrine()->getManager();
$main = $em->getRepository('ItemBundle:Main')
->findOneByPono($pono);
You probably should define Pono as unique.