简体   繁体   中英

Symfony2 (>= 2.3): How to listen to parent form event from child?

I have a custom FormType, which needs to add itself to the parent Entity when the parent Form persists.

In Symfony < 2.3 this could be done by doing the following:

class FooType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        parent::buildForm($builder, $options);
        ...
        $builder->getParent()->addEventSubscriber(new FooSubscriber)
    }
}

class FooSubscriber implements EventSubscriberInterface
{
    static function getSubscribedEvents()
    {
        return array(
            FormEvents::POST_SUBMIT => 'postSubmit'
        );
    }
}

But after upgrading to Symfony 2.6 I've discovered that $builder->getParent() has been removed. But now I can't listen to the parent being submitted.

So I added the listener to my builder and referenced the parent from within the Subscriber. But this doesn't really work, since I do a check on the parent form being valid - which it isn't, since it's not submitted yet:

function postSubmit(FormEvent $e)
{
    if ($e->getForm()->getParent()->getRoot()->isValid()) {
        //this gives 'false'

This false is caused by the next piece of code:

// Symfony\Component\Form\Form.php @ line 744
public function isValid()
{
    if (!$this->submitted) {
        return false;
    }

And because the parent form first loops through all the childs and submits that, before setting $this->submitted = true on itself... I'm not sure if the parent is valid.


TL;DR

How can I add an Eventlistener to my parent Form, without having to adjust my parent Form? I want my FooType be something I can add to all forms, without having to know/remember to do some logic for that FooType specific.

I needed the same functionality because I have a custom form field that needs the parent entity after all mapped fields have been updated. Unfortunately the POST_SUBMIT of child forms is called before SUBMIT on the parent is run.

I ended up passing the eventDispatcher to the child, and tying my listener there. I needed two listeners to get the job done: one to get the processed value, and one to update the main entity. passing $generatedPassword to the closure by reference allows you to share data from the child event to the parent.

Parent::buildForm

$builder->add('generate_password', GeneratePasswordType::class, [
    'event_dispatcher' => $builder->getEventDispatcher(),
]);

Child::buildForm

//first listed to submit even to get current field value
$generateNewPassword = false;
$builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) use (&generateNewPassword) {
    $generateNewPassword = null !== $event->getData();
});

//then run updater after parent entity has been updated
$parentDispatcher = $options['event_dispatcher'];
$parentDispatcher->addListener(FormEvents::POST_SUBMIT, function (FormEvent $event) use (&$generateNewPassword) {
    $user = $event->getData();
    if(true === $generateNewPassword){
        //update password & email user new credentials
    }
}

(The custom field is a checkbox marked 'generate new password on save' for a user management module. It emails the user the generated password, which is why I need the latest email address from the main entity)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM