Symfony2 Container Aware Form Type

2019-06-23 16:29发布

问题:

Is there a way to make a form type container aware?

As an example I have 3 entities Account, User and Event. Users have ManyToMany relationship in order to associate many users with many other users (called approvers), reason for this is so that an Event created by a User can have a list of Users who area able approve it. In the User edit form where I have an approvers multiple select field the list needs to be filtered by Account so I need my form type to be container aware in order to filter the list of available Users by Account ID.

Am I right in thinking that making the form type container aware is the right way to go? I'd like to use the entity manager to filter a list of Users by Account.

回答1:

1 Inject the entity manager through the constructor

<?php

namespace Acme\YourBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityManager;

class YourType extends AbstractType
{

    /**
     * The entity manager
     *
     * @var EntityManager
     */
    private $entityManager;

    /**
     * @param EntityManager
     */
    public function __construct(EntityManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
       //build your form here



    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\YourBundle\Entity\YourEntity',
        ));
    }

    public function getName()
    {
        return 'name_of_your_form';
    }

}

2 Declare it as a service

services:
    # Form type
    acme_your_bundle.name_of_your_form.form.type:
        class: Acme\YourBundle\Form\Type\YourType
        arguments:
            entityManager: "@doctrine.orm.entity_manager"

Note:

If you're starting with Symfony, take this advice:

look very closely at the code of the FOSMessageBundle, it will give you exactly what you need to do anything in symfony, from form models, to form factories, to the creation of special services (like composer, authorizer, etc..). The more you study this bundle, the quicker you will learn symfony, I guarantee you that 100%. Finally, in your specific case, look at the FormFactory in this bundle



回答2:

This solution allows you to inject the container into many forms without each of your form types being a service:

Create a new form type:

class ContainerAwareType extends AbstractType implements ContainerAwareInterface
{

    protected $container;

    public function setContainer(ContainerInterface $container = null) {
        $this->container = $container;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults(array(
            'container' => $this->container
        ));
    }

    public function getName() {
        return 'container_aware';
    }

    public function getParent() {
        return 'form';
    }
}

Declare as a service:

services:
    app.container_aware_type:
        class: Burgerfuel\CmsBundle\Form\Type\ContainerAwareType
        calls:
          - [setContainer, ['@service_container']]
        tags:
            - { name: form.type, alias: 'container_aware' }

This type is now available to be a 'parent' to any other form type - whether its a service or not. In this case the important part is that setDefaultOptions from this class will be used to help build the $options argument that will be passed into any 'child' form types.

In any of your form types you can do this:

class MyType extends AbstractType
{
    public function getParent() {
        return 'container_aware';
    }

    public function buildForm(FormBuilderInterface $builder, array $options) {
        $container = $options['container'];
        $builder->add( ...

This solution will be beneficial if you can't make your form type a service for some reason. Or it can save you time if you are creating many types that require access to the container.



回答3:

A simple way to do this, without doing any dependency injection / declaring a service.

In your FormType file, force the form to require an EntityManager

//..    
use Doctrine\ORM\EntityManager;

class YourFormType extends AbstractType
{
    //...
    //...
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Acme\YourBundle\Entity\YourEntity',
        ));
        $resolver->setRequired('entity_manager');
        $resolver->setAllowedTypes('entity_manager', EntityManager::class);
    }
}

Then you'll be able to (and forced - important for testing), pass the entity manager from the controller.

public function yourControllerAction(Request $request)
{
    //..
    $em = $this->getDoctrine()->getManager();
    $form = $this->createForm('Acme\YourBundle\Form\YourEntityType', $yourEntityObject, array(
                'entity_manager'=>$em,
            )); 
    $form->handleRequest($request);
    //..
}