Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dispatch an additional event when creating `FormView` #31419

Open
Steveb-p opened this issue May 8, 2019 · 2 comments

Comments

Projects
None yet
2 participants
@Steveb-p
Copy link

commented May 8, 2019

Description
Dispatch an additional event when creating FormView, so an external EventSubscriber can attach additional vars for consumption in templates.

My use case for dispatching additional event during FormView construction was tracking of original value of a simple field.

I was wondering if it wouldn't be worthwile to introduce an additional event for buildView and/or finishView FormBuilder methods?

Example
In my own code, I was interested in tracking original values for TextType fields when a particular object was being modified to later present them in case a validation error occured.

I have created classes like so:

TextOriginalValueExtension:

<?php

namespace AppBundle\Form\Extensions;

use AppBundle\Events\FormViewEvent;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TextOriginalValueExtension extends AbstractTypeExtension
{

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefault('remember_original_value', false);
        $resolver->setAllowedTypes('remember_original_value', 'bool');
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        if ($options['remember_original_value']) {
            $builder->addEventSubscriber(new OriginalValueSubscriber());
        }
    }

    public function finishView(FormView $view, FormInterface $form, array $options)
    {
        if ($options['remember_original_value']) {
            $config = $form->getConfig();
            $dispatcher = $config->getEventDispatcher();
            $dispatcher->dispatch('form.pre_build_view', new FormViewEvent($form, $view, $form->getData()));
        }
    }

    public static function getExtendedTypes()
    {
        return [
            TextType::class,
        ];
    }
}

OriginalValueSubscriber:

<?php

namespace AppBundle\Form\Extensions;

use AppBundle\Events\FormViewEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;

class OriginalValueSubscriber implements EventSubscriberInterface
{
    private $originalValue;

    public static function getSubscribedEvents()
    {
        return [
            FormEvents::PRE_SET_DATA => 'establishOriginalValue',
            'form.pre_build_view' => 'addOriginalValueToVars',
        ];
    }

    public function establishOriginalValue(FormEvent $event): void
    {
        $form = $event->getForm();
        $config = $form->getConfig();
        if (!$config->getOption('remember_original_value')) {
            return;
        }

        if (!isset($this->originalValue)) {
            $this->originalValue = $event->getData();
        }
    }

    public function addOriginalValueToVars(FormViewEvent $event): void
    {
        $view = $event->getView();
        $view->vars['original_value'] = $this->originalValue;
    }
}

FormViewEvent:

<?php

namespace AppBundle\Events;

use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;

class FormViewEvent extends FormEvent
{
    /**
     * @var FormView
     */
    private $view;

    public function __construct(FormInterface $form, FormView $view, $data)
    {
        parent::__construct($form, $data);
        $this->view = $view;
    }

    /**
     * @return FormView
     */
    public function getView(): FormView
    {
        return $this->view;
    }
}
@xabbuh

This comment has been minimized.

Copy link
Member

commented May 8, 2019

I am not convinced that we really need a new event for that. Your use case looks like it could purely be solved with a form type extension.

@Steveb-p

This comment has been minimized.

Copy link
Author

commented May 8, 2019

@xabbuh in my case I could not make to work inside FormTypeExtension because for multiple fields (I'm using it to track collection data) only a single instance of extension class was created. So extension did not really cut it for me. I'd have to use some kind of map to track fields and their values.

That's why I used EventDispatcher, since I passed a new instance of subscriber for each built field. I was considering if maybe there could be some more uses cases for introducing an event for building FormView specifically, hence the issue / proposition and my use case presentation.

EDIT: For reference, here is my collection form:

<?php

namespace AppBundle\Form\Services;

use Adtredo\models\entities\Service;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class ServicePagesForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('pages', CollectionType::class, [
            'entry_type' => PageSimpleType::class,
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Service::class,
        ]);
    }
}

and each collection entry form:

<?php

namespace AppBundle\Form\Services;

use AppBundle\Entity\Page;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class PageSimpleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name', TextType::class, [
            'remember_original_value' => true,
        ]);
        $builder->add('url', TextType::class, [
            'remember_original_value' => true,
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Page::class,
        ]);
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.