Symfony2 - Set a selected value for the entity field - forms

I'm trying to set a selected value inside an entity field. In accordance with many discussions I've seen about this topic, I tried to set the data option but this doesn't select any of the values by default:
class EventType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('place', 'entity', array(
'class' => 'RoyalMovePhotoBundle:Place',
'property' => 'name',
'empty_value' => "Choisissez un club",
'mapped' => false,
'property_path' => false,
'data' => 2
))
->add('begin')
->add('end')
->add('title')
->add('description')
;
}
// ...
}
By looking for more I've found that some people had to deactivate the form mapping to the entity. That seems logical so I tried to add 'mapped' => false to the options, without success...
If it can help, here's my controller:
class EventController extends Controller
{
// ...
public function addAction()
{
$request = $this->getRequest();
$em = $this->getDoctrine()->getManager();
$event = new Event();
$form = $this->createForm(new EventType(), $event);
$formHandler = new EventHandler($form, $request, $em);
if($formHandler->process()) {
$this->get('session')->getFlashBag()->add('success', "L'évènement a bien été ajouté.");
return $this->redirect($this->generateUrl('photo_event_list'));
}
return $this->render('RoyalMovePhotoBundle:Event:add.html.twig', array(
'form' => $form->createView()
));
}
}
And the EventHandler class:
class EventHandler extends AbstractHandler
{
public function process()
{
$form = $this->form;
$request = $this->request;
if($request->isMethod('POST')) {
$form->bind($request);
if($form->isValid()) {
$this->onSuccess($form->getData());
return true;
}
}
return false;
}
public function onSuccess($entity)
{
$em = $this->em;
$em->persist($entity);
$em->flush();
}
}
I'm a bit stuck right now, is there anyone who got an idea?

You only need set the data of your field:
class EventController extends Controller
{
// ...
public function addAction()
{
$request = $this->getRequest();
$em = $this->getDoctrine()->getManager();
$event = new Event();
$form = $this->createForm(new EventType(), $event);
// -------------------------------------------
// Suppose you have a place entity..
$form->get('place')->setData($place);
// That's all..
// -------------------------------------------
$formHandler = new EventHandler($form, $request, $em);
if($formHandler->process()) {
$this->get('session')->getFlashBag()->add('success', "L'évènement a bien été ajouté.");
return $this->redirect($this->generateUrl('photo_event_list'));
}
return $this->render('RoyalMovePhotoBundle:Event:add.html.twig', array(
'form' => $form->createView()
));
}
}

In order to option appear selected in the form, you should set corresponding value to entity itself.
$place = $repository->find(2);
$entity->setPlace($place);
$form = $this->createForm(new SomeFormType(), $entity);
....

For non-mapped entity choice fields, the method I found easiest was using the choice_attr option with a callable. This will iterate over the collection of choices and allow you to add custom attributes based on your conditions and works with expanded, multiple, and custom attribute options.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('place', 'entity', array(
//...
'choice_attr' => function($place) {
$attr = [];
if ($place->getId() === 2) {
$attr['selected'] = 'selected';
//for expanded use $attr['checked'] = 'checked';
}
return $attr;
}
))
//...
;
}

When you use the query_builder option, and the data option expects an collection instance, and you don't want to touch your controller by adding setDatas for only certain fields, and you already have your querybuilder and the ids of the repopulating options in your form type class, you can repopulate a selection as following:
// Querybuilder instance with filtered selectable options
$entities = $qb_all;
// Querybuilder instance filtered by repopulating options (those that must be marked as selected)
$entities_selected = $qb_filtered;
Then in your add() Method
'data' => $entities_selected->getQuery()->getResult(), // Repopulation
'query_builder' => $entities,
EDIT: Real use case example
You want to repopulate a checkbox group rendered with following elements:
Label: What is your favourite meal?
4 Checkboxes: Pasta, Pizza, Spaghetti, Steak
And you want to repopulate 2 Checkboxes:
Pizza, Steak
$qb_all would be a QueryBuilder instance with the all 4 selectable Checkboxes
$qb_filtered would be a new additional QueryBuilder instance with the repopulating Checkboxes Pizza, Steak. So a "filtered" version of the previous one.

Related

Symfony 5 dynamic form conditional default logic

I've an use case where i need some default conditional logic on my dynamic form build in Symfony 5.
Let me try to explain what my use case is and my problem with a simple form.
For example i've a form Product with two fields:
Part (choiceType => left, right)
Length (numberType)
On change all fields (:input) are being submitted through an Ajax request.
I've two controller methods one for visiting the page (form is being build), the other
is being called for rendering the form through the ajax request (handle conditional logic).
For the conditional logic part the following needs te be done
When part is left, default length needs to be 50
When part is right, default length needs to be 100
user could change default data
Setting the default data on length based on left or right is not the problem.
When left is selected, default length becomes 50. When changing the value to 55 (form is being submitted through every change) it becomes 50 again.
This behaviour is logic, but how could the default data been overwritten?
Above situation could also been described as give user default data with option to change it
form type
<?php
// ... namespace, use statments
class ProductType extends AbstractType
{
/**
* {#inheritDoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('part', ChoiceType::class, array(
'choices' => array(
'Left' => 'left',
'Right' => 'right',
)
));
$builder->add('length', NumberType::class);
$builder->addEventListener(FormEvents::POST_SET_DATA, function(FormEvent $event) use ($options)
{
$form = $event->getForm();
if(null === $product = $event->getData()) {
return;
}
switch($product->getPart()) {
case 'left': $defaultLength = 50; break;
case 'right': $defaultLength = 100; break;
default: $defaultLength = 0;
}
$form->get('length')->setData($defaultLength);
});
}
/**
* {#inheritDoc}
*/
public function getName(): string
{
return 'product';
}
/**
* {#inheritDoc}
*/
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(array(
'data_class' => Product::class,
'translation_domain' => 'forms',
));
}
}
controller
// src/Controller/ProductController.php
// ... namespace, use statments
namespace App\Controller;
class ProductController extends AbstractController
{
public function productAction(Request $request): Response
{
$product = new Product();
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$product = $form->getData();
dd($product);
}
return $this->render('product_view.html.twig', array(
'form' => $form->createView()
));
}
public function productConfigureAjaxAction(Request $request): Response
{
$product = new Product();
$part = $request->request->get('product')['part'] ?? null;
$product->setPart($part);
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
// product_form.html.twig is an separated file and included in product_view.html.twig
// by making the form separated is could been used for an ajax response
return $this->render('product_form.html.twig', array(
'form' => $form->createView()
));
}
}

extend EntityType, set choices to selected Entity only

I've extended Symfony's EntityType as UserChooserType for use with my User entity and Select2. The choice list for the UserChooserType comes from an ldap query (via an ajax call), not a Doctrine query. So the field starts out blank.
The User entity is related to many different entities across my application. But if I want the UserChooserType to load with a current the selected User I have to add a listener to every form that uses it. e.g.:
class SiteType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$siteAdminOpts = array('label' => 'entity.site.admin', 'required'=>false);
//opts for the UserChooserType
$builder
->add('siteName', FT\TextType::class, array('label' => 'entity.site.name'))
->add('siteAdmin', UserChooserType::class, $siteAdminOpts )
//must be added to every form type that uses UserChooserType with mod for the datatype that $event->getData() returns
->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){
$site = $event->getData();
$form = $event->getForm(); //SiteType
if($user = $site->getSiteAdmin()) $siteAdminOpts['choices'] = array($user);
$form->add('siteAdmin', UserChooserType::class, $siteAdminOpts);
});
}
//etc.
tldr;
I'd like to either:
set UserChooserType's choices option to the selected user in UserChooserType::configureOptions(), or
move ->addEventListener(...) into UserChooserType::buildForm().
Any idea how it might be done?
Here is the UserChooserType:
class UserChooserType extends AbstractType
{
/**
* #var UserManager
*/
protected $um;
/**
* UserChooserType constructor.
* #param UserManager $um
*/
public function __construct(UserManager $um){
$this->um = $um; //used to find and decorate User entities. It is not a Doctrine entity manager, but it uses one.
}
/**
* #inheritDoc
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
if (!$data) return;
$user = $this->um->getUserByUserName($data);
if(!$user->getId()) $this->um->saveUser($user); //create User in db, if it's not there yet.
});
$builder->resetViewTransformers(); //so new choices aren't discarded
$builder->addModelTransformer(new CallbackTransformer(
function ($user) { //internal storage format to display format
return ($user instanceof User) ? $user->getUserName() : '';
},
function ($username) { //display format to storage format
return ($username) ? $this->um->getUserByUserName($username) : null;
}
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'class' => 'ACRDUserBundle:User',
'label' => 'ldap.user.name',
'choice_label' => function($user, $key, $index){
$this->um->decorateUser($user);
$label = $user->getDetail('displayName');
return $label ? $label : $user->getUserName();
},
'choice_value' => 'userName',
'choices' => [],
'attr' => array(
'class' => 'userchooser',
'placeholder' => 'form.placeholder.userchooser'
)
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'my_userchooser';
}
/**
* #inheritDoc
*/
public function getParent() {
return EntityType::class;
}
}
This helped me out:
https://symfony.com/doc/current/reference/forms/types/entity.html#using-choices
Here is how I used it (again with select 2):
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** #var ProductCollection $productCollection */
$productCollection = $builder->getData();
$builder
->add('products', EntityType::class,
[
'class' => Product::class,
'required' => FALSE,
'expanded' => FALSE,
'multiple' => TRUE,
'choices' => $productCollection->getProducts(), // We only preload the selected products. The rest come from api.
'attr' => [
'data-toggle' => 'select',
'data-options' => json_encode([
'ajax' => [
'url' => '/admin/product/autocomplete',
'dataType' => 'json'
]
])
],
'choice_label' => function (Product $product) {
return $product->getName();
},
'choice_attr' => function (Product $product) {
return [
'data-avatarsrc' => $product->getImage()->getUrl(),
'data-caption' => $product->getSkus()->first()->getSku(),
];
},
]
)
;
}
And here is my JS for the init of the select2 (copy/paste):
function() {
var elements = document.querySelectorAll('[data-toggle="select"]');
function templateResult(element) {
let avatarsrc, caption;
if (element.id && element.avatarsrc) {
avatarsrc = element.avatarsrc
caption = element.caption ? ' ' + element.caption : ''
}
else if (element.element) {
avatarsrc = element.element.dataset.avatarsrc ? element.element.dataset.avatarsrc : ''
caption = element.element.dataset.caption ? ' ' + element.element.dataset.caption : ''
}
if (!avatarsrc) {
return element.text + caption
}
let wrapper = document.createElement("div");
return wrapper.innerHTML = '<span class="avatar avatar-xs mr-3 my-2"><img class="avatar-img rounded-circle" src="' + avatarsrc + '" alt="' + element.text + '"></span><span>' + element.text + '</span><span class="badge badge-soft-success ml-2">' + caption + '</span>', wrapper
}
jQuery().select2 && elements && [].forEach.call(elements, function(element) {
var select, additionalOptions, select2options;
additionalOptions = (select = element).dataset.options ? JSON.parse(select.dataset.options) : {};
select2options = {
containerCssClass: select.getAttribute("class"),
dropdownCssClass: "dropdown-menu show",
dropdownParent: select.closest(".modal") ? select.closest(".modal") : document.body,
templateResult: templateResult,
templateSelection: templateResult
}
$(select).select2({...select2options, ...additionalOptions})
})
}(),
Some remarks first:
The field name UserChooserType is a bit elaborated. UserType is shorter and just as clear, and fits the Symfony naming conventions better
I wouldn't extend UserChooserType from EntityType. Doc says EntityType is specifically dedicated to entities coming from Doctrine. It adds magic so that the field is easily configured around Doctrine: transformers, automatic fetching of choices from the DB, etc. If your users are fully defined in the LDAP, I would instead recommend extending ChoiceType and populate your choices from the LDAP directly.
Coming back to your problem, what you want here is to leverage the almighty power of dependency injection, by declaring your field type as a service. After having done that, instead of:
$builder->add('siteAdmin', UserChooserType::class, $siteAdminOpts)
You will do (assuming you chose the form name user_chooser_type):
$builder->add('siteAdmin', 'user_chooser_type', $siteAdminOpts)
You need to inject the security.token_storage service into your field type. This is the service that holds the current user's information, you can access it like this:
$user = $securityTokenStorage->getToken()->getUser();
With this the population of the default value can happen at the field type level instead of its parent.
Using this, you could also add the LDAP choices at field type level too, by injecting a service able to communicate with the LDAP.
It should finally noted that all default symfony field types are also declared as services.
EDIT:
Previous answer is beside the point.
In Symfony, I'm not aware of any possible simple way to modify the choice list of a choice/entity field after it has been created. I don't think there is, and actually I might have done the same as you to tackle the issue you're describing.
My opinion is that you're already doing what needs to be done in this case.
If you're really motivated though, I think it might just be possible to move the event listener in the form type like this:
->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($options) {
if ($form->hasParent()) {
$data = $event->getData();
$form = $event->getForm();
$method = 'get' . ucfirst($form['action_name']);
if($user = $data->$method()) $siteAdminOpts['choices'] = array($user);
$form->getParent()->add($options['form_name'], UserChooserType::class, $siteAdminOpts);
}
});
Something like that. Not sure how that would work though.
I'm still interested in seeing if anyone comes up with a clean solution.

