Witajcie. Programuję ostatnio we frameworku symfony i ze względu na sporą przerwę trafiam na różne problemy związane ze białymi plamami wiedzy na temat funkcjonowania pewnych małych trybików, bez których jednak ciężko byłoby ogarnąć działanie całego projektu. Niedawno niemałą zagwozdkę dał mi komponent sfWidgetFormI18nDate, który jest odpowiedzialny za wyświetlanie elementu formularza pozwalającego na wprowadzenie daty, a dokładnie sama część “roku”. Problem niby prosty, ale nadal problem, dlatego zapraszam do lektury. ;]

symfony: klasa sfWidgetFormI18nDate i problem z podawaniem zakresu lat.

Moja konfiguracja tego pola wyglądała następująco:

'birthdate' => new sfWidgetFormI18nDate(array(
    	'culture' => 'pl',
    	'format' => '%day%/%month%/%year%',
    	'years' => range(date('Y'), 1970),
    	)),

Problem polegał na tym, że przy danej konfiguracji walidatora:

'birthdate' => new sfValidatorDate(
	array(
		'required' => true,
		'max' => strtotime('19 years ago'),
		),
	array(
		'required' => 'To pole jest wymagane.',
		'max' => 'Musisz mieć co najmniej 19 lat.',
		'invalid' => 'Niepoprawne dane.',
		)
	),

podczas sprawdzania formularza nie działo się absolutnie… nic. Jakkolwiek nie wybierałbym daty, czy też innych pól formularza, nigdy nie pokazywał się ani komunikat o błędzie, a formularz przechodził walidację bez żadnego problemu.

Przez kilkanaście minut ogarnęła mnie głęboka konsternacja, bo to w końcu nie pierwsza wersja stabilna symfony, a więc błąd frameworka można z całą pewnością wykluczyć. Ten komponent z powodzeniem wykorzystywały tysiące innych osób, więc dlaczego miałoby to nie działać w moim przypadku?

Z pomocą przyszła chłodna analiza przypadku. Jak to mówią: “urządzenia elektryczne najlepiej działają, jak się je podłączy do prądu” - podążając za sensem tego powiedzenia sprawdziłem kod HTML generowany przez formularz. Okazało się, że problem został zlokalizowany, ponieważ wynik przedstawiał się następująco:

(...)
<select name="register[birthdate][year]" id="register_birthdate_year">
<option value="" selected="selected"></option>
<option value="0">2011</option>
<option value="1">2010</option>
<option value="2">2009</option>
<option value="3">2008</option>
<option value="4">2007</option>
(...)
<option value="37">1974</option>
<option value="38">1973</option>
<option value="39">1972</option>
<option value="40">1971</option>
<option value="41">1970</option>
</select>
(...)

Okazało się, że problem polega na tym, że generowane są poprawne napisy po stronie użytkownika, ale identyfikatory danych przekazywane na serwer nie są takie cacy. Zamiast wartości z zakresu 1970-2011 przesyłany jest tylko kolejny numer elementu: 0-41.

Długo się nie namyślając zacząłem szukać rozwiązania. Z pomocą przyszła funkcja array_combine(), która potrafi utworzyć tablicę asocjacyjną na podstawie dwóch tablic zawierających odpowiednio klucze i wartości tablicy wynikowej. Kod, którego potrzebowałem prezentuje się następująco:

array_combine(range(date('Y'), 1970), range(date('Y'), 1970))

Wygląd całej deklaracji pola formularza:

'birthdate' => new sfWidgetFormI18nDate(array(
    'culture' => 'pl',
    'format' => '%day%/%month%/%year%',
    'years' => array_combine(range(date('Y'), 1970), range(date('Y'), 1970)),
    )),

Oczywiście można by nieco ulepszyć działanie tego kodu przez co najmniej zapisanie wyniku funkcji range() do zmiennej i potem przekazanie dwóch jej odczytów do array_combine(), ale jako szybki sposób na załatwienie problemu jest jak znalazł.

Morał z tej historii? Nie próbuj nigdy robić niczego na pamięć. Dokumentacja nie gryzie. ;]