Symfony form listener - php

I have form with choice field customer type entity and I want when select some customer in another place(input or label, whatever) change data, I use example from cook book but not work
my form
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('customer', 'entity', array(
'class' => Customer::class,
'property' => 'name',
'empty_value' => 'Choice Customer',
'query_builder' => function ($repository) {
/** #var CustomerRepository $repository */
return $repository->getAllQuery();
},
'required' => true
));
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
/** #var Customer $customer */
$customer = $data->getCustomer(); //always have null, why ?
$positions = null === $customer ? '-' : 'its_work';
$form
->add('invoicing_address', 'text', [
'mapped' => false,
'data' => $positions
]);
}
);
/**
* #return string
*/
public function getName()
{
return 'out_bound_invoice';
}
my js ?
var $sport = $('#out_bound_invoice_customer');
// When sport gets selected ...
$sport.change(function() {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected sport value.
var data = {};
data[$sport.attr('name')] = $sport.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) { // in html always have empty, like ''
// Replace current position field ...
$('#out_bound_invoice_address').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#out_bound_invoice_address')
);
// Position field now displays the appropriate positions.
}
});
});
when I change customer in js in data have name form and customer name key and data id and then in action in request I see this data
when using event form FormEvents::POST_SUBMIT in $customer I have customer entity
$builder->get('customer')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$customer = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $customer);
}
);
and I want understand why needed PRE_SET_DATA, POST_SET_DATA for this event I have null
my twig
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_label(form.customer, 'customer')}}
{{ form_widget(form.customer, {'attr': {'class': 'select2'}}) }}
{{ form_label(form.invoicing_address)}}
{{ form_widget(form.invoicing_address) }}
{{ form_end(form) }}
why all time I have null in $customer in form listener and how change another field (label or default data, whatever in another field)?

Related

Symfony, how to use form event to validate dynamic client-side form

