在 Symfony 3/4 中修改表单的分离方式

Decoupled way of modifying forms in Symfony 3/4

我希望能够使用 Symfony Event Dispatcher 更改表单。 Symfony 文档 talks about 动态形式修改,但是,在所有示例中,事件侦听器或订阅者都是以 class 形式创建的。我想让这个逻辑与我的表单分离 class.

如何修改 Symfony 表单而不必在表单中指定要调用的事件侦听器 class?

可能您需要的是 form type extension,它允许您修改整个系统中任何现有的表单类型。在那里,您可以添加事件 listeners/subscribers 或您想要的任何特定或通用表单类型。

但是,如果这是一个非常频繁的情况,这个任务往往会变得乏味。所以做这样的事情可以为你提供一个完美的契合:

class FoobarFormSubscriber implements EventSubscriberInterface, FormEventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return array(FormEvents::PRE_SET_DATA => 'preSetData');
    }

    public static function getFormClass()
    {
        return FoobarType::class;
    }

    public function preSetData(FormEvent $event)
    {
        $form = $event->getForm();
        $form->add('custom', null, array('mapped' => false));
    }
}

但显然这不是 Symfony 实现的功能。在这里,我留给你一个实现它的方法:

  • 首先,create a new form type extension根据配置将订阅者添加到表单生成器:

    class FormEventTypeExtension extends AbstractTypeExtension
    {
        private $subscribers;
    
        public function __construct(array $subscribers = array())
        {
            $this->subscribers = $subscribers;
        }
    
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $formClass = get_class($builder->getType()->getInnerType());
    
            if (isset($this->subscribers[$formClass])) {
                foreach ($this->subscribers[$formClass] as $subscriber) {
                    $builder->addEventSubscriber($subscriber);
                }
            }
        }
    
        public function getExtendedType()
        {
            return FormType::class;
        }
    }
    
  • 新建接口配置表单class监听:

    interface FormEventSubscriberInterface
    {
        public static function getFormClass();
    }
    
  • 最后,进入一个new compiler pass,注入扩展服务所有注册的kernel.event_subscriber实现了之前的接口:

    public function process(ContainerBuilder $container)
    {
        $subscribers = array();
        foreach ($container->findTaggedServiceIds('kernel.event_subscriber') as $serviceId => $tags) {
            $subscriberClass = $container->getDefinition($serviceId)->getClass();
    
            if (is_subclass_of($subscriberClass, FormEventSubscriberInterface::class, true)) {
                $subscribers[$subscriberClass::getFormClass()][] = new Reference($serviceId);
            }
        }
    
        $extensionDef = $container->getDefinition(FormEventTypeExtension::class);
        $extensionDef->setArgument(0, $subscribers);
    }
    

然后,您的自定义订阅者将解耦并准备好按原样工作,只需确保实现这两个接口 (EventSubscriberInterface, FormEventSubscriberInterface) 并将事件订阅者注册为服务。