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

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

Related

Symfony Dynamically Adding Entry to Form Leads to TypeError

In my form, I have a collection of forms embedded. The idea is to use jQuery to add or remove an entry (from the collection). The jQuery part works fine. I know for a fact that my entities are fine as well, because I used EasyAdminBundle before to realise this functionality and that was no problem.
The problem is: When I add an entry via jQuery and submit the form, I get the following error:
Type error: Return value of App\Entity\Answer::getIsCorrect() must be of the type boolean, null returned
This is the parent form:
<?php
namespace App\Form\Type;
use App\Entity\Question;
use App\Form\DataTransformer\TagTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class QuestionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('answers', CollectionType::class, array(
'entry_type' => AnswerType::class,
'allow_add' => true,
'allow_delete' => true,
'delete_empty' => true,
'entry_options' => ['label' => false],
'prototype' => true,
'label' => false,
'by_reference' => false,
)
)
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
'data_class' => Question::class
)
);
}
}
This is the collection form:
<?php
namespace App\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Entity\Answer;
use Symfony\Component\Validator\Constraints\Choice;
class AnswerType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'isCorrect',
CheckboxType::class,
['required' => false]
)
->add(
'text',
TextType::class,
['label' => false, 'required' => false]
);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Answer::class
));
}
}
Here are some facts:
Symfony 3.3 used
Entities work fine
jQuery works fine
Forms with fixtures can be submitted fine
The whole setup is largely inspired by this tutorial
I read that there could be a problem when allow_add is not true. But that is explicitly set. Removing entries works fine. delete_empty is ignored as well.
My first thought was that there is a problem because the form fields are not duplicated correctly by jQuery, e.g. wrong name attribute used. Especially checkboxes can sometimes be problematic. But even if I only use a text field, the issue persists. As an example:
Generated with Twig:
<input type="checkbox" id="question_answers_2_isCorrect" name="question[answers][2][isCorrect]" value="1">
Result from jQuery:
<input type="checkbox" id="question_answers_3_isCorrect" name="question[answers][3][isCorrect]" value="1">
Now, here comes the kicker: In the Stack Trace, you can clearly see that the form has the data in the first place, but it gets lost.
Symfony\Component\Debug\Exception\FatalThrowableError:
Type error: Return value of App\Entity\Answer::getIsCorrect() must be of the type boolean, null returned
at src/Entity/Answer.php:82
at App\Entity\Answer->getIsCorrect()
(vendor/symfony/property-access/PropertyAccessor.php:487)
at Symfony\Component\PropertyAccess\PropertyAccessor->readProperty(array(object(Answer)), 'isCorrect')
(vendor/symfony/property-access/PropertyAccessor.php:410)
at Symfony\Component\PropertyAccess\PropertyAccessor->readPropertiesUntil(array(object(Answer)), object(PropertyPath), 1, true)
(vendor/symfony/property-access/PropertyAccessor.php:179)
at Symfony\Component\PropertyAccess\PropertyAccessor->getValue(object(Answer), object(PropertyPath))
(vendor/symfony/form/Extension/Core/DataMapper/PropertyPathMapper.php:92)
at Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper->mapFormsToData(object(RecursiveIteratorIterator), object(Answer))
(vendor/symfony/form/Form.php:630)
// Here the data is suddenly lost
at Symfony\Component\Form\Form->submit(array(), true)
(vendor/symfony/form/Form.php:574)
at Symfony\Component\Form\Form->submit(array(array('text' => 'this gets lost')), true)
(vendor/symfony/form/Form.php:574)
// Here the data comes in, now see above ↑
at Symfony\Component\Form\Form->submit(array('answers' => array(array('isCorrect' => '1', 'text' => 'foo'), array('text' => 'bar'), array('text' => 'baz'), array('text' => 'this gets lost'))), true)
(vendor/symfony/form/Extension/HttpFoundation/HttpFoundationRequestHandler.php:113)
at Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler->handleRequest(object(Form), object(Request))
(vendor/symfony/form/Form.php:500)
at Symfony\Component\Form\Form->handleRequest(object(Request))
(src/Controller/QuestionsController.php:79)
Here's the entity Answer:
class Answer
{
/**
* #var
* #ORM\Column(type="string")
*/
protected $text;
/**
* #var
* #ORM\Column(type="boolean")
*/
protected $isCorrect;
/**
* #var
* #ORM\ManyToOne(targetEntity="Question", inversedBy="answers", cascade={"persist"})
*/
protected $question;
/**
* #return mixed
*/
public function getText() : string
{
return $this->text;
}
/**
* #param mixed $text
*/
public function setText(string $text) : void
{
$this->text = $text;
}
/**
* #return mixed
*/
public function getIsCorrect() : bool
{
return $this->isCorrect;
}
/**
* #param bool $isCorrect
*/
public function setIsCorrect(bool $isCorrect) : void
{
$this->isCorrect = $isCorrect;
}
}
This is the culprit
/**
* #return mixed
*/
public function getIsCorrect() : bool
{
return $this->isCorrect;
}
Change it to
/**
* #return mixed
*/
public function getIsCorrect() : bool
{
return boolval($this->isCorrect);
}
EDIT: ok I'll try to be a little more informative. Teh reason you're getting errors like these is because typehints in your methods are false. For instance your getIsCorrect method says it returns bool but in reality it also returns null when your isCorrect property isn't initialized. Personally I'd recommend you remove the typehints for good and just keep phpdoc, but if you really need them then try initializing your fields in your entity's constructor and setting a default db value.

