Form post-processing in Symfony2 - forms

I am new of Symfony, and I am trying to create a form bound to an Entity User.
One field of this entity is of type ArrayCollection. It is actually a OneToMany relationship with objects of another class.
So, a little bit of code just to be clearer.
class User
{
\\...
/**
* #ORM\OneToMany(targetEntity="UserGoods", mappedBy="users")
* #ORM\JoinColumn(name="goods", referencedColumnName="id")
*/
private $goods;
public function __construct()
{
$this->goods = new ArrayCollection();
}
\\...
}
And the associated class
class UserGoods
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\Column(name="inserted_at", type="datetime")
*/
private $insertedAt;
/**
* #var float
*
* #ORM\Column(name="value", type="float")
*/
private $value;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="goods")
*/
protected $users;
}
Now, I want to create a FormBuilder that does something extremely simple, yet I couldn't figure it out how to do it by myself.
I want just a field of type number, and if an object of type Goods with the current date exists, modify it, otherwise add a new object to the collection.
This could be easily done inside the controller, but I have a lot of instances of this form, and this would make my program impossible to maintain.
Is there a way to add some post-processing of submitted data inside the form builder?
I already tried with DataTransformers but these won't suffice, as at most they would transform a number to a UserGoods object, and the original ArrayCollection would not be preserved (and what about doctrine associations?).
In addition, if I declare the field type as collection of number types, all the items inside the ArrayCollection would be displayed when rendering the form, not just the last one.
Any idea on how to get out of this?
Thank you in advance for your help.

As suggested, use Form Events. Inside the event you will check if the Goods with the submitted date already exist (load them from database) and your will modify them with the post data. If they dont exist, you will be creating new ones. You can also make another method in your entity, getLastItemsInCollection(), where you can use Criteria, to only load the last one from the database (recommended), or get the last item from original ArrayCollection. You can make a field unmapped, and map the Goods manually in the FormEvent, as described above. I hope that helps and I hope I understood correctly.

