Walidacja formularzy w Symfony – 3 sprawdzone sposoby

Oto druga część z serii jak okiełznać formularze. W pierwszej mogłeś się dowiedzieć jak je tworzyć. Teraz zajmiemy się walidacją danych. Formularze to bardzo newralgiczne miejsca systemu. Niezabezpieczone mogą spowodować wiele problemów, ponieważ niepełne lub po prostu śmieciowe dane mogą wywalić system i spowodować wielki fuck up. Dlatego bardzo ważna jest walidacja poprawności danych, zarówno na poziomie formularza jako na pierwszej linii frontu, jak i na poziomie późniejszego modelu i encji. Walidacja formularzy w Symfony to bardzo prosta sprawa. Pokażę Ci moje trzy sprawdzone sposoby.

Walidacja formularzy w Symfony

Symfony udostępnia nam wiele możliwości, aby sprawdzić przeróżne zależności i wymagania względem wprowadzanych danych. Zaczynając od najprostszych przypadków, czyli typów prostych, przez zależności między elementami, aż po walidację całościową lub bardziej skomplikowanych złożonych elementów. Pokażę Ci jak zabezpieczyć formularze przed niepoprawnymi danymi. Poniżej przedstawione asercje są ustawione na modelu formowym lub ew. na encji, która jest zdefiniowana jako data_class.

Asercje

Gdy zależy nam na podstawowej walidacji formularzy w Symfony t.j. typ danych, ich wymagalność, długość, wartość, przedział wartości w przypadku liczb itd.  wykorzystasz asercje. Wystarczy, że zdefiniujesz je tak jak na poniższym przykładzie. Symfony daje nam całkiem sporą listę takich wbudowanych walidatorów nie tylko do bazowej walidacji, ale są także całkiem sensowne bardziej wyspecjalizowane t.j. Iban, Email, Regex czy Uuid.

use Symfony\Component\Validator\Constraints as Assert;

 class EventModel
 {
     /**
      * @Assert\NotBlank()
      */
     public $type;

     /**
      * @Assert\NotBlank()
      * @Assert\Type("\DateTime")
      */
     protected $startDate;
}

Pojedyncze walidatory możemy tworzyć również sami, ale to jest temat na osobny wpis, który pojawi się niebawem. Przedstawię Ci krok po kroku jak stworzyć, skonfigurować i wywołać swój własny customowy walidator.

Grupy walidacyjne

W przypadku gdy np. wymagalność pól zależy od konfiguracji innych zawartych w tym formularzu, same asercje nam już nie pomogą, ponieważ asercja w formie, jaką przedstawiłam powyżej, wywoła się zawsze przy weryfikacji poprawności danych. W przypadku gdy chcemy uruchomić daną asercję w zależności czy inne pole przybierze daną wartość, musisz użyć grup walidacyjnych tak jak w kodzie poniżej.

use Symfony\Component\Validator\Constraints as Assert;

 class EventModel
 {
     /**
      * @Assert\NotBlank()
      */
     public $type;

     /**
      * @Assert\NotBlank()
      * @Assert\Type("\DateTime")
      */
     protected $startDate;
      
      /**
      * @Assert\NotBlank()
      * @var boolean
      */
      protected $isCyclic;

      /**
      * @Assert\NotBlank(groups={"Cyclic"})
      */
      protected $cyclicCode;
      
      /** 
      * @param FormInterface $form 
      * @return array
      */
      public static function getValidationGroups(FormInterface $form)
      {
         $groups =  ['Default];
         
         if ($form->getData()->isCyclic) {
            $groups[] = 'Cyclic';
         }

         return $groups;
      }
}

Jak możesz zauważyć, deklarując asercje NotBlank na polu cyclicCode, ustawiłam parametr groups. Teraz potrzebujesz dopisać jeszcze jedną funkcję getValidationGroups, która zawiera logikę zależności między grupami. Bez tej funkcji asercja ze zdefiniowana grupą nigdy by się nie wykonała. W powyższym przykładzie zdefiniowałam, że jeśli w formularzu nie zostanie określone, że wydarzenie jest cykliczne, to cyclicCode nie będzie wymagany.

Aby zadziałała konfiguracja grup walidacyjnych, musimy jeszcze poinformować formularz, że ma to sprawdzić. można zrobić to w ten sposób:

/**
 * @param OptionsResolver $resolver
 */
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(
        [
            'data_class' => EventModel::class,
            'validation_groups' => [
                EventModel::class,
                'getValidationGroups',
            ],
        ]
    );
}

