Alternative to $form->isValid() for use in a Symfony DataMapper?

236 views Asked by At

I am building a Symfony data transformer class called EventDataMapper. It handles two fields: A TextType field called My mapDataToForms() definition looks like this:

public function mapDataToForms($data, $forms)
{
    $existingTitle = $data->getTitle();
    $existingAttendees = $data->getAttendees();
    $this->propertyPathMapper->mapDataToForms($data, $forms);
    foreach ($forms as $index => $form) {
        if ($form->getName() === 'title' && !is_null($existingTitle)) {
            $form->setData($existingTitle);
        }
        if ($form->getName() === 'attendees' && !is_null($existingAttendees)) {
            $form->setData($existingAttendees);
        }
    }
}

The problem is that I'm setting data before validation runs, so if I submit a form with a non-numeric string in the "attendees" field, I get an ugly TransformationFailedException ('Unable to transform value for property path "attendees": Expected a numeric'). And if I try to do a check for whether my field is valid by adding a call to $form->isValid() in the line before I call $form->setData(), I get a LogicException. ('Cannot check if an unsubmitted form is valid. Call Form::isSubmitted() before Form::isValid().')

Is there any way for my to preemptively call a validator on this specific field from within my DataMapper?

(Yes, this can be somewhat prevented with frontend logic. But I don't want to rely too much on that.)

1

There are 1 answers

0
Mayor of the Plattenbaus On

Closing the loop on this. Here's what we did.

A colleague made a new form type corresponding to a new adapter class that wraps our two previous classes, providing a uniform set of wrapper methods for interacting with them.

We passed Symfony's validator service into our new form type using the constructor.

In that form type, we're using $builder->addEventListener() to add a callback/listener on the POST_SUBMIT event. Here's the callback:

        function(FormEvent $event): void {
            $adapter = $event->getData();
            $form = $event->getForm();
            $errors = $adapter->propagate($this->validator);

            foreach ($errors as $error) {
                $formError = new FormError($error->getMessage());
                $targetPath = self::mapPropertyPath($error->getPropertyPath());
                $target = $targetPath !== null ? $form->get($targetPath) : $form;
                $target->addError($formError);
            }
        }

The adapter, in turn, has some logic that does various translations of data into a form that can be used in our legacy classes, followed by this:

return $validator->validate($this->legacyObject);

This works well for us. I hope it helps somebody else out too.