I followed Cerad and tomazahlin suggestions and I came up with a solution.
I am sure that every year at least 2 people over the world share my same problem, so I'll take some time to post my outcome.
Feel free to correct, criticize or add me, in the end I am a newbie of Symfony!
First, how I defined my two classes in the end.
class User
{
//...
/**
* #ORM\ManyToMany(targetEntity="UserGoods", inversedBy="users", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="goods", referencedColumnName="id")
*/
// Should have been a OneToMany relationship, but Doctrine requires the
// owner side to be on the Many side, and I need it on the One side.
// A ManyToMany relationship compensate this.
private $goods;
public function __construct()
{
$this->goods = new ArrayCollection();
}
//...
}
And the connected class
/**
* #ORM\HasLifecycleCallbacks()
**/
class UserGoods
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\Column(name="inserted_at", type="datetime")
*/
private $insertedAt;
/**
* #var float
*
* #ORM\Column(name="value", type="float", nullable=true)
*/
// I do not want this field to be null, but in this way when
// persisting I can look for null elements and remove them
private $value;
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="goods")
*/
protected $users;
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
// This automatically sets InsertedAt value when inserting or
// updating an element.
public function setInsertedAtValue()
{
$date = new \DateTime();
$this->setInsertedAt( $date );
}
}
As I said, I wanted a FormBuilder to handle my array collection. The best form type for this purpose is... collection type.
This require a subform to be defined as its type.
<?php
namespace MyBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use MyBundle\Entity\UserGoods;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('goods', 'collection', array(
'type' => new GoodsdataWithDateType(),
'required' => false,
)
);
\\ ...
And the subform.
Since I need only the today's value to be displayed, and not all of them, I also need to add a FormEvent clause to check which items to insert.
namespace MyBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class GoodsdataWithDateType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Here I add the event listener:
// Since I want only today's value to be displayed, I implement
// a check on this field of each element
$builder->addEventListener(
FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$goods = $event->getData();
$form = $event->getForm();
$datetime1 = $goods->getInsertedAt();
$datetime2 = new \DateTime();
$datetime2->setTime(0, 0, 0);
if ($datetime1 > $datetime2)
{
$form->add('value', 'number', array(
'required' => false,
));
// I am setting this value with LifecycleCallbacks, and I do not
// want the user to change it, I am adding it commented just for
// completeness
// $form->add('insertedAt', 'date', array(
// 'widget' => 'single_text',
// 'format' => 'yyyy,MM,dd',
// ));
}
});
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyBundle\Entity\UserGoods',
));
}
public function getName()
{
return 'goodsdatawithdate';
}
}
This works fine, but is displayed very badly when rendered with something like {{ form(form) }} in twig files.
To make it more user-friendly, I customized how the form was presented, in order to remove some garbage and include only the labels that were necessary.
So in my twig:
{{ form_start(form) }}
{{ form_errors(form) }}
<div>
{{ form_label(form.goods) }}
{{ form_errors(form.goods) }}
<br>
{% for field in form.goods %}
{{ form_widget(field) }}
{% endfor %}
</div>
{{ form_end(form) }}
This is nice so far, but I also want to include new elements in my collection, in particular if today's good has not been inserted yet.
I can do this inside my FormBuilder, by manually add a new item in the array before calling the $builder.
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$thisuser = $builder->getData();
// I added the following function inside the User class.
// I use a for loop to scroll all the associated Goods to get the
// latest one.
$mygoods = $thisuser->getLatestGoods();
if ( $mygoods && null !== $mygoods->getId() ) {
// The Array contains already some elements
$datetime1 = $mygoods->getInsertedAt();
$datetime2 = new \DateTime();
$datetime2->setTime(0, 0, 0);
// Check when was the last one inserted
if ($datetime1 < $datetime2) // Nice way to compare dates
{
// If it is older than today, add a new element to the array
$newgoods = new UserGoods();
$thisuser->addGoods($newgoods);
}
} else {
// The array is empty and I need to create the firs element
$newgoods = new UserGoods();
$thisuser->addGoods($newgoods);
}
$builder->add('goods', 'collection', array(
'type' => new GoodsdataWithDateType(),
'required' => false,
'allow_add' => true, // this enables the array to be
// populated with new elements
)
);
But I also want that if a user removes an inserted value (i.e., inserts nothing in the form), the associated array element should be removed.
Allowing the user to remove elements is a little bit trickyer. I cannot rely on 'allow_delete' property, since by working only with the last item in the collection, all the previous ones would be removed when the form is submitted.
I cannot rely on LifecycleCallbacks neither, because the changes made to relationships are not persisted in the database.
Thankfully to open source, I found a post here that helped me.
What I needed was an EventListener on Doctrine Flush operations.
namespace MyBundle\EventListener;
use Doctrine\ORM\Event\OnFlushEventArgs;
use MyBundle\Entity\UserGoods;
class EmptyValueListener
{
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
$entities = array_merge(
$uow->getScheduledEntityInsertions(),
$uow->getScheduledEntityUpdates()
);
foreach ($entities as $entity) {
if ($entity instanceof UserGoods) {
if ($entity && null !== $entity )
{
if ( empty($entity->getValue()) )
{
$users = $entity->getUsers();
foreach ($users as $curruser)
{
$curruser->removeGoods($entity);
$em->remove($entity);
$md = $em->getClassMetadata('MyBundle\Entity\UserGoods');
$uow->computeChangeSet($md, $entity);
$em->persist($curruser);
$md = $em->getClassMetadata('MyBundle\Entity\User');
$uow->computeChangeSet($md, $curruser);
}
}
}
}
}
}
}
and registered it in my config.yml as
mybundle.emptyvalues_listener:
class: MyBundle\EventListener\EmptyValueListener
tags:
- { name: doctrine.event_listener, event: onFlush }

Related

The best practies for upload file in Symfony 3

I learned Symfony 3 and I want create form class to upload File, so i created ImageType, cutom form type to handle image uploaded in NewsType (form with some description and this field):
class ImageType extends AbstractType
{
private $path;
public function __construct($path)
{
$this->path = $path;
}
public function getParent()
{
return FileType::class;
}
public function getName()
{
return 'image';
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'image_name' => ''
));
}
/**
* #param FormView $view
* #param FormInterface $form
* #param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['image_name'] = $options['image_name'];
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAttribute('image_name', $options['image_name'])
->addModelTransformer(new ImageTransformer($this->path))
}
}
I use ImageTransformer to transform file name like 124324235342.jpg to instance File class. Form work fine when i created and saved date to database, but how manage entity in edit mode ?
public function editAction(Request $request, News $news)
{
$path = $this->getParameter('upload_directory') . $news->getImage();
$image = $news->getImage();
$form = $this->createForm(NewsType::class, $news, ['image_name' => $image]);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
$this->get('app.image_uploader')->uploadNews($news, $image);
$em = $this->getDoctrine()->getManager();
$em->persist($news);
$em->flush();
return $this->redirectToRoute('admin_news_index');
}
return $this->render('admin/news/form.html.twig', [
'form' => $form->createView(),
'news' => $news
]);
}
I want handle case to use same form to edit database entity. I populated form, but when user not upload image I don't want change this field and live old value. How accomplish this ?
The simplest method for your code:
In setter of your News type:
function setImage($image) {
if(strlen($image)) {
$this->image = $image;
}
}
This allow you to do not worry about case of lacking image, when user edits other fields. In this case $this->fileName in News will be not overwritten.
Then in service app.image_uploader you should check if file with given name exist. If not then you should not overwrite this file.
But there are some other poroblems:
1) You should validate extension of file.
2) You should use unique names for your files in your server different than names from users. Yo can use both names, your unique for string files in hard drive, and name form user in user interface, but you should not use name of file form user to store file in your hard drive.
I recommend to read about these problems in docs:
https://symfony.com/doc/current/controller/upload_file.html
Or on stack overflow:
Symfony2 file upload step by step

Symfony2 form field constraints validation before Data Transformer

I have created form which requires data transformer, but got myself into single problem: I transform data by exploding string (string should be be exploded to 3 parts), everything works, if I supply correct format string, but otherwise it throws error inside data transformer, because transformation cannot occur if wrong string format is supplied (this is expected behavior).
So the question is is there a way to validate form field for correct string before data transformation? I know that data transformation by default occurs before validation, but maybe there's a way to do it other way around?
I found one solution that might work on this thread: Combine constraints and data transformers ,
but it's looks like rough solution, besides I need to translate validation message, and I would really like to do it using default translation methods for symfony forms (without using translation service)
I thought, and also someone from symfony IRC (Iltar) suggested do it by using events, but I'm not sure how to go about this - how to attach data transformer dynamically to form field? Or maybe there's other way?
It's maybe too late but I eventually manage to do it.
Maybe it will help you.
Here is my FormType:
class PersonType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('mother', 'personSelector', array('personEntity' => $options['personEntity']));
}
}
Here is my customField where are validations:
class PersonSelectorType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options){
$transformer = new PersonByFirstnameAndLastnameTransformer($this->entityManager,$options);
$builder->addModelTransformer($transformer);
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmitForm'));
}
public function onPreSubmitForm(FormEvent $event){
$mother = $event->getData();
$form = $event->getForm();
$options = $form->getConfig()->getOptions();
if (!empty($mother)){
preg_match('#(.*) (.*)#', $mother, $personInformations);
if (count($personInformations) != 3){
$form->addError(new FormError('[Format incorrect] Le format attendu est "Prénom Nom".'));
}else{
$person = $this->entityManager->getRepository($options['personEntity'])->findOneBy(array('firstname' => $personInformations[1],'lastname' =>$personInformations[2]));
if ($person === null) {
$form->addError(new FormError('Il n\'existe pas de person '.$personInformations[1].' '.$personInformations[2].'.'));
}
}
}
}
}
Here is my transformer:
class PersonByFirstnameAndLastnameTransformer implements DataTransformerInterface{
public function reverseTransform($firstnameAndLastname) {
if (empty($firstnameAndLastname)) { return null; }
preg_match('#(.*) (.*)#', $firstnameAndLastname, $personInformations);
$person = $this->entityManager->getRepository($this->options['personEntity'])->findOneBy(array('firstname' =>$personInformations[1],'lastname' =>$personInformations[2]));
if (count($personInformations) == 3){
$person = $this->entityManager->getRepository($this->options['personEntity'])->findOneBy(array('firstname' =>$personInformations[1],'lastname' =>$personInformations[2]));
}
return $person;
}
public function transform($person) {
if ($person === null) { return ''; }
return $person->getFirstname().' '.$person->getLastname();
}
}
Perhaps you could pass the instance of your form to your transformer. If the string doesn't parse correctly, simply add a validation error to the form, like so:
<?php
// src/Acme/MyBundle/Form/DataTransformer/StringTransformer.php
namespace Acme\MyBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
use Acme\MyBundle\Entity\MyEntity;
use Acme\MyBundle\Entity\AnotherEntity;
use Acme\MyBundle\Type\MyEntityType;
class StringTransformer implements DataTransformerInterface
{
/**
* #var MyEntityType
*/
private $form;
/**
* #param ObjectManager $om
*/
public function __construct(MyEntityType $form)
{
$this->form = $form;
}
/**
* Transforms an object (entity) to a string (number).
*
* #param MyEntity|null $entity
* #return string
*/
public function transform($value)
{
// ...
}
/**
* Transforms a string (number) to an object (entity).
*
* #param string $number
*
* #return MyEntity|null
*
* #throws TransformationFailedException if object (entity) is not found.
*/
public function reverseTransform($value)
{
$collection = new ArrayCollection();
try{
$vals = explode(',', $value);
foreach($vals as $v){
$entity = new AnotherEntity();
$entity->setValue($v);
$collection->add($v);
}
} catch(\Exception $e){
$this->form
->get('my_location')
->addError(new FormError('error message'));
}
return $collection;
}
}
but it's looks like rough solution, besides I need to translate validation message, and I would really like to do it using default translation methods for symfony forms (without using translation service)
I know this question is old, but as any answer has been marked yet as the right solution, I share with you another approach.
emottet solution, using a presubmit listener to validate the data before the model transformer has been applied, is a good approach, based on this discussion.
If you want to keep using Symfony validation system for these errors too, you could use Symfony validator service (ValidatorInterface) in your pre-submit listener and pass it the required constraints, for example:
$builder
->add('whatever1', TextType::class)
->add('whatever2', TextType::class)
;
$builder->get('whatever1')
->addEventListener(FormEvents::PRE_SUBMIT, function(FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
/** #var ConstraintViolationListInterface $errors */
if ($errors = $this->validator->validate($data, new Choice([
'choices' => $allowedChoices,
'message' => 'message.in.validators.locale.xlf'
]))) {
/** #var ConstraintViolationInterface $error */
foreach ($errors as $error) {
$form->addError(new FormError($error->getMessage()));
}
}
})
->addModelTransformer($myTransformer)
;
Kind of redundant, but it works. More info here.

