Nowa, długo oczekiwana, wersja Zend Frameworka oznaczona numerem 2, rodzi się w bólach. Aż cztery wersje beta były potrzebne, byśmy mogli w końcu zobaczyć jak mniej-więcej będzie wyglądało tworzenie aplikacji w oparciu o ten framework. Jako pierwszy komponent na warsztat wziąłem Zend Form, który przysparzał programistom masę problemów. Począwszy od dekoratorów, na które klęli chyba wszyscy, przez walidację i filtrowanie danych, na zupełnym oderwaniu od wszelkich wzorców kończąc.

Nowy Zend Form na pierwszy rzut oka wydaje się być dobrze przemyślanym komponentem, jednak kilka drobiazgów potrafi napsuć krwi, zwłaszcza rozdrobnienie kodu po niezliczonej ilości plikach. W ZF1 mieliśmy po prostu klasę dziedziczącą po Zend_Form, która zajmowała się wszystkim. Na upartego można było dodać własną klasę pośrednią, która robiła porządek z dekoratorami oraz ewentualnie view script. ZF2 wprowadził małą rewolucję, która wbrew pozorom znacząco ułatwi pracę z formularzami oraz danymi pochodzącymi od użytkownika w ogóle.

Jak już wspomniałem, nowy Zend Form jest mocno rozdrobniony. Co to właściwie oznacza? Ni mniej, ni więcej jak konieczność napisania kilku metod oraz utworzenie kilku plików, tylko po to, byśmy mogli cieszyć się formularzem na stronie. Początkowo takie podejście mocno mi nie odpowiadało, jednak z czasem dostrzegłem jego zalety, zwłaszcza przy założeniu pełnej modułowości ZF2.

Zacznijmy od utworzenia formularza. Do przygotowania formularza zdecydowałem się na klasę modelu, której jedna z metod zwraca formularz. Należy tutaj wspomnieć, iż formularz to nie jest gotowy kod HTML, tylko klasa pośrednicząca między danymi, a widokiem.

namespace AuthModel;

use ZendFormForm;
use ZendFormElement;
use ZendInputFilterInputFilter;
use ZendInputFilterInput;
use ZendValidator;

class Sign
{
    public function getForm()
    {
        $login = new Element('login');
        $login->setAttributes(
            array(
                'type' => 'text',
                'label' => 'Login'
            )
        );

        $password = new Element('password');
        $password->setAttributes(
            array(
                'type' => 'password',
                'label' => 'Hasło'
            )
        );

        $submit = new Element('btn_signin');
        $submit->setAttributes(
            array(
                'type' => 'submit',
                'value' => 'Zaloguj'
            )
        );

        $form = new Form('signin_form');
        $form->add($login);
        $form->add($password);
        $form->add($submit);

        return $form;
    }
}

Nie będę wyjaśniał poszczególnych metod, ponieważ ich opis znajdziecie w dokumentacji. Powyższy formularz jest typowym formularzem logowania i składa się z pola do wpisania loginu i hasła oraz przycisku wysyłającego formularz. Na pewno zwróciliście uwagę, że formularz nie posiada zdefiniowanych walidatorów oraz filtrów. Od wersji ZF2 formularz nie wie co to jest walidator i filtr. Zajmuje się tym zupełnie inne klasa o nazwie InputFilter. Dzięki tej klasie możemy zdefiniować reguły walidacji oraz filtrowania danych niezależnie od ich pochodzenia. Oznacza to, że reguły, które zastosowalibyśmy w przypadku formularza, będą mogły być wykorzystane w przypadku dowolnego źródła danych (bez konieczności duplikowania kodu), np. usługi sieciowej czy wiersza poleceń. Dodajmy więc reguły walidatora. Zrobimy to w kolejnej metodzie, a następnie przekażemy je do formularza. Ostatecznie klasa modelu będzie wyglądała następująco.

namespace AuthModel;

use ZendFormForm;
use ZendFormElement;
use ZendInputFilterInputFilter;
use ZendInputFilterInput;
use ZendValidator;