I'm using the select2 plugin with ajax to have a dynamic field on my form, but when i submit the it return me an error "This value is not valid", which is normal cause i use the ChoiceType with an empty array() in the choices options on creation. According to this part of the symfony doc, the form event is my savior, so trying to use it but it look like something wrong with my code and can't really see what.
So My Question Is :
HOW to pass the choices possibility to the field, for the form to be valid.
My form Type
class ArticleType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//My other field
//My functions to add the field with the possible choices
$formModifier = function (FormInterface $form, $imageValue) use ($options) {
if ($imageValue !== null) {
$listImages = $this->getChoiceValue($imageValue, $options);
if (!$listImages) {
$form->get('image')->addError(new FormError(
'Nous n\'avons pas pu trouver l\'image, veuiller choisir une autre'
));
}
} else {
$listImages = array();
}
//die(var_dump($listImages)); //Array of Image
$form->add('image', ChoiceType::class, array(
'attr' => array(
'id' => 'image'),
'expanded' => false,
'multiple' => false,
'choices' => $listImages));
};
$formModifierSubmit = function (FormInterface $form, $imageValue) use ($options) {
if ($imageValue !== null) {
$listImages = $this->getChoiceValue($imageValue, $options);
if (!$listImages) {
$form->get('image')->addError(new FormError(
'Nous n\'avons pas pu trouver l\'image, veuiller choisir une autre'
));
}
} else {
$form->get('image')->addError(new FormError(
'Veuillez choisir une image s.v.p.'
));
}
//die(var_dump($listImages)); //Array of Image object
$config = $form->get('image')->getConfig();
$opts = $config->getOptions();
$chcs = array('choices' => $listImages);
//die(var_dump($chcs)); //output an array with a 'choices' keys with array value
array_replace($opts, $chcs); //not work
//array_merge($opts, $chcs); //not work
//die(var_dump($opts)); //replacements/merge are not made
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be the entity Article
$data = $event->getData();
$formModifier($event->getForm(), $data->getImage());
}
);
//$builder->get('image')->addEventListener( //give error cause the field image don't exist
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($formModifierSubmit) {
$imageVal = $event->getData();
//die(var_dump($imageVal)); //return all the submitted data field in an array
//But when change this event to Submit it return the Article model populated by the submitted data, EXCEPT the image field which have null as value
$formModifierSubmit($event->getForm(), $imageVal['image']);
}
);
}
public function getChoiceValue($imageValue, $options)
{
$listImages = $options['em']->getRepository('AlmotivAppBundle:Image')->findBy(array(
'id' => $imageValue
));
return $listImages; //array of Image object
}
[...]
}
For Info
My image field is not depending on any other field like the doc example, so i need to populate the choices options on PRE_SUBMIT event to give the possible choice.
And also image have a ManyToOne relation in my Article entity
class Article implements HighlightableModelInterface
{
//some properties
/**
* #ORM\ManyToOne(targetEntity="Image\Entity\Path", cascade={"persist"})
* #Assert\Valid()
*/
private $image;
}
If i'm in the bad way let me know cause i'm out of idea now, i try much thing, like
array_replace with the options in the configuration of the field but didn't wrong.
make an ajax request to the url of the form action url : $form.attr('action'), i think it will load the choices option with the possible of <option> but my select is still returned with none <option>.
and much more (can't remmenber).
And also i'm using the v3.1 of the framework with the v4.0.3 of the select2 plugin, if need more info just ask and thx for reading and trying help.
Edit
Just add some info to be more clear
You making things way too complicated. In your documentation example they add eventListener for already existing form field ('sport') and you are adding it to only later added field which does not exist (your 'image' field and 'position' field from the documentation example).
You should use EntityType and if you need (which I'm not if sure you are) filter your images using query_builder option, for validation add constraints (example with controller).
class ArticleType extends AbstractType {
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// $builder
// My other field
$imageFieldFunction = $this->getImageFieldFunction();
$builder->addEventListener(FormEvents::PRE_SET_DATA, $imageFieldFunction);
$builder->addEventListener(FormEvents::PRE_SUBMIT, $imageFieldFunction);
}
private function getImageFieldFunction()
{
return function(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
//when your data_class is Article
$image = $data->getImage();//depending on your Article class
/*if you are using data_class => null
$image = $data['image'];
*/
$imageId = $image ? $image->getId() : 0;
$builder->add('image', EntityType::class , array(
'class' => 'AlmotivAppBundle:Image',
'attr' => array(
'id' => 'image'
) ,
'expanded' => false,
'multiple' => false,
'constraints' => new NotBlank(),
'query_builder' => function (EntityRepository $er) use ($imageId) {
return $er->createQueryBuilder('i')
->where('i.id = :image_id')
->setParameter('image_id', $imageId);
}
));
}
}
}

Entity is empty in formModifier on form events

English is not my native language, sorry for that.
I have a meet entity (rendezVous) and in this entity, i have two others mapped entities doctor(docteur) and customer(client).
I want to change the list of doctors when choosing a customer.
For that, I create a form events in my RendezVousType, but the problem is when i choose a customer, the Client entity is empty in my formModifier.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('client', EntityType::class, array(
'class' => 'AppBundle:Client',
'placeholder' => '',
));
$formModifier = function (FormInterface $form, Client $client = null) {
$idEspece = null === $client ? 0 : $client->getId();
$form->add('docteur', EntityType::class, array(
'class' => 'AppBundle:Docteur',
'placeholder' => '',
'query_builder' => function (DocteurRepository $er) use ($idEspece) {
return $er->getByClientEspece($idEspece);
},
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$data = $event->getData();
$formModifier($event->getForm(), $data->getClient());
}
);
$builder->get('client')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$client = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $client);
}
);
}
When I set a default value for $idEspece, the query builder returns the correct list.
Your form type is valid but Symfony don't do that automatically using ajax, then the doctor only appear after form submission.
Then to get the list of doctors using ajax you need add some additional logic.
Firstly, create a action in your controller to get the list of doctors and return a json
/**
* #Route(name="get_doctors", path="/get_doctors" )
*/
public function getDoctorsAction(Request $request)
{
$client = $request->get('rendez_vous')['client'];
$clients = $this->getDoctrine()
->getRepository('AppBundle:Docteur')
->getByClientEspece($client)
->select('s.id', 's.name')
->getQuery()
->getArrayResult();
$indexedClients = array_column($clients, 'name', 'id');
return new JsonResponse($indexedClients);
}
Add the following jquery plugin in your scripts.
https://appelsiini.net/projects/chained/
<script type="text/javascript" src="{{ asset('js/jquery.chained.min.js') }}"></script>
<script type="text/javascript" src="{{ asset('js/jquery.chained.remote.min.js') }}"></script>
Add the following javascript after your form to initialize the jquery plugin in your doctor input:
{{ form(form) }}
<script>
$("#rendez_vous_docteur").remoteChained({
parents: "#rendez_vous_client",
url: '{{ path('get_doctors') }}',
clear: true,
loading: "Loading..."
});
</script>
ADIVSE: review and update as needed ids and parameters used in the example.
Thanks for you help, but I solved my issue by myself by simply doing a
php app/console cache:clear
I'm sorry, if you loose your time for this
PS: I followed this tutorial on symfony doc for create form events.