[Symfony][Form] Add validator/constraint to property only if it has changed

I've got the following scenario: I'm validating appointments and there's a custom validator, which tells the user if his choosen date is valid or not. It's not valid, if the date is already blocked by another entity. This works flawlessly on adding new entities.
Now I'd like to trigger the date validation on edit only if the date itself has changed. So just changing the title of the appointment should not validate the date.
My entity class:
use Doctrine\ORM\Mapping as ORM;
use Acme\Bundle\Validator\Constraints as AcmeAssert;
/**
* Appointment
*
* #ORM\Entity
* #AcmeAssert\DateIsValid
*/
class Appointment
{
/**
* #ORM\Column(name="title", type="string", length=255)
*
* #var string
*/
protected $title;
/**
* #ORM\Column(name="date", type="date")
*
* #var \DateTime
*/
protected $date;
}
The validator class (used as a service):
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the date of an appointment.
*/
class DateIsValidValidator extends ConstraintValidator
{
/**
* {#inheritdoc}
*/
public function validate($appointment, Constraint $constraint)
{
if (null === $date = $appointment->getDate()) {
return;
}
/* Do some magic to validate date */
if (!$valid) {
$this->context->addViolationAt('date', $constraint->message);
}
}
}
The corresponding Constraint class is set to target the entity class.
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class DateIsValid extends Constraint
{
public $message = 'The date is not valid!';
/**
* {#inheritdoc}
*/
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
/**
* {#inheritdoc}
*/
public function validatedBy()
{
return 'acme.validator.appointment.date';
}
}
Now I don't find a clean way to depend on a date change. I could simply track the old date in my entity, but that doesn't feel like a proper solution, if I'd like to implement more complex constraints. :[
Cheers
Since symfony 2.3 you can use Form Events to solve this problem. I added the change-check code to my FormType, by storing (and cloning) the original entity at the form creation.
Then added a POST_SUBMIT event listener to check if the fields were changed. The listener can add validation errors to your fields.
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormError;
use Acme\Bundle\Entity\Appointment;
class AppointmentType extends AbstractType
{
private $originalAppointment;
public function __construct(Appointment $original)
{
// save the original entity
$this->originalAppointment = clone $original;
}
// ...
public function buildForm(FormBuilderInterface $builder, array $options)
{
// define your fields
$builder->addEventListener(FormEvents::POST_SUBMIT, [$this, 'dateCheckListener']);
}
public function dateCheckListener(FormEvent $event)
{
$appointment = $event->getData();
$form = $event->getForm();
// if no appointments exist, we can skip the check
if (empty($appointment) || empty($this->originalAppointment)) {
return;
}
if ($appointment->getDate() !== $this->originalAppointment->getDate()) {
// the dates changed, you can call your validator here
if ('dates are not valid') {
$form->get('date')->addError(new FormError('We have a problem.'));
}
}
}
}
In your controller, you can create this formType with the original appointment:
$appointment = $this->getYourAppointmentSomehow();
$form = $this->createForm(new AppointmentType($appointment), $appointment);
Maybe you will find this article useful, to check which property is changed. Everything is possible in symfony. You might end up writing entity listeners, listener resolvers and so on. Things can get ultra advanced.
http://docs.doctrine-project.org/en/latest/reference/change-tracking-policies.html
Pay attention to the setter method:
public function setData($data)
{
if ($data != $this->data) {
$this->_onPropertyChanged('data', $this->data, $data);
$this->data = $data;
}
}
Do you see the trick?:)
I would also use !== operator to also check variable type.
You can also simplify things. You dont need to call _onPropertyChanged, but call the function, which will set a property 'dateChanged' to true. Then use method:
public function getGroupSequence()
{
if($this->dateChanged)
{
return ['date_check'];
}
else
{
return false;
}
}
And also tell your class that it implements GroupSequenceProviderInterface.
You can then use the validation group in your validation.yml for example.
maybe you want to try it with a preUpdate-Listener instead of a custom validation constraint?
Section 10.5.4 in the doctrine documentation gives an example of a validation listener "ValidCreditCardListener".
i know this will not work for automagic form validation, but i think it's the fastest way atm.
edit:
another option could be to use #UniqueEntiy constraint for the date field of your Appointment class. this will not break form validation but will cause an additional database query (as far as i know)

zend 2 + doctrine 2 hydrator - the hydrator is not hydratating POST objects

I am having problems getting my doctrine hydrator to hydrate my return post forms.
Post forms.
I keep getting the following message:
An exception occurred while executing 'INSERT INTO worker_essay
(title) VALUES (?)' with params [null]: SQLSTATE[23000]: Integrity
constraint violation: 1048 Column 'title' cannot be null
but this cannot be correct because I have a validator on my form requiring this value to be inserted, yet my form is validating.
I would really appreciate any help or advice on resolving the problem or advice on how to go about discovering what is causing the problem.
public function getInputFilterSpecification()
{
return array(
'title' => array(
'required' => true
),
);
}
these are the var_dumped values from the returned form:
object(Zend\Stdlib\Parameters)[146] public 'WorkerStatement' =>
array (size=2)
'id' => string '' (length=0)
'title' => string 'the values from title' (length=21) public 'submit' => string 'Submit' (length=6)
As you can see, the values are clearly there, which means that the problem might be in the hydrators.
I now enclosed the rest of the documents.
The Controller
public function workerStatementAction()
{
$form = new CreateWorkerStatementForm($this->getEntityManager());
$workerStatement = new WorkerStatement();
// $form->setInputFilter($workerEssay->getInputFilter());
$form->bind($workerStatement);
// var_dump($workerStatement); die();
if ($this->request->isPost()) {
$post = $this->request->getPost();
$form = $form->setData($this->request->getPost());
if ($form->isValid()) {
$post =$this->request->getPost();
$this->getEntityManager()->persist($workerStatement);
$this->getEntityManager()->flush();
// Redirect to list of aboutyou
return $this->redirect()->toRoute('worker');
}
}
return array('form' => $form);
}
The fieldset
class WorkerStatementFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('WorkerStatement');
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\WorkerStatement'))
->setObject(new WorkerStatement());
$this->add(array(
'name' => 'title',
'type' => 'Zend\Form\Element\Text',
'options' => array(
'label' => 'title',
),
));
}
** The Form**
class CreateWorkerStatementForm extends Form
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('WorkerStatement');
// The form will hydrate an object of type "AboutYou"
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\WorkerStatement'));
// Add the user fieldset, and set it as the base fieldset
$workerStatementFieldset = new WorkerStatementFieldset($objectManager);
$workerStatementFieldset->setUseAsBaseFieldset(true);
$this->add($workerStatementFieldset);
}
}
Here is the var_daump of the persist in the controller:
$this->getEntityManager()->persist($workerStatement);
object(Workers\Entity\WorkerStatement)[351]
protected 'id' => null
protected 'title' => null
You will note that they are empty, yet the var dump of the values from the returned post clearly contain the values.
I enclose my workstatement class. you will note that I have used the magic getter/setter.
<?php
namespace Workers\Entity;
use Doctrine\ORM\Mapping as ORM;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
/**
*
* #ORM\Entity
* #ORM\Table(name="worker_essay")
* #property string $title
*/
class WorkerStatement
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $title;
/**
* Magic getter to expose protected properties.
*
* #param string $property
* #return mixed
*/
public function __get($property)
{
return $this->$property;
}
/**
* Magic setter to save protected properties.
*
* #param string $property
* #param mixed $value
*/
public function __set($property, $value)
{
$this->$property = $value;
}
public function getInputFilterSpecification()
{
return array(
'title' => array(
'required' => true
)
);
}
}
DoctrineHydrator by default is hydrating and extracting values using getters and setters. If your entity doesn't have these methods then it cannot work properly. If you dont' want to use getters/setters, use new DoctrineHydrator($objectManager, 'Workers\Entity\WorkerStatement', false) instead of new DoctrineHydrator($objectManager, 'Workers\Entity\WorkerStatement').
Maybe it's not the reason why hydrator doesn't work. Please edit your first post and paste Workers\Entity\WorkerStatement class.
EDIT
Hydrator is calling getTitle() and your magic method is trying to access getTitle property which doesn't exist. You have three options:
Change DoctrineHydrator to new DoctrineHydrator($objectManager, 'Workers\Entity\WorkerStatement', false).
Add getters and setters. For example getTitle(), setTitle($title).
Refactor magic methods to accept getProperty, setProperty.
Actually You dont need to add the hydrator in the form , use it in the controller (or service) if its necessary .
plz add a var dump before :
$this->getEntityManager()->persist($workerStatement);
and post the result

