1
votes

I'm using Symfony 3.3.

- Summary:

I have a form which I have to validate in the Controller, the entity used in the validation contains an ArrayCollection attribute, which means that I'm going to validate an embeded forms using 2 EntityType classes (a parent one having a CollectionType of child one).

The problem is: I want to pass an EntityManager object to the child EntityType to edit the form data, before the validation, through the OptionResolver.

So, I have to pass the EntityManager Object from the Controller to the ChildType by 2 steps:

  • From the Controller to the ParentType : successfully done
  • From the ParentType to the ChildType : I got an error (see below)

- Detail:

In the Controller:

public function postAssignmentAction(Request $request)
{
    $result = new Assignments();
    $em = $this->get('doctrine.orm.entity_manager');
    $form = $this->createForm(AssignmentsType::class, $result, array(
            'validation_groups' => array('Creation', 'Default'),
            'em' => $em,
    ));
    $form->submit($request->request->all()["assignments"]);
    //validation ...
}

In the ParentType (AssignmentsType):

class AssignmentsType extends AbstractType
{
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    //dump($options['em']); //this line works fine and it dump the EntityManager
    $builder->add('assignments', CollectionType::class,array(
      'entry_type' => AssignmentType::class,
      'required'=> true,
      'allow_add' => true,
      'by_reference' => false,
      'em' => $options['em'] //without this line, there is no error if we delete the ChildType (AssignmentType)
    );
  public function configureOptions(OptionsResolver $resolver)
  {
    $resolver->setDefaults([
      'data_class' => 'Empire\UniversityBundle\Entity\Assignments',
      'cascade_validation' => true,
      'error_bubbling' => false,
      'em' => null,
    ]);
    $resolver->setRequired('em');
  }
}

Until now there is no error if we delete this line : ('em' => $options['em']) from the previous code.

If I reapeat the same thing while passing the options from the ParentType to the ChildType as we did in the ParentType, I got an error (below).

Let's finish with the ChildType class:

class AssignmentType extends AbstractType
{
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    dump($options['em']); //this line can't be executed because of the error
    $this->em = $options['em'];
    $builder->add('full_name');
  }
  public function configureOptions(OptionsResolver $resolver)
  {
    $resolver->setDefaults([
        'data_class' => 'Empire\UniversityBundle\Entity\Assignment',
        'validation_groups' => array('Creation', 'Default'),
        'cascade_validation' => true,
        'error_bubbling' => false,
        'em' => null,
    ]);
    $resolver->setRequired('em');
  }
}

So in the ParentType, using the options of the ParentType:buildForm():

