- możliwie jak najprostszy, spójny i elastyczny sposób przetwarzania formularza, aby ewentualne dodanie nowego pola lub całego formularza kosztowało jak najmniej nakładu pracy
- napisanie kodu, który będzie można również wykorzystać w przyszłości w innym projekcie
Istnieją dwa główne mechanizmy przechowywania danych z formularzy z poprzednich stron / kroków. Na pierwszy rzut oka zaprzęgnięcie sesji w tym celu wydaje się dobrym rozwiązaniem, jednakże czy tak w rzeczywistości jest? Sesja może wygasnąć podczas wypełniania długiego formularza lub też formularz może nie zostać w pełni wypełniony, co skutkuje przechowywaniem śmieci w sesji (rzutuje to również na wydajność). Oczywiście, można napisać coś w rodzaju garbage collection aby rozwiązać ten drugi problem, ale jest też inne rozwiązanie - przekazywanie danych z poprzednich kroków w ukrytych polach formularza. To również nie jest doskonałe, ale w mojej ocenie przysparza mniej problemów niż sesja.
Jeśli wiemy w jaki sposób przekazywać dane między żądaniami, zobaczmy jak to by wyglądało w praktyce.
(pseudokod)
[PHP]
if(/*wysłano formularz*/) { $page = /*strona obecnego formularza, załóżmy że numerowanie zaczyna się od 0 */; $values = /*dane wysłane postem*/; $fail = null;//pierwszy formularz, który nie przeszedł walidacji //waliduj wszystkie formularze do ostatnio wysłanego for($i=0; $i<$page; $i++) { if(!$forms[$i]->isValid($values)) { $fail = $forms[$i]; break; } } //wystąpił błąd if($fail) { //wyświetl $i-ty formularz z komunikatami błędów //oraz poprawnie zweryfikowane formularze jako ukryte pola } else { if(/* wysłano i zweryfikowano formularze ze wszystkich stron */) { //zapisanie danych do bazy danych i przekierowanie } } } if(!$fail) { //wyświetl obecny formularz echo $forms[$page]; for($i=0; $i<$page; $i++) { //wyświetl poprzednie formularz jako ukryte pola formularzy } }
W powyższym rozwiązaniu cała logika obsługi wielostronicowego formularza jest skumulowana w jednym miejscu. To znacznie lepsze rozwiązanie niż obróbka formularzy z kolejnych kroków w innej akcji kontrolera. Dzięki powyższemu rozwiązaniu dodawanie kolejnych formularzy nie stanowi problemu, należy jedynie do tablicy $forms dodać kolejny obiekt formularza (lub tablicę reprezentującą formularz?) na odpowiednim miejscu oraz ewentualnie zmienić kod wykonywany po weryfikacji wszystkich formularzy.
Pierwszy punkt z listy przedstawionej na początku artykułu został spełniony: sposób przetwarzania jest dosyć prosty, spójny i elastyczny. Pozostaje problem przenośności kodu.
Plugin do obsługi wielostronicowych formularzy dla symfony
Swego czasu napisałem plugin do obsługi wielostronicowych formularzy dla frameworku symfony. Jego główne zadania to rozdzielanie wartości parametrów, walidacja poszczególnych formularzy oraz ukrycie przed programistą problemu przenoszenia danych między żądaniami.
Tworzenie własnego wielostronicowego formularza.
[PHP]
class Form extends psPageableForm { public function setup() { $this->addForm(new Form1()); $this->addForm(new Form2()); $this->addForm(new Form3()); $this->setNameFormat('form[%s]'); } }
Jednym ze sposobów stworzenia wielostronicowego formularza jest nadpisanie klasy psPageableForm i w metodzie setup (lub configure) dodanie kolejnych formularzy.
Przetwarzanie formularza tego typu będzie zbliżone do tego zaprezentowanego na pierwszym listingu - algorytm jest niemalże ten sam, z tą różnicą, że część zadań wykonują klasy pluginu.
[PHP]
//kontroler public function executeProcessForm(sfWebRequest $request) { $page = (int) $request->getParameter('step', 1); $form = $request->hasAttribute('form') ? $request->getAttribute('form') : new Form(); $form->setPage($page); if($request->isMethod('post')) { //jeśli ta metoda została wywołana po błędzie walidacji poprzedniego formularza //nie przeprowadzaj walidacji if($request->getAttribute('return') != 1){ $form->bind($request->getParameter('form')); if(!$form->isValid()){ //numer strony to numer pierwszego niepoprawnego formularza $page = $form->getFirstInvalidForm()->getOption('page'); $request->setParameter('step', $page); //utawić atrybuty żądania, aby przekazać zwalidowany formularz $request->setAttribute('return', 1); $request->setAttribute('form', $form); //wywołanie rekurencyjne akcji aby wyświetlić formularz //który nie przeszedł walidacji return $this->executeProcessForm($request); //zweryfikowano ostatni formularz }elseif($page > $form->getPages()){ //zapisanie danych do bazy $this->redirect(/*przekierowanie*/); } } } else { $form->setPage(1); } $this->form = $form; } //widok echo $form->getCurrentForm(); //wyświetlenie ukrytych pól formularza echo $form->persist();
Plugin ma zaimplementowane dwie strategie przekazywania danych między żądaniami: poprzez ukryte pola formularzy oraz sesja.
Można pójść jeszcze dalej z realizacją ponownego wykorzystania kodu i napisać odpowiednią klasę (np. ProcessPageableForm), która będzie w ogólny sposób hermetyzować przetwarzanie wielostronnicowego formularza (czyli wyżej przedstawiony kod kontrolera). W tym miejscu jednak zostawiam wolność w ewentualnej implementacji tego rozwiązania ;)
Kod źródłowy pluginu: klik

Komentarze (3)
#1 Sajam, 25.01.2010 21:22
Ja użyłem nieco innego sposobu. Czy bardziej elastyczny? Nie wiem...
Tworzę wpis w bazie danych z oznaczeniem krok=1, ostatnią datą modyfikacji, kluczem i kolejno uzupełniam pola. Przy pokazaniu formularza usuwam stare wpisy (niedokończone wypełnienie etc.). W cookie i bazie danych zapisuję sobie jakiś wygenerowany klucz. Przy wejściu w któryś formularz np. register/step_3 wyciągam z bazy krok przy którym jestem na podstawie klucza z cookie. Jeżeli zostanie wypełniony poprawnie znowu aktualizuję baze danych i zwiększam "krok". Jeżeli zakończyliśmy proces czyszczę pole "krok" i na tej podstawie identyfikuje późniejsze prawidłowe rekordy.
#2 piotrooo89 (www) , 25.01.2010 22:21
Tylko czy zaprzęganie db do tego jest dobrym rozwiązaniem, co jak potrzeba zrobić coś "lekkiego"? Rozwiązanie które podałeś jest fajne bo można cuda robić jeśli użytkownikowi zamknie się przeglądarka etc no ale jak wspomniałem wcześniej nie jest to 'lekkie' rozwiązanie.
#3 Sajam, 26.01.2010 10:25
Że to rozwiązanie jest "Ciężkie"? Dane i tak musisz zapisać w jakimś źródle danych, a że robię to bezpośrednio to chyba nie jest ciężkie rozwiązanie. No i łatwiej kodem zarządzać, nie trzeba się martwić o pre-definiowane pola, errory i ukryte przesyłanie danych. Tu wystarczy tylko klucz w cookie.