Symfony 2 How do I embed collection form using some criterias

I have a problem using the embed collection forms because I want to filter the data displayed on the collection given. i.e.
<?php
Class Parent
{
... some attributes ...
/**
* #ORM\OneToMany(targetEntity="Child", mappedBy="parent", cascade={"all"})
*/
private $children;
... some setters & getters ...
}
Class Child
{
private $attribute1;
private $attribute2;
/**
* #ORM\ManyToOne(targetEntity="Parent", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id")
*/
private $parent;
... some setters & getters ...
}
Then I build the form using:
class ParentChildType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('children', 'collection', array(
'type' => ChildrenType(),
'allow_add' => true,
));
}
}
...
On controller:
$parent = $this->getDoctrine()->getEntityManager()->getRepository('AcmeBundle:Parent')->find( $id );
$forms = $this->createForm( new ParentChildType(), $parent)->createView();
and then..
return array('forms' => $forms->crateView());
My problem is when I want to filter the collection by $attribute1 and/or $attribute2 of Child model class.
There's a way to filter by a criteria for this collection forms?
It's seems that I have to filter the object before using CreateQuery and then create the form using this filtered object.
Like this:
$parent = $this->getDoctrine()->getEntityManager()->createQuery("
SELECT p, c
FROM AcmeBundle:Parent p
JOIN p.children c
WHERE c.attribute1 = :attr1
AND c.attribute2 = :attr2
")
->setParameter('attr1', <some_value>)
->setParameter('attr2', <some_value>)
->getOneOrNullResult();
$forms = $this->createForm( new ParentChildType(), $parent)->createView();
....
return array('forms' => $form->createView());
I point you to right direction (I hope) :
http://www.craftitonline.com/2011/08/symfony2-ajax-form-republish/
This article deals with field dependencies. for example, when you select a country, you have the towns that belongs to the country that appears in the list.
It's seems it looks like your problem