Symfony2: How call the repository of an entity in the FormType

I tried to call the repository of my entity Category in the class form of my entity BonCommande, but this error ocuured:
Notice: Undefined property: Application\VehiculeBundle\Form\BonCommandeType::$em in C:\wamp\www\Symfony_test\src\Application\VehiculeBundle\Form\BonCommandeType.php line 74
This is my code:
The class BonCommandeType.php:
namespace Application\VehiculeBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Application\VehiculeBundle\Entity\Category;
class BonCommandeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Name of the user
$builder->add('observation', 'text');
/* Add additional fields... */
$builder->add('save', 'submit');
// Add listeners
$builder->addEventListener(FormEvents::PRE_SET_DATA, array($this, 'onPreSetData'));
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmit'));
}
protected function addElements(FormInterface $form, Category $categorie = null) {
// Remove the submit button, we will place this at the end of the form later
$submit = $form->get('save');
$form->remove('save');
// Add the province element
$form->add('categorie', 'entity', array(
'data' => $categorie,
'empty_value' => '-- Choose --',
'class' => 'ApplicationVehiculeBundle:Category',
'property' => 'intitule',
'mapped' => false)
);
// Cities are empty, unless we actually supplied a province
$vehicules = array();
if ($categorie) {
// Fetch the cities from specified province
$repo = $this->em->getRepository('ApplicationVehiculeBundle:Vehicule');
$cities = $repo->findByCategory($categorie);
}
// Add the city element
$form->add('vehicule', 'entity', array(
'empty_value' => '-- Select a categorie first --',
'class' => 'ApplicationVehiculeBundle:Vehicule',
'choices' => $vehicules,
));
// Add submit button again, this time, it's back at the end of the form
$form->add($submit);
}
function onPreSubmit(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
// Note that the data is not yet hydrated into the entity.
$categorie = $this->em->getRepository('ApplicationVehiculeBundle:Category')->find($data['categorie']);
$this->addElements($form, $categorie);
}
function onPreSetData(FormEvent $event) {
$account = $event->getData();
$form = $event->getForm();
// We might have an empty account (when we insert a new account, for instance)
$categorie = $account->getVehicule() ? $account->getVehicule()->getCategorie() : null;
$this->addElements($form, $categorie);
}
...
}
This is the instruction that causes the error:
$categorie = $this->em->getRepository('ApplicationVehiculeBundle:Category')->find($data['categorie']);
FormComponent is an independent component and it doesn't provide any entityManager to use. You have to inject it or pass it by $options if you want to use it..
In your case it would be correct if you directly pass it to the type's __construct or pass by $options array or declare your type as a service and inject entity manager to it:
class BonCommandeType extends AbstractType
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
...
}
or
$this->createForm(TYPE, DATA, ['em' => $em]);
From your code I assume you are missing this:
//Somewhere at the begging of your BonCommandeType
protected $em;
...
public function __construct(EntityManager $em)
{
$this->em = $em;
}
Keep in mind that when you create a new form object you should use smth like :
BonCommandeType($this->getDoctrine()->getManager()) // if inside a controller