How to add data transformer inside a event listener

So, here's my problem: I have to add a field to a form based on it's underlying data but i've to add a data transformer to the field.
I thought the solution will be simple, just add a PRE_SET_DATA event listener to the form (just to access to the underlying data) and add the field and the transformer inside the listener. But i can't add the transformer inside the listener because the form is already locked.
I´ve tried many workarounds but i couldn't solved it. Her's my code:
$builder->...
->add(
$builder->create('date', 'choice', array(
'label' => 'form.game.date',
'empty_value' => 'form.game.date',
'required' => false,
'choices' => array(
'2014-04-10' => '10/Apr', // just to test
'2014-04-11' => '11/Apr',
)
))
->addModelTransformer(new DateToStringTransformer())
);
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
$game = $event->getData();
$form = $event->getForm();
$period = new \DatePeriod(
$game->getTournament()->getBeginDate(),
new \DateInterval('P1D'),
$game->getTournament()->getEndDate()->add(new \DateInterval('P1D'))
);
$dates = array();
foreach($period as $date){
$dates[$date->format("Y-m-d")] = $date->format("j/M");
}
$form->add('date', 'choice', array(
'label' => 'form.game.date',
'choices' => $dates,
));
});
When i add the date field to the form inside event listener, the previously added data field is replaced so it's data transformer...
Is there a way to do it?
I wrote some test and updated your code a bit. Check if i understand your question correctly.
SomeTypeTest.php:
<?php
class SomeTypeTest extends TypeTestCase
{
/**
* #test
*/
public function testSubmitValidData()
{
$begin = new \DateTime();
$formData = array(
'date' => '2014-01-15'
);
$type = new SomeType();
$form = $this->factory->create($type);
$form->submit($formData);
$this->assertTrue($form->isSynchronized());
$this->assertEquals(['date' => \DateTime::createFromFormat('Y-m-d', '2014-01-15')], $form->getData());
$view = $form->createView();
$children = $view->children;
foreach (array_keys($formData) as $key) {
$this->assertArrayHasKey($key, $children);
}
}
}
SomeType.php:
<?php
class SomeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($builder) {
//$game = $event->getData();
$form = $event->getForm();
$period = new \DatePeriod(
\DateTime::createFromFormat('Y-m-d', '2014-01-01'), // for test
new \DateInterval('P1D'),
\DateTime::createFromFormat('Y-m-d', '2014-01-30') // for test
);
$dates = array();
foreach ($period as $date) {
$dates[$date->format("Y-m-d")] = $date->format("j/M");
}
$form->add($builder->create('date', 'choice', array(
'label' => 'form.game.date',
'empty_value' => 'form.game.date',
'auto_initialize' => false,
'required' => false,
'choices' => $dates
))
->addModelTransformer(new DateToStringTransformer())->getForm()
);
});
}
public function getName()
{
return 'st';
}
}
DateToStringTransformer.php:
<?php
class DateToStringTransformer implements DataTransformerInterface
{
/**
* #param mixed $value
* #return mixed|void
*/
public function transform($value)
{
if (!$value) {
return null;
}
return $value->format('Y-m-d');
}
/**
* #param mixed $value
* #return mixed|void
*/
public function reverseTransform($value)
{
return \DateTime::createFromFormat('Y-m-d', $value);
}
}
https://gist.github.com/ChubV/11348928
I've managed to get it work by creating a custom type that always add the data transformer. Then i can call "form->add('date', 'my_type',..)" from any event listener without loosing the data transformer.
MyType.php
class MyType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('field1')
->add('field2')
...;
$builder->addEventSubscriber(new AddDateSubscriber());
}
}
CustomType.php
class DateChoiceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new DateToStringTransformer());
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'invalid_message' => 'The selected date does not exist',
));
}
public function getParent()
{
return 'choice';
}
public function getName()
{
return 'date_choice';
}
}
Every time i add a date_choice type to a form the data transformer will be added too.
class AddDateSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event)
{
$game = $event->getData();
$form = $event->getForm();
$endDate = \DateTime::createFromFormat('Y-m-d', $game->getTournament()->getEndDate()->format('Y-m-d'));
$period = new \DatePeriod(
$game->getTournament()->getBeginDate(),
new \DateInterval('P1D'),
$endDate
);
$dates = array();
foreach($period as $date){
$dates[$date->format("Y-m-d")] = $date->format("j/M");
}
$form->add('date', 'date_choice', array(
'label' => 'form.game.date.label',
'empty_value' => 'form.game.date.none',
'required' => false,
'choices' => $dates,
));
}
}
DateToStringTransformer.php
class DateToStringTransformer implements DataTransformerInterface
{
public function transform($date)
{
if (null === $date) {
return "";
}
return $date->format("Y-m-d");
}
public function reverseTransform($stringDate)
{
if (!$stringDate) {
return null;
}
$date = \DateTime::createFromFormat('Y-m-d', $stringDate);
if (false === $date) {
throw new TransformationFailedException('Sting to date transformation failed!');
}
return $date;
}
}
Hope that this will help someone.

zendframework 2 Doctrine 2 my post form is not returning the values

i am a little baffled by this;
my post forms is not populating the values received from the returned post values; i suspect the problem is arising from my getJobId() in my jobsort class values;
below is my form:
public function jobSortAction()
{
$form = new CreateJobSortForm($this->getEntityManager());
$jobSort = new JobSort();
$form->setInputFilter($jobSort->getInputFilter());
$id= 11;
$jobSort->setId($id);
$form->bind($jobSort);
if ($this->request->isPost()) {
//$post = $this->request->getPost();
$form->setData($this->request->getPost());
//var_dump($post);
//var_dump($jobSort);
if ($form->isValid()) {
$this->getEntityManager()->persist($jobSort);
$this->getEntityManager()->flush();
}
}
return array('form' => $form);
}
below is the var_dumped values of the 'return post values' and the Jobsort() object. You will note that the returned post values has values for both the Id and the JobId
object(Zend\Stdlib\Parameters)[168]
public 'JobSort' =>
array (size=2)
'jobId' => string '5' (length=1)
'id' => string '11' (length=2)
public 'submit' => string 'Submit' (length=6)
object(Workers\Entity\JobSort)[394]
protected 'inputFilter' => null
protected 'id' => int 11
protected 'jobId' => null
protected 'workerservicelist' => null
yet, when i populate the values, it does not seem to record the values for the jobId
below is my jobsort entity class:
class JobSort
{
protected $inputFilter;
/**
* #ORM\Id
*
* #ORM\Column(name="user_id", type="integer")
*/
protected $id;
/**
* #ORM\Column(name="jobId", type="integer")
*/
protected $jobId;
public function setId($id)
{
return $this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setJobId($jobId)
{
return $this->jobId = $jobId;
}
public function getJobId( )
{
return $this->jobId;
}
is there any advice or suggestions on what i need to do to find out why the values are not been populated
warm regards
Andreea
by the way; the form actually works when i had the Id of CLASS jobsort set to
#ORM\GeneratedValue(strategy="AUTO")
the problem started when i took it out and set it to manual
Hello again
here is my form:
this is the error message i received;
An exception occurred while executing 'INSERT INTO worker_main_jobsort (user_id, jobId) VALUES (?, ?)' with params [11, null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'jobId' cannot be null
here is my form:
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
use Zend\Form\Form;
use Workers\Form\Fieldset\JobSortFieldset;
class CreateJobSortForm extends Form
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('create-Job-post-form');
// The form will hydrate an object of type "BlogPost"
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\JobSort'));
// Add the user fieldset, and set it as the base fieldset
$JobSortFieldset = new JobSortFieldset($objectManager);
$JobSortFieldset->setUseAsBaseFieldset(true);
$this->add($JobSortFieldset);
// Optionally set your validation group here
// … add CSRF and submit elements …
$this->add(array(
'name' => 'submit',
'type' => 'Submit',
'attributes' => array(
'value' => 'Submit',
'id' => 'submitbutton',
),
));
// Optionally set your validation group here
}
}
and here is the fieldset class:
class JobSortFieldset extends Fieldset
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('JobSort');
$id= 10;
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\JobSort'))
->setObject(new JobSort());
}
}
this addition is in response to rafaame solution;
i amended my form as recommended; however it still not working. i think the issue now is that Rafaame solution is in regarding to zendDB save method, but i am using doctrine persis**t and **flush method . i accordingly get the following error message;
Call to undefined method Workers\Entity\JobSort::save()
below is my amended form:
public function jobSortAction()
{
$form = new CreateJobSortForm($this->getEntityManager() );
$jobSort = new JobSort();
if($this->request->isPost())
{
$form->setData($this->request->getPost());
if ($form->isValid())
{
$entity = $form->getData();
$model = new JobSort();
$model->save($entity);
// $this->getEntityManager()->persist( $model);
// $this->getEntityManager()->flush();
}
}
return array('form' => $form);
}
in response to Rafaame question about what problems i had,the message that i am now receiving is this:
**
EntityManager#persist() expects parameter 1 to be an entity object,
array given.
**
below is my function:
public function jobSortAction()
{
$serviceLocator = $this->getServiceLocator();
$objectManager = $this->getEntityManager();
$form = new CreateJobSortForm($this->getEntityManager());
if ($this->request->isPost())
{
$form->setData($this->request->getPost());
if ($form->isValid()) {
$entity = $form->getData();
$model = new JobSort($objectManager, $serviceLocator);
$model->getEntityManager()->persist($entity);
$model->getEntityManager()->flush();
}
}
return array('form' => $form);
}
my form; i.e where the hydrator should be set
namespace Workers\Form;
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
use Zend\Form\Form;
use Workers\Form\Fieldset\JobSortFieldset;
class CreateJobSortForm extends Form
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('JobSort');
// The form will hydrate an object of type "BlogPost"
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\JobSort'));
// Add the user fieldset, and set it as the base fieldset
$JobSortFieldset = new JobSortFieldset($objectManager);
$JobSortFieldset->setUseAsBaseFieldset(true);
$this->add($JobSortFieldset);
If you check your code, you are creating a JobSort entity, setting only its id and binding it to the form:
$jobSort = new JobSort();
$jobSort->setId($id);
$form->bind($jobSort);
After that, you are dumping $jobSort and $this->request->getPost(). So, obviously, you are getting jobId in the POST data but not in the entity (you didn't set the entity's jobId before binding it to the form). There's nothing wrong with your entity's code.
The solution for this: don't bind anything to the form. You should only bind an entity to the form in the case of an edit action, that you fetch the entity from the database and want to populate the form with its values.
Example of add action:
public function addAction()
{
$serviceLocator = $this->getServiceLocator();
$objectManager = $this->getObjectManager();
$form = new Form\EmailCampaign\Add($serviceLocator, $objectManager);
if($this->request instanceof HttpRequest && $this->request->isPost())
{
$form->setData($this->request->getPost());
if($form->isValid())
{
$entity = $form->getData();
//If you want to modify a property of the entity (but remember that it's not recommended to do it here, do it in the model instead).
//$entity->setJobId(11);
$model = new Model\EmailCampaign($serviceLocator, $objectManager);
$model->save($entity);
if($entity->getId())
{
$this->flashMessenger()->addSuccessMessage('Email campaign successfully added to the database.');
return $this->redirect()->toRoute('admin/wildcard', ['controller' => 'email-campaign', 'action' => 'edit', 'id' => $entity->getId()]);
}
else
{
$this->flashMessenger()->addErrorMessage('There was an error adding the email campaign to the database. Contact the administrator.');
}
}
}
return new ViewModel
([
'form' => $form,
]);
}
Example of edit action:
public function editAction()
{
$serviceLocator = $this->getServiceLocator();
$objectManager = $this->getObjectManager();
$form = new Form\EmailCampaign\Edit($serviceLocator, $objectManager);
$id = $this->getEvent()->getRouteMatch()->getParam('id');
$entity = $objectManager
->getRepository('Application\Entity\EmailCampaign')
->findOneBy(['id' => $id]);
if($entity)
{
$form->bind($entity);
if($this->request instanceof HttpRequest && $this->request->isPost())
{
$form->setData($this->request->getPost());
if($form->isValid())
{
//If you want to modify a property of the entity (but remember that it's not recommended to do it here, do it in the model instead).
//$entity->setJobId(11);
$model = new Model\EmailCampaign($serviceLocator, $objectManager);
$model->save($entity);
$this->flashMessenger()->addSuccessMessage('Email campaign successfully saved to the database.');
}
}
}
else
{
$this->flashMessenger()->addErrorMessage('A email campaign with this ID was not found in the database.');
return $this->redirect()->toRoute('admin', ['controller' => 'email-campaign']);
}
return new ViewModel
([
'form' => $form,
'entity' => $entity,
]);
}
Hope this helps.
EDIT:
What I provided was an example of how to handle the form and the entities with Doctrine 2 + ZF2.
What you have to keep in mind is that Doctrine doesn't work with the concept of models, it just understands entities. The model I'm using in my application is a concept of the MVC (Model-View-Controller) design pattern (that ZF2 uses) and I have decided to wrap the entity manager calls (persist and flush) inside my model's method, that I named save() (in the case the entity needs some special treatment before being save to the database and also because it is not a good practice to use the entity manager directly in the controller - see this slide of Marcos Pivetta presentation http://ocramius.github.io/presentations/doctrine2-zf2-introduction/#/66).
Another thing that you may be misunderstanding is that when you do $form->getData() to a form that has the DoctrineObject hydrator, it will return you the entity object, and not an array with the data (this last happens if it has no hydrator). So you don't need to create the entity after doing $form->getData(), and if you do so, this created entity won't have any information provided by the form.
Your code should work now:
public function jobSortAction()
{
$serviceLocator = $this->getServiceLocator();
$entityManager = $this->getEntityManager();
$form = new CreateJobSortForm($entityManager);
if ($this->request->isPost())
{
$form->setData($this->request->getPost());
if ($form->isValid()) {
//I'm considering you are setting the DoctrineObject hydrator to your form,
//so here we will get the entity object already filled with the form data that came through POST.
$entity = $form->getData();
//Again, if you need special treatment to any data of your entity,
//you should do it here (well, I do it inside my model's save() method).
//$entity->setJobId(11);
$entityManager->persist($entity);
$entityManager->flush();
}
}
return array('form' => $form);
}

