This post comes from the first version of this blog.
Please send me an email if anything needs an update. Thanks!

Realizuję w tym momencie bardzo przyjemny projekt - przyjemny dlatego, że powstaje “od zera” i mam możliwość wyboru narzędzi, w jakich będę go tworzył. Chciałem spróbować wykorzystać do tego testowany od pewnego czasu framework Symfony2, jednak idąc za radą udzieloną mi przez batmana podczas konferencji 4Developers wracam na “stare śmieci”, czyli stabilną wersję symfony. Jak to bywa przy powrotach - zazwyczaj pojawiają się problemy z ponownym “zgraniem się” programisty z kodem. Tym razem padło na mechanizm walidacji formularzy, który nie do końca chciał współpracować.

Problem: sfValidatorAnd i właściwości "required" komponentów wewnętrznych.

Walidacja formularzy w symfony odbywa się poprzez odpowiednie skonfigurowanie listy walidatorów dla każdego pola. Dostępnych jest wiele tzw. "widgetów" opisujących różne typy pól, a także wiele różnych, mniej lub bardziej ogólnych walidatorów testujących konkretne wymagania dotyczące przetwarzanych danych.

Weźmy pod uwagę przykładową konstrukcję walidatora dla pola “login”:

1
2
3
4
5
6
(...)
'login' => new sfValidatorString(
	array('required' => true),
	array('required' => 'This field is required.')
	),
(...)

Jak widać, do konkretnego pola formularza możemy podpiąć tylko jeden walidator, ze względu na fakt, że klucz “login” może mieć tylko jedną wartość. Wynika to z budowy tablic w językach programowania, gdzie określony identyfikator odpowiada konkretnej wartości [pomijam tutaj funkcjonalności takich bibliotek jak STL i jego klasa std::multimap ;]].

Twórcy symfony pozwalają nam jednak na podpięcie kilku walidatorów do jednego pola za pomocą tzw. “walidatorów agregujących” [ok, sam to wymyśliłem] - klas, przyjmujących jako wymagania… inne wymagania. Są to m. in. komplementarne sfValidatorAnd i sfValidatorOr, których zastosowanie powinno być jasne. W tym wpisie weźmiemy pod uwagę pewien problem związany z pierwszą z wymienionych klas - sfValidatorAnd.

Weźmy pod uwagę przykładowy kod:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(...)
'login' => new sfValidatorAnd(array(
	new sfValidatorEmail(
		array(),
		array('invalid' => 'Invalid email address.')
		),
	new sfValidatorString(
		array('required' => true),
		array('required' => 'This field is required.')
		),
	),
),
(...)

Tak skonfigurowany walidator ma za zadanie:

Chciałbym tutaj rozwiać wszelkie wątpliwości - to działa. Problem polega jednak na tym, że ewentualny błąd we wprowadzonych danych powinien się przełożyć bezpośrednio na komunikaty o błędach, także zawarte w kodzie walidatora. O ile komunikat o niepoprawnym adresie email jest "respektowany", o tyle komunikat "required" elementu sfValidatorString jest zupełnie ignorowany, a potencjalny użytkownik zobaczy jedynie napis "Required.".

Jak więc to naprawić?

Rozwiązanie: Powtórzenie właściwości "required" w parametrach sfValidatorAnd.

Konstrukcja walidatora w symfony jest w pewnym sensie ustandaryzowana, dlatego zazwyczaj możemy trzymać się następującego schematu:
1
2
3
4
new sfValidatorCustom(
	array([parametry walidacji]),
	array([parametry komunikatów o błędach])
	);

“Walidatory agregujące”, ze względu na to, że zawierają inne walidatory, wprowadzają tutaj małą zmianę:

1
2
3
4
5
new sfValidatorAggregateCustom(
	array([lista walidatorów]),
	array([parametry walidacji]),
	array([parametry komunikatów o błędach])
	);

Jak zatem sugeruje śródtytuł - dodajmy do naszej konstrukcji “And” komunikat określający wynik jego działania jako całości:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
'login' => new sfValidatorAnd(array(
	new sfValidatorEmail(
		array(),
		array('invalid' => 'Invalid email address.')
		),
	new sfValidatorString(
		array('required' => true),
		array('required' => 'This field is required.')
		),
	),
array('required' => true),
array('required' => 'This field is required.')
),

W tym momencie nasz walidator zaczyna działać jak należy, reagując poprawnie zarówno na fakt braku danych w określonym polu, jak i jego błędnym formacie.