I\'ve searched and not found anyone with this problem.
I\'ve created my own Data Transformer as set out in the Cookbook and it all seems right but i get the error:
This is the way I handle entities with hidden inputs:
DataTransformer
<?php
namespace Comakai\CQZBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
class EntityToIdTransformer implements DataTransformerInterface
{
/**
* @var ObjectManager
*/
private $om;
private $entityClass;
/**
* @param ObjectManager $om
*/
public function __construct(ObjectManager $om, $entityClass)
{
$this->om = $om;
$this->entityClass = $entityClass;
}
/**
* Transforms an object to a string (id).
*
* @param Object|null $entity
* @return string
*/
public function transform($entity)
{
if (null === $entity) {
return "";
}
return $entity->getId();
}
/**
* Transforms a string (id) to an object.
*
* @param string $id
* @return Object|null
* @throws TransformationFailedException if object is not found.
*/
public function reverseTransform($id)
{
if (!$id) {
return null;
}
$entity = $this->om->getRepository($this->entityClass)->findOneById($id);
if (null === $entity) {
throw new TransformationFailedException(sprintf(
'An entity of class ' . $this->entityClass . ' with id "%s" does not exist!', $id
));
}
return $entity;
}
}
FormType
<?php
namespace Comakai\CQZBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\Common\Persistence\ManagerRegistry;
use Doctrine\Common\Persistence\ObjectManager;
use Comakai\CQZBundle\Form\DataTransformer\EntityToIdTransformer;
class EntityHiddenType extends AbstractType
{
/**
* @var ManagerRegistry
*/
private $registry;
/**
* @var ObjectManager
*/
private $om;
private $cache;
/**
* @param ObjectManager $om
*/
public function __construct(ManagerRegistry $registry)
{
$this->registry = $registry;
$this->om = $registry->getManager();
$this->cache = [];
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$class = (empty($options['data_class'])) ? $this->getClassFromMetadata($builder->getName(), $builder->getParent()->getDataClass()) : $options['data_class'];
$transformer = new EntityToIdTransformer($this->om, $class);
$builder->addViewTransformer($transformer);
$builder->setAttribute('data_class', $class);
}
public function getParent()
{
return 'hidden';
}
public function getName()
{
return 'entity_hidden';
}
protected function getClassFromMetadata($name, $parentClass)
{
/* @var $md \Doctrine\ORM\Mapping\ClassMetadata */
$md = $this->getMetadata($parentClass)[0];
$a = $md->getAssociationMapping($name);
$class = $a['targetEntity'];
return $class;
}
protected function getMetadata($class)
{
if (array_key_exists($class, $this->cache)) {
return $this->cache[$class];
}
$this->cache[$class] = null;
foreach ($this->registry->getManagers() as $name => $em) {
try {
return $this->cache[$class] = array($em->getClassMetadata($class), $name);
} catch (MappingException $e) {
// not an entity or mapped super class
} catch (LegacyMappingException $e) {
// not an entity or mapped super class, using Doctrine ORM 2.2
}
}
}
}
Config (services.yml)
services:
cqz.form.type.entity_hidden:
class: Comakai\CQZBundle\Form\Type\EntityHiddenType
arguments: ["@doctrine"]
tags:
- { name: form.type, alias: entity_hidden }
JobType
$builder->add('company', 'entity_hidden');
Then in your controller
$job = new \Niche\JobBundle\Entity\Job();
$type = new \Niche\JobBundle\Form\JobType();
$job->setCompany($businessUser);
$form = $this->createForm($type, $job);
This way you'll have a reusable entity_hidden type.
UPDATE FOR 2.3
Since there is no $builder->getParent() anymore (https://github.com/symfony/symfony/blob/master/UPGRADE-2.2.md) and because I don't want to set the class of the field's data, I came up with this (btw, now I'm using the form.type_guesser.doctrine service to get the class):
Config
cqz.form.type.suggest:
class: Comakai\CQZBundle\Form\Type\SuggestType
arguments: ["@doctrine.orm.entity_manager", "@form.type_guesser.doctrine"]
DataTransformer
<?php
namespace Comakai\CQZBundle\Form\DataTransformer;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\Common\Persistence\ObjectManager;
class ObjectToIdTransformer implements DataTransformerInterface
{
/**
* @var ObjectManager
*/
private $om;
private $objectClass;
/**
* @param ObjectManager $om
*/
public function __construct(ObjectManager $om, $objectClass = null)
{
$this->om = $om;
$this->objectClass = $objectClass;
}
/**
* Transforms an object to an id.
*
* @param Object|null $object
* @return mixed
*/
public function transform($object)
{
if (null === $object) {
return '';
}
return $object->getId();
}
/**
* Transforms an id to an object.
*
* @param mixed $id
*
* @return Object|null
*
* @throws TransformationFailedException if object is not found.
*/
public function reverseTransform($id)
{
if (!$id) {
return null;
}
$object = $this->om
->getRepository($this->objectClass)
->find($id)
;
if (null === $object) {
throw new TransformationFailedException(sprintf(
'An instance of "%s" with id "%s" does not exist!',
$this->objectClass,
$id
));
}
return $object;
}
public function getObjectClass()
{
return $this->objectClass;
}
public function setObjectClass($class)
{
$this->objectClass = $class;
}
}
Type
<?php
namespace Comakai\CQZBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmTypeGuesser;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Comakai\CQZBundle\Form\DataTransformer\ObjectToIdTransformer;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
class SuggestType extends AbstractType
{
/**
* @var ObjectManager
*/
private $om;
private $guesser;
/**
* @param ObjectManager $om
*/
public function __construct(ObjectManager $om, DoctrineOrmTypeGuesser $guesser)
{
$this->om = $om;
$this->guesser = $guesser;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new ObjectToIdTransformer($this->om);
$builder->addModelTransformer($transformer);
if($options['class'] === null) {
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($transformer, $builder) {
/* @var $form \Symfony\Component\Form\Form */
$form = $event->getForm();
$class = $form->getParent()->getConfig()->getDataClass();
$property = $form->getName();
$guessedType = $this->guesser->guessType($class, $property);
$options = $guessedType->getOptions();
$transformer->setObjectClass($options['class']);
});
} else {
$transformer->setObjectClass($options['class']);
}
}
...
I feel that using a PRE_SET_DATA to set the data class on the transformer is nasty, what do you think?
I just created an input type hidden:
$builder->add('child_id', 'hidden', array('mapped' => false))
In the newAction I did fill with the parent id:
$childForm->get('parent_id')->setData($parentEntity->getId());
And finally in createAction I did put:
$child->setParent($em->getReference('MyBundle:Parent', $form["child_id"]->getData()))
PS: I understand that you wanted to create your Data Transformer, but if your problem is persist object with parent id, it will help you.