I would like to introduce my solution. At first, I am injecting the entity type into my form. Here is what I did in my UserBundle, which extends the FOSUserBundle because I needed to assign Groups to Users:
namespace UserBundle\Form\Type;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
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\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Email;
use UserBundle\Entity\Group;
/**
* Class UserType
* @package UserBundle\Form\Type
*/
class UserType extends AbstractType
{
/**
* @var array
*/
private $roles;
/**
* GroupType constructor.
* @param $roles
*/
public function __construct($roles)
{
$this->roles = $roles;
}
/**
* @var EntityType
*/
private $entityType;
/**
* @param EntityType $entityType
* @internal param $imagepath
*/
public function setEntityType(
$entityType
)
{
$this->entityType = $entityType;
}
/**
* Mainly for testing
* @return bool
*/
public function hasEntityType(){
return $this->entityType instanceof EntityType;
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('enabled',
CheckboxType::class,
[
'label' => 'smartadmin.user.active',
'required' => false
]
)->add('groups',
$this->entityType,
[
'class' => Group::class,
'property' => 'name',
'multiple' => true,
'expanded' => true,
'required' => false,
'label' => 'smartadmin.user.groups'
]
)->add('username',
TextType::class,
[
'label' => 'form.username',
'translation_domain' => 'FOSUserBundle'
]
)->add('email',
EmailType::class,
[
'label' => 'form.email',
'translation_domain' => 'FOSUserBundle',
'constraints' => [new Email()]
]
)->add('firstname',
TextType::class,
[
'label' => 'smartadmin.user.firstname'
]
)->add('lastname',
TextType::class,
[
'label' => 'smartadmin.user.lastname'
]
)->add('gender',
ChoiceType::class,
[
'label' => 'smartadmin.user.gender',
'choices' => [1 => 'smartadmin.user.gender_male', 2 => 'smartadmin.user.gender_female']
]
)->add('plainPassword', RepeatedType::class,
[
'type' => PasswordType::class,
'options' => array('translation_domain' => 'FOSUserBundle'),
'first_options' => array('label' => 'form.new_password'),
'second_options' => array('label' => 'form.new_password_confirmation'),
'invalid_message' => 'fos_user.password.mismatch',
'required' => false
]
);
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'UserBundle\Entity\User',
'csrf_token_id' => 'profile'
));
}
}
And in my Controller:
$oUser = $this->userRepository->find($userId);
$userType = new UserType($this->roleService->getAvailableRoles());
$userType->setEntityType(new EntityType($this->doctrine));
$oForm = $this->formFactory->createBuilder($userType)
->setData($oUser)
->getForm();
To test this form, I created a replacement entity type, which only provides the required methods to work like an entityType:
namespace UserBundle\Tests\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Util\StringUtil;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* Class EntityTypeSimulator
* @package UserBundle\Tests\Form\Type
*/
class EntityTypeSimulator extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
}
/**
* {@inheritdoc}
*/
public function finishView(FormView $view, FormInterface $form, array $options)
{
}
/**
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
if (!$resolver instanceof OptionsResolver) {
throw new \InvalidArgumentException(sprintf('Custom resolver "%s" must extend "Symfony\Component\OptionsResolver\OptionsResolver".', get_class($resolver)));
}
$this->configureOptions($resolver);
}
/**
* Configures the options for this type.
*
* @param OptionsResolver $resolver The resolver for the options.
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'multiple' => false,
'expanded' => false,
'class' => null,
'property' => true,
'invalid_message' => null
));
}
/**
* {@inheritdoc}
*/
public function getName()
{
// As of Symfony 2.8, the name defaults to the fully-qualified class name
return get_class($this);
}
/**
* Returns the prefix of the template block name for this type.
*
* The block prefixes default to the underscored short class name with
* the "Type" suffix removed (e.g. "UserProfileType" => "user_profile").
*
* @return string The prefix of the template block name
*/
public function getBlockPrefix()
{
$fqcn = get_class($this);
$name = $this->getName();
// For BC: Use the name as block prefix if one is set
return $name !== $fqcn ? $name : StringUtil::fqcnToBlockPrefix($fqcn);
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return 'Symfony\Component\Form\Extension\Core\Type\FormType';
}
}
And now my test case:
namespace UserBundle\Tests\Form\Type;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Validator\Validation;
use UserBundle\Entity\User;
use UserBundle\Form\Type\UserType;
/**
* Class UserTypeTest
* @package UserBundle\Tests\Form\Type
*/
class UserTypeTest extends TypeTestCase
{
/**
* Load the ValidatorExtension so RepeatedType can resolve 'invalid_message'
* @return array
*/
protected function getExtensions()
{
return array(new ValidatorExtension(Validation::createValidator()));
}
public function testGroupType(){
$formData['user'] = [
'enabled' => 1,
'groups' => [1,2,3],
'username' => 'Test username',
'email' => '[email protected]',
'firstname' => 'Test Firstname',
'lastname' => 'Test Lastname',
'gender' => 1,
'plainPassword' => ['first' => 'Test Password', 'second' => 'Test Password']
];
$roles = ['ROLE_USER'];
$entity = new User('Test name');
$type = new UserType($roles);
$type->setEntityType(new EntityTypeSimulator());
$oFormBuilder = $this->factory->createBuilder();
$oFormBuilder->add('user', $type);
$oFormBuilder->setData(['user' => $entity]);
$oForm = $oFormBuilder->getForm();
$oForm->submit($formData);
$oForm->handleRequest();
$this->assertTrue($oForm->isSynchronized());
$view = $oForm->createView();
$children = $view->children['user']->children;
foreach (array_keys($formData['user']) as $key) {
$this->assertArrayHasKey($key, $children);
}
$this->assertEquals($formData['user']['username'], $entity->getUsername());
$this->assertEquals($formData['user']['email'], $entity->getEmail());
}
}
entityfield documentation, the optionsattr,empty_value, etc. are not available for this type of field. Butclassis available so this may not be the reason.