Callback

Ostatnim, w pewnym sensie najszerszym sposobem  jaki daje nam walidacja formularzy w Symfony jest Callback. Mogłoby się wydawać, że grupy walidacyjne załatwiają nam wszystkie możliwości. Niestety tak nie jest.

Dla przykładu, gdy np. mamy kolekcje, której elementami są obiekty Money i w ramach tej kolekcji nie mogą pojawić się dwie kwoty w tej samej walucie, zaczynają się schody. Są dwie opcje, pierwsza to stworzyć własny validator. Całkiem dobry pomysł, ale polecam wybrać tę opcję, gdy wiesz, że przyda Ci się on w kilku miejscach. Druga zastosować właśnie wspomniany callback.

use Symfony\Component\Validator\Constraints as Assert;

/**
 * @Assert\Callback({"EventBundle\Form\Validation\EventFormValidation", "validate"})
 */
 class EventModel
 {
     /**
      * @Assert\NotBlank()
      */
     public $type;

     /**
      * @Assert\NotBlank()
      * @Assert\Type("\DateTime")
      */
     protected $startDate;
      
      /**
      * @Assert\NotBlank()
      * @var boolean
      */
      protected $isCyclic;

      /**
      * @Assert\NotBlank(groups={"Cyclic"})
      */
      protected $cyclicCode;
      
      /** 
      * @param FormInterface $form 
      * @return array
      */
      public static function getValidationGroups(FormInterface $form)
      {
         $groups =  ['Default];
         
         if ($form->getData()->isCyclic) {
            $groups[] = 'Cyclic';
         }

         return $groups;
      }
}

Callback to także jeden z constraintów dostarczanych przez Symfony. Bardzo przydany przy różnych bardziej skomplikowanych walidacjach. Definiuje się go tak jak na powyższym przykładzie, wystarczy, że dodasz adnotacje z asercją dla całej klasy modelu. Kolejny krok to stworzenie zdeklarowanej klasy  EventBundle\Form\Validation\EventFormValidation.php oraz metody statycznej validate:

use Symfony\Component\Validator\Context\ExecutionContextInterface;

class EventFormValidation
{
     public static function validate(EventModel $dto, ExecutionContextInterface $context) 
     {
          //walidacja całego obiektu EventModel
     }
}

Funkcja przyjmuje obiekt, na który została założona walidacja oraz obiekt kontekstu potrzeby do tego, aby wyrzucić komunikat o błędzie walidacji. Jeśli już wyłapiesz błąd danych w przekazanym modelu, możesz wyrzucić błąd w ten sposób:

foreach ($dto->collection as $i => $value) {
    if (in_array($value->currency, $currencies)) {
        $context->buildViolation('Value with that currency already exist.')
            ->atPath('collection[' . $i . ']')
            ->setTranslationDomain('validation')
            ->addViolation();
    }
}

Mam nadzieję, że ten tekst będzie dla Ciebie pomocny. Jeśli masz jakieś pytania albo doświadczenia miłe i te mniej miłe z walidacją lub formularzami w Symfony pisz śmiało w komentarzu. To nie są wszystkie sposoby jakie udostępnia nam walidacja formularzy w Symfony. Jeśli zainteresował Cię ten temat już niedługo pojawi się kolejny post w tym temacie lub poczytaj tutaj.

Podobne posty

Jestem programistką, która lubi mieć ręce pełne roboty. Do życia potrzebuje komputera z internetem i kubka gorącej kawy. Więcej na stronie o mnie.

Comments

ZOSTAW ODPOWIEDŹ

Please enter your comment!
Please enter your name here