I am rendering a form with an entity field in symfony.
It works well when i choose a regular entity field.
$builder
->add(\'parent\',\'entity\
This can be achieved fairly cleanly with form theming, using the standard hidden
field theme in place of that for the entity. I think using transformers is probably overkill, given that hidden and select fields will give the same format.
{% block _recipe_parent_widget %}
{%- set type = 'hidden' -%}
{{ block('form_widget_simple') }}
{% endblock %}
With Symfony 5, I use the solution of a Hidden type that implements DataTransformerInterface interface.
<?php
namespace App\Form\Type;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Defines the custom form field type used to add a hidden entity
*
* See https://symfony.com/doc/current/form/create_custom_field_type.html
*/
class EntityHiddenType extends HiddenType implements DataTransformerInterface
{
/** @var ManagerRegistry $dm */
private $dm;
/** @var string $entityClass */
private $entityClass;
/**
*
* @param ManagerRegistry $doctrine
*/
public function __construct(ManagerRegistry $doctrine)
{
$this->dm = $doctrine;
}
/**
*
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// Set class, eg: App\Entity\RuleSet
$this->entityClass = sprintf('App\Entity\%s', ucfirst($builder->getName()));
$builder->addModelTransformer($this);
}
public function transform($data): string
{
// Modified from comments to use instanceof so that base classes or interfaces can be specified
if (null === $data || !$data instanceof $this->entityClass) {
return '';
}
$res = $data->getId();
return $res;
}
public function reverseTransform($data)
{
if (!$data) {
return null;
}
$res = null;
try {
$rep = $this->dm->getRepository($this->entityClass);
$res = $rep->findOneBy(array(
"id" => $data
));
}
catch (\Exception $e) {
throw new TransformationFailedException($e->getMessage());
}
if ($res === null) {
throw new TransformationFailedException(sprintf('A %s with id "%s" does not exist!', $this->entityClass, $data));
}
return $res;
}
}
And to use the field in the form:
use App\Form\Type\EntityHiddenType;
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// Field name must match entity class, eg 'ruleSet' for App\Entity\RuleSet
$builder->add('ruleSet', EntityHiddenType::class);
}
Just made this on Symfony 3 and realised it's a bit different from what has already been posted here so I figured it was worth sharing.
I just made a generic data transformer that could be easily reusable across all your form types. You just have to pass in your form type and that's it. No need to create a custom form type.
First of all let's take a look at the data transformer:
<?php
namespace AppBundle\Form;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
/**
* Class EntityHiddenTransformer
*
* @package AppBundle\Form
* @author Francesco Casula <fra.casula@gmail.com>
*/
class EntityHiddenTransformer implements DataTransformerInterface
{
/**
* @var ObjectManager
*/
private $objectManager;
/**
* @var string
*/
private $className;
/**
* @var string
*/
private $primaryKey;
/**
* EntityHiddenType constructor.
*
* @param ObjectManager $objectManager
* @param string $className
* @param string $primaryKey
*/
public function __construct(ObjectManager $objectManager, $className, $primaryKey)
{
$this->objectManager = $objectManager;
$this->className = $className;
$this->primaryKey = $primaryKey;
}
/**
* @return ObjectManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* @return string
*/
public function getClassName()
{
return $this->className;
}
/**
* @return string
*/
public function getPrimaryKey()
{
return $this->primaryKey;
}
/**
* Transforms an object (entity) to a string (number).
*
* @param object|null $entity
*
* @return string
*/
public function transform($entity)
{
if (null === $entity) {
return '';
}
$method = 'get' . ucfirst($this->getPrimaryKey());
// Probably worth throwing an exception if the method doesn't exist
// Note: you can always use reflection to get the PK even though there's no public getter for it
return $entity->$method();
}
/**
* Transforms a string (number) to an object (entity).
*
* @param string $identifier
*
* @return object|null
* @throws TransformationFailedException if object (entity) is not found.
*/
public function reverseTransform($identifier)
{
if (!$identifier) {
return null;
}
$entity = $this->getObjectManager()
->getRepository($this->getClassName())
->find($identifier);
if (null === $entity) {
// causes a validation error
// this message is not shown to the user
// see the invalid_message option
throw new TransformationFailedException(sprintf(
'An entity with ID "%s" does not exist!',
$identifier
));
}
return $entity;
}
}
So the idea is that you call it by passing the object manager there, the entity that you want to use and then the field name to get the entity ID.
Basically like this:
new EntityHiddenTransformer(
$this->getObjectManager(),
Article::class, // in your case this would be FoodAnalytics\Recipe::class
'articleId' // I guess this for you would be recipeId?
)
Let's put it all together now. We just need the form type and a bit of YAML configuration and then we're good to go.
<?php
namespace AppBundle\Form;
use AppBundle\Entity\Article;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class JustAFormType
*
* @package AppBundle\CmsBundle\Form
* @author Francesco Casula <fra.casula@gmail.com>
*/
class JustAFormType extends AbstractType
{
/**
* @var ObjectManager
*/
private $objectManager;
/**
* JustAFormType constructor.
*
* @param ObjectManager $objectManager
*/
public function __construct(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @return ObjectManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('article', HiddenType::class)
->add('save', SubmitType::class);
$builder
->get('article')
->addModelTransformer(new EntityHiddenTransformer(
$this->getObjectManager(),
Article::class,
'articleId'
));
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\MyEntity',
]);
}
}
And then in your services.yml
file:
app.form.type.article:
class: AppBundle\Form\JustAFormType
arguments: ["@doctrine.orm.entity_manager"]
tags:
- { name: form.type }
And in your controller:
$form = $this->createForm(JustAFormType::class, new MyEntity());
$form->handleRequest($request);
That's it :-)
A quick solution whitout creating new transformer and type classes. When you want to prepopulate an related entity from the db.
// Hidden selected single group
$builder->add('idGroup', 'entity', array(
'label' => false,
'class' => 'MyVendorCoreBundle:Group',
'query_builder' => function (EntityRepository $er) {
$qb = $er->createQueryBuilder('c');
return $qb->where($qb->expr()->eq('c.groupid', $this->groupId()));
},
'attr' => array(
'class' => 'hidden'
)
));
This results a single hidden selection like:
<select id="mytool_idGroup" name="mytool[idGroup]" class="hidden">
<option value="1">MyGroup</option>
</select>
But yes, i agree that with a little more effort by using a DataTransformer
you can achieve something like:
<input type="hidden" value="1" id="mytool_idGroup" name="mytool[idGroup]"/>
I think you are simply confused about the field types and what they each represent.
An entity
field is a type of choice
field. Choice fields are meant to contain values selectable by a user in a form. When this form is rendered, Symfony will generate a list of possible choices based on the underlying class of the entity field, and the value of each choice in the list is the id of the respective entity. Once the form is submitted, Symfony will hydrate an object for you representing the selected entity. The entity
field is typically used for rendering entity associations (like for example a list of roles
you can select to assign to a user
).
If you are simply trying to create a placeholder for an ID field of an entity, then you would use the hidden
input. But this only works if the form class you are creating represents an entity (ie the form's data_class
refers to an entity you have defined). The ID field will then properly map to the ID of an entity of the type defined by the form's data_class
.
EDIT: One solution to your particular situation described below would be to create a new field type (let's call it EntityHidden) that extends the hidden
field type but handles data transformation for converting to/from an entity/id. In this way, your form will contain the entity ID as a hidden field, but the application will have access to the entity itself once the form is submitted. The conversion, of course, is performed by the data transformer.
Here is an example of such an implementation, for posterity:
namespace My\Bundle\Form\Extension\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\DataTransformerInterface;
/**
* Entity hidden custom type class definition
*/
class EntityHiddenType extends AbstractType
{
/**
* @var DataTransformerInterface $transformer
*/
private $transformer;
/**
* Constructor
*
* @param DataTransformerInterface $transformer
*/
public function __construct(DataTransformerInterface $transformer)
{
$this->transformer = $transformer;
}
/**
* @inheritDoc
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// attach the specified model transformer for this entity list field
// this will convert data between object and string formats
$builder->addModelTransformer($this->transformer);
}
/**
* @inheritDoc
*/
public function getParent()
{
return 'hidden';
}
/**
* @inheritDoc
*/
public function getName()
{
return 'entityhidden';
}
}
Just note that in the form type class, all you have to do is assign your hidden entity to its corresponding form field property (within the form model/data class) and Symfony will generate the hidden input HTML properly with the ID of the entity as its value. Hope that helps.