doctrine2 zf2 form collection not render element

UPDATE: I just work until version 2.2.5 of ZF2 if I upgrade to 2.2.6 on, the item is not displayed on the form.
ZF2 2.3.1
PHP 5.4.4
Doctrine2: Master
I have a problem with the bind () the form. When I try to show an element of the Zend\Form\Collection fieldset appears empty.
I have reviewed the tutorial Doctrine Hydrator but I can not fix it.
The relationship between the entities is quite simple:
Product (OneToMany) $images
Image (ManyToOne) $product
This only happens when I add a new product, if I edit a product with image (action edit), FormCollection element shown on the form.
Product Entity
class Product
{
/**
* #var int
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=255, unique=false, nullable=true)
*/
protected $name;
/**
* #ORM\OneToMany(targetEntity="Image", mappedBy="product", cascade={"persist"})
*/
protected $images;
public function __construct()
{
$this->images = new ArrayCollection();
}
public function addImages(Collection $images)
{
foreach ($images as $image)
{
$image->setProduct($this);
$this->images->add($image);
}
}
public function removeImages(Collection $images)
{
foreach ($images as $image)
{
$image->setProduct(null);
$this->images->removeElement($image);
}
}
public function getImages()
{
return $this->images;
}
}
Image Entity
class Image
{
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Product", inversedBy="images")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $product;
public function setProduct(Product $product = null)
{
$this->product = $product;
}
public function getProduct()
{
return $this->product;
}
}
Product Form
class ProductForm extends Form implements InputFilterProviderInterface
{
public function __construct($sl)
{
$objectManager = $sl->get('Doctrine\ORM\EntityManager');
parent::__construct('product-form');
$this->setAttribute('enctype', 'multipart/form-data')
->setAttribute('method', 'post')
->setHydrator(new DoctrineHydrator($objectManager));
// Add the user fieldset, and set it as the base fieldset
$productFieldset = new ProductFieldset($sl);
$productFieldset->setName('product');
$productFieldset->setUseAsBaseFieldset(true);
$this->add($productFieldset);
$this->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Go',
'id' => 'submitbutton',
'class' => 'btn btn-primary'
)
));
$this->setValidationGroup(array(
'product',
));
}
Product Fieldset
class ProductFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct($sl)
{
$objectManager = $sl->get('Doctrine\ORM\EntityManager');
parent::__construct('product');
$this->setHydrator(new DoctrineHydrator($objectManager))
->setObject(new Product());
$this->add(array(
'name' => 'id',
'type' => 'Zend\Form\Element\Hidden'
));
$this->add(array(
'type' => 'Zend\Form\Element\Collection',
'name' => 'images',
'options' => array(
'count' => 1,
'target_element' => new ImageFieldset($objectManager)
)
));
}
}
Image Fieldset
class ImageFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct($objectManager)
{
parent::__construct('image');
$this->setHydrator(new DoctrineHydrator($objectManager))
->setObject(new Image());
$this->add(array(
'name' => 'id',
'type' => 'Zend\Form\Element\Hidden'
)
);
$this->add(array(
'name' => 'filename',
'type' => 'Zend\Form\Element\File',
'options' => array(
'label' => 'Photo Upload',
'label_attributes' => array(
'class' => 'form-label'
),
'multiple' => true,
'id' => 'filename'
)
)
);
}
public function getInputFilterSpecification()
{
return array(
'id' => array(
'required' => false
),
'filename' => array(
'required' => true,
)
);
}
}
Controller
public function addAction()
{
$sl = $this->getServiceLocator();
$form = new ProductForm($sl);
$product = new Product();
$form->bind($product);
if ($request->isPost()):
....
endif;
return array('form' => $form);
}
Maybe you are facing this issue:
BC break in forms between 2.3.0 and 2.3.1
There's been a BC break in Zend\Form between 2.3.0 and 2.3.1, we just upgraded and stumbled into it.
We get instances of our Forms through the FormElementManager as we use custom form elements, the site has been working with <= 2.3.0 but after the 2.3.1 upgrade when we get instances of Forms they no longer have any elements attached.
Some general changes when using the FormElementManager:
Creating custom elements
To use custom elements with the FormElementManager needs a bit more work and most likely a change in how you write and use your forms.
Maybe this tutorial can help - it's pretty up to date:
Inject DoctrineORM EntityManager in ZF2 Form
It uses the formElementManager and the objectManager awareness.
It is important to add the elements inside the init() method, not the constructor.
Please also have a look at the comments where Michael states:
I just found out that in the latest version of ZF2 (I have 2.2.5 here) it´s not necessary any more to pass the entityManager via the module config.
You only have to implement the “ObjectManagerAwareInterface” in the form class (as well as the required getter + setter) and you will have access via $this->getObjetManager().