public function buildForm(FormBuilderInterface $builder, array $options){
  $builder->add('assignments', CollectionType::class,array(
    'entry_type' => AssignmentType::class,
    'required'=> true,
    'allow_add' => true,
    'by_reference' => false,
    'em' => $options['em']
  );
}

- The error part

I got this error:

"message": "The option \"em\" does not exist. Defined options are: \"action\", \"allow_add\", \"allow_delete\", \"allow_extra_fields\", \"attr\", \"auto_initialize\", \"block_name\", \"by_reference\", \"compound\", \"constraints\", \"csrf_field_name\", \"csrf_message\", \"csrf_protection\", \"csrf_token_id\", \"csrf_token_manager\", \"data\", \"data_class\", \"delete_empty\", \"disabled\", \"empty_data\", \"entry_options\", \"entry_type\", \"error_bubbling\", \"error_mapping\", \"extra_fields_message\", \"horizontal_input_wrapper_class\", \"horizontal_label_class\", \"horizontal_label_offset_class\", \"inherit_data\", \"invalid_message\", \"invalid_message_parameters\", \"label\", \"label_attr\", \"label_format\", \"label_render\", \"mapped\", \"method\", \"post_max_size_message\", \"property_path\", \"prototype\", \"prototype_data\", \"prototype_name\", \"required\", \"sonata_admin\", \"sonata_field_description\", \"sonata_help\", \"translation_domain\", \"trim\", \"upload_max_size_message\", \"validation_groups\".",
"class": "Symfony\\Component\\OptionsResolver\\Exception\\UndefinedOptionsException",

Even after defining the "em" option in the ChildType:configureOptions() method:

$resolver->setDefaults([
    'data_class' => 'Empire\UniversityBundle\Entity\Assignment',
    'cascade_validation' => true,
    'error_bubbling' => false,
    'em' => null,
]);
$resolver->setRequired('em');

I don't know why in the first step (passing the EntityManager from the Controller to the ParentType), it works

But in the second step (passing the EntityManager from the ParentType to the ChildType), it doesn't work

For more details about the entities, you can find here the Entity classes:

  • Assignment : is the Child Entity which have these attributes

    • name
    • description
  • Assignments : is the Parent Entity which have this attribute

    -assignments : an ArrayCollection of "Assignment" entities

Otherwise, is there any solution to pass have the EntityManager in the ChildType ? I mean something without any hack such as using the variable $options["label"] or $options["attr"]

Thank you very much for any suggestion.

- Github discussion issue : https://github.com/symfony/symfony/issues/25675

Best regards

2
You can define your form types as services and just inject the em into them. - Cerad
How many classes you have? AssignmentType or 'AssignmentsType'? - Imanali Mamadiev
Thank you for answering my question. @Cerad I can create a form type as a service but the problem with Symfony3 is that we have to specify the class type using a string like Assignment::class in the "entry_type" row of the buildForm:add() method. Even in the createForm() method in the Controller. So we can't use new Assignment($options['em']) syntax in the AssignmentsType - Mohamed Aymen Karmous
@ImanaliMamadiev Thank you also for answering my question, as I explained above, I have a ParentType which is AssignmentsType and I have also a ChildType which is AssignmentType - Mohamed Aymen Karmous
@MohamedAymenKarmous - no need for new. Would not work anyways. Just pass the class name exactly like you are doing. The createForm method is smart enough to look in the container to see if the type has been defined as a service and, if so, pull it from the container. No need to pass $em as an option. It all should just work. - Cerad

2 Answers

1
votes

I asked a general question about passing an object (not specifically a service like an instance of EntityManager which have the same value wherever) from an EntityType to another EntityType : github.

After some researches I found the best solution that I posted here github answer.

It was a mistake for me to pass an object from a ParentType to a ChildType using the same row "custom_option" in the third parameter of buildForm:add() method in the ParentType. Because the ParentType class contains an embeded form and to pass any option (even a custom one) to the ChildType, the option should be in the 'entry_options' row of the third argument of the buildForm:add(). Otherwise the option will be sent to the CollectionType and not ChildType class.

Reference: collection.html#entry-options

So the modified ParentType should be like this according to my code in my first question (in this subject):

class AssignmentsType extends AbstractType
{
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    //dump($options['em']); //this line works fine and it dump the object from the em option
    $builder->add('assignments', CollectionType::class,array(
      'entry_type' => AssignmentType::class,
      'required'=> true,
      'allow_add' => true,
      'by_reference' => false,
      'entry_options' => array('em' => $options['em']) ,// ->>>we modified this line <<<-
    );
  public function configureOptions(OptionsResolver $resolver)
  {
    $resolver->setDefaults([
      'data_class' => 'Empire\UniversityBundle\Entity\Assignments',
      'cascade_validation' => true,
      'error_bubbling' => false,
      'em' => null,
    ]);
    $resolver->setRequired('em');
  }
}

Best regards

0
votes

In symfony 3.3 Doc.

If you need to access services from your form class, add a __construct() method like normal:

class AssignmentsType extends AbstractType
{
    private $em;

    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->em = $entityManager;
    }

...
..
.

If you're using the default services.yml configuration (i.e. services from the Form/ are loaded and autoconfigure is enabled), this will already work!

If you're not using autoconfigure, make sure to tag your service with form.type.