Model populated with unposted data

I have two models, Lesson and Evaluation. Each lesson can have multiple evaluations.
I am trying to set up an embedded form which will allow users to enter all of this data at the same time.
It works fine for adding and editing data, however I have a problem if I try to remove an evaluation.
For example, I have a lesson with three evaluations attached. I then submit with form again but with one of those removed.
In the controller, I first get the lesson being edited, then get its evaluations and loop through them, printing the ids. Three ids are printed as expected.
Next, I bind the request to the form and check if it is valid. I then get the evaluations again and loop through them once more to check that they've been removed, however all three ids were still there!
If I print the raw POST data, there are only two.
Can anyone see what I have done wrong?
Here is my controller code:
public function editAction($id = NULL)
{
$lesson = new Lesson;
if ( ! empty($id))
{
$lesson = $this->getDoctrine()
->getRepository('LessonBundle:Lesson')
->find($id);
}
foreach ($lesson->getEvaluations() as $evaluation)
{
print_r($evaluation->getId());
print_r('<br />');
}
$form = $this->createForm(new LessonType(), $lesson);
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
foreach ($lesson->getEvaluations() as $evaluation)
{
print_r($evaluation->getId());
print_r('<br />');
}
die();
$em = $this->getDoctrine()->getEntityManager();
$em->persist($lesson);
$em->flush();
}
}
}
Here is my lesson form:
class LessonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('evaluations', 'collection', array(
'type' => new EvaluationType(),
'allow_add' => true,
'by_reference' => false,
'allow_delete' => true,
));
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'LessonBundle\Entity\Lesson',
);
}
public function getName()
{
return 'Lesson';
}
}
And finally, my Evaluation form:
class EvaluationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('report');
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'LessonBundle\Entity\Evaluation',
);
}
public function getName()
{
return 'Evaluation';
}
}
Any advice appreciated.
Thanks.
I think your form is not binded properly with the Class.
have a look at here http://symfony.com/doc/2.0/cookbook/form/form_collections.html
Form should look like this
$builder
->add('name lesson')
->add('evaluation', 'collection', array('type'=>new EvaluationsType()))
YOu need to create a new form class with other forms embedded.
The other way is to manually check the posted data and remove the evalauation manually in the controller and then persist the Lesson again
I got around in by using:
$lesson = $form->getData();