class Sign
{
    public function getForm()
    {
        $login = new Element('login');
        $login->setAttributes(
            array(
                'type' => 'text',
                'label' => 'Login'
            )
        );

        $password = new Element('password');
        $password->setAttributes(
            array(
                'type' => 'password',
                'label' => 'Hasło'
            )
        );

        $submit = new Element('btn_signin');
        $submit->setAttributes(
            array(
                'type' => 'submit',
                'value' => 'Zaloguj'
            )
        );

        $form = new Form('signin_form');
        $form->add($login);
        $form->add($password);
        $form->add($submit);

        $form->setInputFilter($this->getInputFilter());

        return $form;
    }

    public function getInputFilter()
    {
        $login = new Input('login');
        $login->getValidatorChain()
              ->addValidator(new ValidatorNotEmpty());

        $password = new Input('password');
        $password->getValidatorChain()
                 ->addValidator(new ValidatorNotEmpty());

        $inputFilter = new InputFilter();
        $inputFilter->add($login);
        $inputFilter->add($password);

        return $inputFilter;
    }
}

Jak widać na załączonym “obrazku”, dodawanie walidatorów nie różni się znacząco od ZF1. Podobnie rzecz ma się z filtrami. Poinformowanie formularza, że ma korzystać ze zdefiniowanych reguł odbywa się poprzez metodę setInputFilter.

Najwyższa pora wypróbować przygotowany formularz. Najpierw kontroler, w którym utworzymy obiekt oraz przekażemy go do widoku.

namespace AuthController;

use ZendMvcControllerActionController;
use ZendViewModelViewModel;

class SignController extends ActionController
{
    public function inAction()
    {
        $model = new AuthModelSign();
        $form = $model->getForm();
        $request = $this->getRequest();

        if($request->isPost()) {
            $data = $request->post();
            $form->setData($data);
            if($form->isValid()) {
                // obsluga formularza
            }
        }

        return new ViewModel(
            array('form' => $form)
        );
    }
}

Zasadniczą różnicą jest tutaj ViewModel, o którym napiszę przy okazji dokładniejszego opisywania ZF2. Na chwilę obecną wystarczy wiedzieć, iż jest to klasa pośrednicząca w wymianie danych między modelem i widokiem. Reszta odbywa się prawie tak samo jak w przypadku ZF1, czyli sprawdzamy, czy zostały wysłane dane POST, przekazujemy te dane do formularza (zamiast populate mamy metodę setData), a następnie sprawdzamy, czy przekazane dane są poprawne.

Na koniec został widok, który mocno różni się od tego, co znamy z ZF1. W poprzedniej wersji frameworka wystarczyło “wyechować” formularz. Teraz musimy sami zatroszczyć się o proces renderowania formularza. Oczywiście robimy to w widoku.

<?php
$form = $this->form;
echo $this->form()->openTag($form);
?>
<?php $name = $form->get(‚login’); echo $this->formLabel($name); echo $this->formInput($name); echo $this->formElementErrors($name); ?>
<?php $name = $form->get(‚password’); echo $this->formLabel($name); echo $this->formInput($name); echo $this->formElementErrors($name); ?>
<?php echo $this->formElement($form->get('btn_signin')); ?>
<?php
echo $this->form()->closeTag();
?>

Podstawową wadą takiego podejścia jest ogrom kodu, który trzeba napisać w przypadku rozbudowanych formularzy. Każde pole musi zostać wyrenderowane z osobna (możliwe, że można to zrobić za jednym wywołaniem metody, jednak jeszcze się nie dokopałem do takiej możliwości), co oznacza, że plik z widokiem może być ogromny, zwłaszcza, jeśli na stronie mamy kilka formularzy. Na szczęście możemy ułatwić sobie sprawę i napisać partial, który będzie odpowiedzialny za konkretny formularz. Oznacza to stworzenie kolejnego pliku, ale i tak lepsze to niż przewijanie setek linii kodu. Niezaprzeczalną zaletą tego rozwiązania jest fakt, iż w końcu możemy bez większego problemu dowolnie rozmieścić elementy formularza, bez zabawy z dekoratorami.

Czy nowy Zend Form przypadnie do gustu programistom? Ciężko stwierdzić. Ja miałem problemy z przyzwyczajeniem się do takiego rozbicia formularza. W końcu na Zend_Form zjadłem zęby. Z drugiej strony, mamy o wiele większą kontrolę nad formularzami, przez co ich tworzenie nie będzie wymagało znajomości wiedzy tajemnej. Sądzę, że jak tylko zacznie się korzystać z ZF2, stary sposób tworzenia formularzy będzie jeszcze bardziej przeklinany niż obecnie.