Witajcie. Dzisiaj po raz kolejny zmierzymy się z frameworkiem symfony, a konkretnie z jego „wewnętrznym frameworkiem” obsługującym formularze. Jak już zdążyliście się dowiedzieć z kilku wcześniejszych wpisów, jest on bardzo wygodnym narzędziem, jednak jeśli się o pewnych rzeczach po prostu nie wie, to niestety potrafi być także złośliwy i ogłupia programistę niezrozumiałymi komunikatami. Tak też właśnie jest z walidatorem pola <select> – sfValidatorChoice, który niestety potrafi zmarnować cenny czas programisty wodząc go za nos komunikatem „Invalid”.
Fotografia: m-a-c, CC-BY.
symfony: sfValidatorChoice i ciągły błąd „Invalid.”.
Weźmy pod uwagę prosty przykład:
// (...) $options = array( '0' => '14 days', '1' => '7 days', '2' => '3 days', '3' => '1 day', ); $this->setWidgets(array( // (...) 'option' => new sfWidgetFormSelect(array( 'choices' => $options, 'default' => 0, )), // (...) )); // (...) $this->setValidators(array( // (...) 'option' => new sfValidatorChoice( array( 'choices' => $options ), ), // (...) ));
Wydawałoby się, że wszystko jest ok, w końcu podaliśmy do konfiguracji pola opcje w relacjach id => nazwa elementu, a także te same dane do walidatora.
Co jednak otrzymamy? Otóż, niezależnie od tego, co wybierzemy z naszej listy w polu <select>, walidator zawsze zwróci komunikat „Invalid.” – oczywiście może być to dowolna inna, ustawiona przez nas wiadomość, co nie zmienia faktu, że formularz nie ma szans przejść walidacji.
Wydawałoby się, że jeśli sam framework nie prostestuje w kwestii otrzymanej konfiguracji, to wszystko powinno bez problemu się uruchomić i zadziałać według naszych oczekiwań. Nic bardziej błędnego – okazuje się, że dane przekazywane do walidatora są nieco inne niż te, których oczekuje pole formularza. Walidator oczekuje samych „kluczy” z tablicy opcji przekazanych do pola. Należy zatem potraktować rzeczoną tablicę funkcją array_keys() i problem zostaje magicznie rozwiązany:
$this->setValidators(array( // (...) 'option' => new sfValidatorChoice( array( 'choices' => array_keys($options), ), ), // (...) ));
Podsumowanie.
Z jednej strony twórcy frameworka podjęli dobrą decyzję o takiej, a nie innej implementacji tego walidatora. W końcu nie musi on mieć informacji na temat tego, co się będzie wyświetlało – jego interesują tylko i wyłącznie dane wynikowe przekazane do skryptu obsługującego formularz.
Z drugiej strony jednak uważam, że jest to pewna niekonsekwencja, ponieważ przekazanie tych samych danych do pola formularza i walidatora ma sens „logiczny” – jednemu bytowi mówimy: „idź, wyświetl mi ten zbiór danych”, a drugiemu: „słuchaj, sprawdź, czy ten zbiór danych jest poprawny”. Poza tym, w przypadku programisty, który pierwszy raz styka się z tym problemem jest to o wiele bardziej zrozumiałe i przewidywalne, a więc jeden potencjalny problem mniej. Inną sprawą jest to, że posiadając gotową tablicę, możemy ją po prostu wykorzystać w innym miejscu, a wywołanie array_keys() to zawsze trochę czasu i pamięci „do tyłu”. Oczywiście nie namawiam nikogo do tego typu mikrooptymalizacji, aczkolwiek strata kilku mikrosekund bez wyraźnego celu nie będzie nigdy zbyt szczęśliwym rozwiązaniem.
Dziękuję za wspólnie spędzony czas i zapraszam do dyskusji w komentarzach.
Warto przeczytać.
Trwa ładowanie…
Witam
Trochę dziwnie deklarujesz zmienną $options bo powinno być jak już zdeklarowane jako zmienna prywatna, poza tym po co sam indeksujesz tą tablicę ?
wystarczy że zrobisz tak :
private $c = array(’14 days’,7 days’, ’3 days’,’1 day’);
i wtedy validator deklarujesz :
new sfWidgetFormChoice(array(‘choices’=>$this->c));
i reszta tak samo czyli ten sfWidgetFormSelect
przynajmniej u mnie to działa bdb
Ok, faktycznie, za bardzo uprościłem ten przykład. Problem polega na tym, że nie zawsze te identyfikatory są kolejnymi liczbami, a w szczególności nie rozpoczynają się od zera. Weź pod uwagę, że zmienna $options, to może być np. wynik działania metody zwracającej dane z bazy danych i całość wygląda następująco:
Tutaj deklaruję całą tablicę ręcznie, żeby pokazać, jak wygląda struktura danych, na jakiej będziemy operować. Opisany przypadek jest szczególnym, kiedy to zadziała bez problemu w obu przypadkach, także dziękuję za wypunktowanie.
Nie rozumiem jednak, co masz na myśli mówiąc „powinno być jak już zdeklarowane jako zmienna prywatna” – cały kod obu listingów zawiera się już w metodzie configure() formularza, a dokładnie:
<?php class CustomForm extends BaseForm { public function configure() { // Tutaj;] } }Nie widzę sensu deklarowania tej zmiennej jako atrybutu klasy, ponieważ jest ona potrzebna tylko lokalnie, nie używam jej pomiędzy metodami. Jeśli mógłbyś wskazać przewagę swojego rozwiązania nad moim, byłbym wdzięczny.
Przy okazji – gratuluję, zostawiłeś 1500 komentarz na moim blogu. ;]
symfony: sfValidatorChoice i ciągły błąd „Invalid.”.