Symfony 3 - Edit form - populate fields with db data (array)

I needed a form with 3 dynamic select boxes (choicetypes), which, after form submission, are saved as a serialized array in the database.
I have managed to make it work in the end but now I am struggling to populate the dropdowns with the database values when editing a project.
Projects Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="projects")
*/
class Projects
{
/**
* #ORM\Column(type="string", length=255)
*/
protected $projectName;
/**
* #ORM\Column(type="array")
*/
protected $frequency;
/**
* No database fields for these.
* They are used just to populate the form fileds
* and the serialized values are stored in the frequency field
*/
protected $update;
protected $every;
protected $on;
/*
* Project Name
*/
public function getProjectName()
{
return $this->projectName;
}
public function setProjectName($projectName)
{
$this->projectName = $projectName;
}
/*
* Frequency
*/
public function getFrequency()
{
return $this->frequency;
}
public function setFrequency($frequency)
{
$this->frequency = $frequency;
}
/*
* Update
*/
public function getUpdate()
{
return $this->update;
}
public function setUpdate($update)
{
$this->update = $update;
}
/*
* Every
*/
public function getEvery()
{
return $this->every;
}
public function setEvery($every)
{
$this->every = $every;
}
/*
* On
*/
public function getOn()
{
return $this->on;
}
public function setOn($on)
{
$this->on = $on;
}
}
Projects Controller
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\Projects;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* Projects controller.
*/
class ProjectsController extends Controller
{
/**
* #Route("/projects/new", name="projects_new")
*/
public function newAction(Request $request)
{
$project = new Projects();
$form = $this->createForm('AppBundle\Form\ProjectsType', $project);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
// This is where the data get's saved in the frequency field
$frequency = array(
"update" => $project->getUpdate(),
"every" => $project->getEvery(),
"on" => $project->getOn()
);
$project->setFrequency($frequency);
$em->persist($project);
$em->flush($project);
return $this->redirectToRoute('projects_show', array('id' => $project->getId()));
}
return $this->render('projects/new.html.twig', array(
'project' => $project,
'form' => $form->createView(),
));
}
/**
* #Route("/projects/edit/{id}", name="projects_edit", requirements={"id": "\d+"})
* #ParamConverter("id", class="AppBundle:Projects")
*/
public function editAction(Request $request, Projects $project)
{
$editForm = $this->createForm('AppBundle\Form\ProjectsType', $project);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
// This is where the data get's saved in the frequency field
$frequency = array(
"update" => $project->getUpdate(),
"every" => $project->getEvery(),
"on" => $project->getOn()
);
$project->setFrequency($frequency);
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('projects_edit', array('id' => $project->getId()));
}
return $this->render('projects/edit.html.twig', array(
'project' => $project,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
}
ProjectsType Form
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use AppBundle\Entity\Projects;
class ProjectsType extends AbstractType
{
private function setUpdateChoice(Projects $project)
{
return $project->getFrequency()['update'];
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('projectName', null, array());
$builder->add('update', ChoiceType::class, array(
'label' => 'Update',
'attr' => array(
'class' => 'update_selector',
),
'choices' => array(
'Daily' => 'Daily',
'Weekly' => 'Weekly',
'Monthly' => 'Monthly'
),
'data' => $this->setUpdateChoice($builder->getData())
)
);
$addFrequencyEveryField = function (FormInterface $form, $update_val) {
$choices = array();
switch ($update_val) {
case 'Daily':
$choices = array('1' => '1', '2' => '2');
break;
case 'Weekly':
$choices = array('Week' => 'Week', '2 Weeks' => '2 Weeks');
break;
case 'Monthly':
$choices = array('Month' => 'Month', '2 Months' => '2 Months');
break;
default:
$choices = array();
break;
}
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($addFrequencyEveryField) {
$update = $event->getData()->getUpdate();
$update_val = $update ? $update->getUpdate() : null;
$addFrequencyEveryField($event->getForm(), $update_val);
}
);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($addFrequencyEveryField) {
$data = $event->getData();
$update_val = array_key_exists('update', $data) ? $data['update'] : null;
$addFrequencyEveryField($event->getForm(), $update_val);
}
);
$addFrequencyOnField = function (FormInterface $form, $every_val) {
$choicesOn = array();
switch ($every_val) {
case 'Week':
$choicesOn = array(
'Monday' => 'Monday',
'Tuesday' => 'Tuesday',
'Wednesday' => 'Wednesday',
'Thursday' => 'Thursday',
'Friday' => 'Friday',
'Saturday' => 'Saturday',
'Sunday' => 'Sunday',
);
break;
case 'Month':
$choicesOn = array(
'1' => '1',
'2' => '2',
'3' => '3',
'4' => '4',
'5' => '5',
'6' => '6',
'7' => '7',
);
break;
default:
$choicesOn = array();
break;
}
$form->add('on', ChoiceType::class, array(
'choices' => $choicesOn,
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($addFrequencyOnField) {
$every = $event->getData()->getEvery();
$every_val = $every ? $every->getEvery() : null;
$addFrequencyOnField($event->getForm(), $every_val);
}
);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($addFrequencyOnField) {
$data = $event->getData();
$every_val = array_key_exists('every', $data) ? $data['every'] : null;
$addFrequencyOnField($event->getForm(), $every_val);
}
);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Projects'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_projects';
}
}
and finally, the Edit Template containing ajax
{% extends 'base.html.twig' %}
{% block body %}
{% block content %}
<h1>Edit Project</h1>
{{ form_start(edit_form) }}
{{ form_widget(edit_form) }}
<input type="submit" value="Edit" />
{{ form_end(edit_form) }}
{% endblock content %}
{% block javascripts %}
<script>
var $update = $('#appbundle_projects_update');
var $every = $('#appbundle_projects_every');
var $on_selector = $('#appbundle_projects_on');
// When update gets selected ...
$update.change(function() {
$every.html('');
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected update value.
var data = {};
data[$update.attr('name')] = $update.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) {
var options = $(html).find('#appbundle_projects_every option');
for (var i=0, total = options.length; i < total; i++) {
$every.append(options[i]);
}
}
});
});
// // When every gets selected ...
$every.change(function() {
$on_selector.html('');
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected every value.
var data = {};
data[$every.attr('name')] = $every.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) {
var options = $(html).find('#appbundle_projects_on option');
for (var i=0, total = options.length; i < total; i++) {
$on_selector.append(options[i]);
}
}
});
});
</script>
{% endblock javascripts %}
{% endblock %}
I was able to set the selected value in the update field using setUpdateChoice() and assigning the return value to the ChoiceType::class data in ProjectsType. However, this doesn't have any effect on the every select or the other one, the PRE_SET_DATA listener returning null.
I've tried the same as above with the other two fields as well, but having the fields rendered in the listener resulted in an error no such field (or something similar)
I've also tried to get the value from the frequency in the controller and using setUpdate(), setEvery() and setOn() before rendering the form, resulting in You can't modify a form that has already been submitted.
This is how the form currently renders when editing a project:
And I would need it populated like this:
Any idea/advice/example of how I can tackle this would be greatly appreciated.
Thank you
PS. If I've omitted something, please let me know and I'll update my question.
Update
Thanks to Constantin's answer, I've managed to set the fields data in the controller doing the following:
public function editAction(Request $request, Projects $project)
{
$project->setUpdate($project->getFrequency()['update']);
$project->setEvery($project->getFrequency()['every']);
$project->setOn($project->getFrequency()['on']);
$editForm = $this->createForm('AppBundle\Form\ProjectsType', $project);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$frequency = array(
"update" => $project->getUpdate(),
"every" => $project->getEvery(),
"on" => $project->getOn()
);
$project->setFrequency($frequency);
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('projects_edit', array('id' => $project->getId()));
}
return $this->render('projects/edit.html.twig', array(
'project' => $project,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
));
}
Not sure that this will help you as I have not enough time to read all your post.
First of all (and just for your information), you can use in your form for your 3 fields (update, every and on) the option 'mapped' => false and remove from your entity these 3 field (this is a mapping with your entity, not DB).
You can then access them in your controller with
$editForm->get('update')->getData(); //or setData( $data );
But of course you can keep these attributes if you want and if it is more convenient for you.
But if you do keep them, please make sure that they will always reflect the data in your frequency attribute.
Next, I see in your edit controller that before creating your form, you don't set the values of your 3 fields.
As these fields are not mapped (with the database this time), they will be null when you fetch your entity (this is done with your paramConverter in your edit controller).
So before passing your $project as a parameter in your creating form, you have to set them (unserialize or whatever).
Hope that this will help you!