zend 2 + Doctrine 2 returned checkbox values

I am using zend 2 and doctrine 2
I am not clear how to return and populate a database with returned values from a checkbox. The values should return multiple entries for the table.
I cannot find any documentation/articles on how to render or use a check box; so, I would really appreciate advice or sample code on this.
I have enclosed my class below. I believe that the problem is occurring in my entity class: i.e I am not properly using the getter/setter for an array collection, consequently I keep getting the error message, from my returned postform, that my returned values are empty.
Here is my code:
My mysql table:
CREATE TABLE workers_timetable(
id MEDIUMINT UNSIGNED NOT NULL,
timesId MEDIUMINT UNSIGNED NOT NULL,
INDEX (id ,timesId ),
INDEX times_id (timesId, id )
);
My Class;
<?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="workers_timetable")
* #property int $timesId
*/
class WorkersAvailabilityTimeTable {
/**
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="integer");
*/
protected $timesId;
public function __construct()
{
$this->timesId = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function addTimesId(Collection $timesId)
{
foreach ($timesId as $timesId) {
$this->setTimesId($this);
$this->timesId->add($timesId);
}
}
public function setTimesId()
{
return $this->timesId;
}
public function removeTimesId(Collection $timesId)
{
foreach ($timesId as $timesId) {
$this->setTimesId(null);
$this->timesId->removeElement($timesId);
}
}
public function getTimesId()
{
return $this->timesId;
}
//put your code here
}
my fieldSet class
<?php
namespace Workers\Form\Fieldset;
use Workers\Entity\WorkersAvailabilityTimeTable;
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject as DoctrineHydrator;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
class WorkersAvailabilityTimeTableFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct(ObjectManager $objectManager)
{
parent::__construct('WorkerTimeTable');
$this->setHydrator(new DoctrineHydrator($objectManager, 'Workers\Entity\WorkersAvailabilityTimeTable'))
->setObject(new WorkersAvailabilityTimeTable());
$this->add(array(
'name' => 'id',
'type' => 'Zend\Form\Element\Hidden',
));
$this->add(array(
'type' => 'Zend\Form\Element\MultiCheckbox',
'name' => 'timesIds',
'options' => array(
'label' => 'Please Select Your Availablity',
'value_options' => array(
'1' =>'mon',
'2' =>'tues'
),
),
'attributes' => array(
'value' => '1' //set checked to '1'
)
));
)
First of all, you'd want to use DoctrineModule\Form\Element\ObjectMultiCheckbox. Refer to the DoctrineModule\docs for more Information.
Second of things would be you doing something wrong inside your Entity if I'm not completely dumb right now...
public function addTimesId(Collection $timesId)
{
foreach ($timesId as $timesId) {
$this->setTimesId($this); // This should be $timesId->setWorker($this);
$this->timesId->add($timesId);
}
}
See more information about the later, again, at the DoctrineModule\docs

Display by default the name in an autocomplete field with his id in hidden field

I tried to use a field city with autocomplete. I use twitter bootstrap style and function typeahead but I do not know if my work is good.
For my example, I have 2 entities: Person and City (with approximately 37,000 communes of France).
The Person entity has a OneToMany relationship with the City field Birthplace.
So I create a hidden field type city_typeahead to add a DataTransformer "CityToIdTransformer" to persist an object from the City id sent by the form.
Until then, the autocomplete works perfectly for the creation and editing.
However, in the edit form, I want to show in the autocomplete field the name of the city registered in my hidden field.And this is where I stuck.
I thought trying with a listener, but I'm not sure of the solution to apply. If someone could guide me I shall be grateful.
Thank you.
Edit : I did similar test with autocomplete field country and I arrived after several test.
I create a listener that checks if the hidden field has a value and from it I get the name and load my field autocomplete.
I do not know if the method is clean, but it works, my autocomplete field now displays the name of the country on edit form.
FormType :
<?php
namespace Myapp\PersonBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Myapp\PersonBundle\Form\EventListener\AddNationalitySearchSubscriber;
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstname')
->add('lastname')
->add('nationality', 'country_typeahead', array(
'attr' => array('class' => 'country-toset'),
'required' => false))
->add('nationality_search','text', array(
'attr' => array(
'class' => 'country-tosearch',
'name' => 'term',
'autocomplete' => true,
'required' => false,
'mapped' => false));
$builder->addEventSubscriber(new AddNationalitySearchSubscriber());
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Myapp\PersonBundle\Entity\Person'
));
}
public function getName()
{
return 'person_person_type';
}
}
Script js in view :
<script type="text/javascript">
$('.country-tosearch').typeahead({
source: function(query, process){
$.ajax({
url: "/ajax/country",
type: "post",
data: "term="+query,
dataType: "json",
async: false,
success: function(data){
countries = []
mapped = {}
$.map(data, function(country, i){
mapped[country.name] = country;
countries.push(country.name);
});
process(countries);
}
})
},
minLength: 3,
property: 'enriched',
items:15,
updater: function (obj) {
if(mapped[obj].id){
$('.country-toset').val(mapped[obj].id);
}
return mapped[obj].name;
}
});
</script>
DataTransformer :
<?php
namespace Myapp\PersonBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
use Myapp\GeoBundle\Entity\Country;
class CountryToIdTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $om;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
/**
* Transforms an object (country) to a string (id).
*
* #param Country|null $country
* #return string
*/
public function transform($country)
{
if (null === $country) {
return "";
}
return $country->getId();
}
/**
* Transforms a string (id) to an object (country).
*
* #param string $id
* #return Country|null
* #throws TransformationFailedException if object (country) is not found.
*/
public function reverseTransform($id)
{
if (!$id) {
return null;
}
$country = $this->om
->getRepository('MyappGeoBundle:Country')->findOneBy(array('id' => $id))
;
if (null === $country) {
throw new TransformationFailedException(sprintf(
'A country with id "%s" does not exist!',
$id
));
}
return $country;
}
}
the listener :
<?php
namespace Myapp\PersonBundle\Form\EventListener;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class AddNationalitySearchSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event)
{
$country = '';
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
if ($data->getNationality()) {
$country = $data->getNationality();
}
$form->add('nationality_search','text', array(
'attr' => array(
'class' => 'country-tosearch',
'name' => 'term',
'autocomplete' => true,
'data' => $country,
'required' => false,
'mapped' => false));
}
}