Map Symfony form clicked button to data class

I have a collection in Symfony form with multiple buttons and I need to determine which button was clicked. I know It can be done calling isClicked() method on that button element, but I'd like to map this clicked button into data class, is that possible?
My basic form:
$builder->add(
'items',
'collection',
[
'type' => new ItemForm(),
'label' => FALSE,
]
);
ItemForm:
$builder->add(
'isRemoved',
'submit'
);
And data class for ItemForm:
class ItemFormData
{
/**
* #var bool
*/
private $isRemoved = FALSE;
/**
* #return boolean
*/
public function isIsRemoved()
{
return $this->isRemoved;
}
/**
* #param boolean $isRemoved
*/
public function setIsRemoved($isRemoved)
{
$this->isRemoved = $isRemoved;
}
}
And what I need is to map TRUE to isRemoved property if appropriate button was clicked. I'm using Symfony 2.7.
Actually, I've found the solution. It can be easily done using form events:
$builder->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) {
$formData = $event->getData();
$form = $event->getForm();
if ($form['isRemoved']->isClicked()) {
$formData->setIsRemoved(TRUE);
}
}
);
Peter.
I'm not sure that you can do like that.
IMO you can simple do something like that:
Create hidden field in form:
$builder->...
->add('isRemoved', 'hidden')
->...;
In template just simply render it:
{{ form_rest(form) }}
And set it field with JS:
$('#<form_selector>').on('submit', function () {
$('#<is_removed_vields_selector>').val();
});
Or use on button click event.

Symfony & Doctrine: persisting a many to one entity on an embedded form collection

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.

Categories