Formularze w Symfony 2
Symfony2 posiada genialne komponenty zarówno do pracy z formularzami jak i do walidacji danych. W tej lekcji chciałbym pokazać Wam, na przykładzie prostego formularza…
Formularze są nieodzownym elementem stron WWW. Niestety ich definiowanie, walidacja i obsługa to zadanie bardzo żmudne (rzeźbienie struktury w HTML’u) i często dość trudne (walidacja danych, zabezpieczenie przed CSRF), a dodatkowo wymaga sporego nakładu czasu. Na szczęście coraz częściej wykorzystuje się gotowe biblioteki, które znacznie przyspieszają pracę.
UWAGA! Już w najbliższy wtorek 22.04 premiera kursu Symfony – Techniki Pracy na eduweb.pl! Więcej informacji znajdziecie w tym poście.
Symfony2 posiada genialne komponenty zarówno do pracy z formularzami jak i do walidacji danych. W tej lekcji chciałbym pokazać Wam, na przykładzie prostego formularza subskrypcji, jak wykorzystać możliwości frameworka i dosłownie w przeciągu kilku minut zdefiniować formularz oraz dodać reguły walidacji.
Pierwszą rzeczą, którą powinniśmy zrobić jest zdefiniowanie encji. Encje to zwykłe klasy PHP odzwierciedlające jakiś byt w naszym programie i może to być klasa użytkownika, klasa produktu, etc. Dobrą praktyką jest tworzenie klas encji w katalogu Entity w głównym katalogu pakietu. W naszym przypadku, klasa encji jest banalnie prosta:
<?php namespace Eduweb\TrainingBundle\Entity; class Subscription { private $name; private $email; public function getName() { return $this->name; } public function getEmail() { return $this->email; } public function setName($name) { $this->name = $name; } public function setEmail($email) { $this->email = $email; } }
W klasie mamy zdefiniowane dwie właściwości: name – nazwa użytkownika oraz email – email tej osoby. Ze względu na to, że są to właściwości prywatne musimy dodać metody dostępowe (gettery i settery). Ich brak może spowodować błędy w działaniu formularza. Dodatkowo warto zwrócić uwagę na linię 2, gdzie określamy przestrzeń nazw, w której znajduje się klasa, aby autoloader mógł ją znaleźć.
Kolejnym krokiem jest stworzenie klasy formularza, w której zdefiniujemy z jakich elementów będzie składał się formularz. Dobrą praktyką jest tworzenie tego typu klas w katalogu Form/Type. Warto też dodać do nazwy klasy przyrostek Type. Kod klasy formularza subskrypcji:
<?php namespace Eduweb\TrainingBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class SubscriptionType extends AbstractType{ public function getName() { return 'edu_subscription'; } public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('name', 'text', array( 'label' => 'Imię i nazwisko' )) ->add('email', 'email', array( 'label' => 'Email' )) ->add('save', 'submit', array( 'label' => 'Zapisz się' )); } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'data_class' => 'Eduweb\TrainingBundle\Entity\Subscription' )); } }
W tej klasie jest kilka miejsc, na które warto zwrócić uwagę:
linia 3: Określamy tu przestrzeń nazw klasy (odzwierciedla ona także strukturę katalogu,w którym znajduje się klasa).
linie 5,6,7: Importujemy wymagane klasy, z których będziemy korzystać.
linia 9: Podążamy za dobrymi praktykami i w nazwie klasy korzystamy z przyrostka Type. Dodatkowo dziedziczymy po klasie AbstractType (wymagane).
linia 11: Metoda getName() jest wymagana i musi ona zwracać nazwę tego formularza. Nazwę możemy określić samodzielnie.
linia 15: Przeciążamy metodę buildForm i to w niej, korzystając z obiektu buildera, definiujemy poszczególne pola formularza. Każde użycie metody add() dodaje nowy element do formularza. W metodzie add() najważniejsze są dwa pierwsze parametry. Pierwszy parametr powinien mieć taką samą nazwę jak właściwość w encji, drugi parametr określa typ pola. Lista wszystkich dostępnych typów wraz z ich opisem dostępna jest w referencji. Trzeci parametr (tablica) jest opcjonalny i to w nim możemy przekazać dodatkowe opcje dla danego pola. My ustawiamy jedynie label. Ostatnie wywołanie metody add() dodaje do formularza przycisk submit.
linia 28: Przeciążamy metodę setDefaultOptions i w niej ustawiamy najważniejszy domyślny parametr czyli data_class. Powinien on określać klasę, dla której ten formularz został stworzony. W naszym przypadku jest to stworzona wcześniej encja Subscription.
Teraz możemy wykorzystać gotową klasę SubscriptionType w kontrolerze do stworzenia instancji formularza. W katalogu Controller, pakietu utworzyłem, klasę dla kontrolerów SubscriptionsController.php, a w niej zdefiniowałem kontroler indexAction. Bazowy kod tej klasy wygląda następująco:
<?php namespace Eduweb\TrainingBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Eduweb\TrainingBundle\Entity\Subscription; use Eduweb\TrainingBundle\Form\Type\SubscriptionType; class SubscriptionsController extends Controller { /** * @Route("/subscription") * * @Template */ public function indexAction(){ $Subscription = new Subscription(); $form = $this->createForm(new SubscriptionType(), $Subscription); return array( 'form' => $form->createView() ); } }
W celu lepszego rozumienia, na pewno przyda się krótkie omówienie, a zatem:
linia 3: Standardowo, definiujemy przestrzeń nazw.
linie 5-9: Importujemy klasy, z których będziemy korzystać.
linia 11: Klasa SubscriptionsController dziedziczy po bazowym kontrolerze w Symfony2. Dzięki temu mam dostęp do szeregu dziedziczonych metod (m.in. do createForm()).
linie 13-17: Używamy adnotacji @Route do zdefiniowania adresu URL pod którym możemy uruchomić kontroler oraz adnotacji @Template do automatycznego podpięcia pliku szablonu.
linia 20: Do zmiennej $Subscription przypisujemy nową instancję encji Subscription. Do tej instancji w dalszej części zbidnujemy dane z formularza.
linia 22: Linia kluczowa, w której za pomocą dziedziczonej metody createForm tworzymy instancję formularza na podstawie definicji SubscriptionType (pierwszy parametr). Opcjonalnie przekazujemy w drugim parametrze encję. Jeżeli zawierałaby ona ustawione właściwości, zostałby by one wstawione do pól formularza.
linia 25: Do widoku przekazujemy, to co zwraca metoda $form->createView() czyli instancję widoku formularza (FormView). Będzie ona dostępna w widoku pod zmienną 'form’.
Wszystko jest już gotowe do tego, aby wyrenderować formularz na stronie. Tworzymy zatem plik szablonu TWIG dla tego kontrolera w katalogu: Resources/views/Subscriptions/index.html.twig. Kod wymagany do wygenerowania formularza jest banalnie prosty i sprowadza się do jednej linii:
<div id="container">{{ form(form) }}</div>
Prostotę tego rozwiązania zawdzięczamy specjalnej funkcji form() w TWIGu, do której jako parametr przekazaliśmy formularz. Ze względu na to, że każdy element formularza, renderowany jest z atrybutem required, przeglądarki wspierające HTML5 uruchomią walidację po stronie klienta. Jeżeli chciałbyś to wyłączyć wystarczy że ustawisz atrybut novalidate w tagu <form>. Używając funkcji form() możemy to osiągnąć w ten sposób:
<div id="container">{{ form(form, {'attr': {'novalidate': ''}}) }}</div>
Wynik tego działania prezentuje się następująco:
Formularz wygląda elegancko, a dodatkowo Symfony 2 dodało nam jeszcze jedno pole, którego my nie widzimy, a mianowicie token zabezpieczający przed atakami CSRF! Super!
Pora na dodanie do kontrolera kodu obsługującego formularz po kliknięciu w przycisk „Zapisz się”. Najpierw prezentacja zaktualizowanego kodu:
<?php namespace Eduweb\TrainingBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; use Eduweb\TrainingBundle\Entity\Subscription; use Eduweb\TrainingBundle\Form\Type\SubscriptionType; use Symfony\Component\HttpFoundation\Request; class SubscriptionsController extends Controller { /** * @Route("/subscription") * * @Template */ public function indexAction(Request $Request){ $Subscription = new Subscription(); $form = $this->createForm(new SubscriptionType(), $Subscription); $form->handleRequest($Request); if($form->isValid()){ //kod zapisujący dane znajdujące się w $Subscription } return array( 'form' => $form->createView() ); } }
a teraz omówienie różnic w porównaniu z kodem bazowym:
linia 10: zaimportowanie klasy Request
linia 19: wstrzyknięcie w nagłówku obiektu Request do kontrolera.
linia 25: za pomocą metody $form->handleRequest($Request) bindujemy do formularza dane wpisane przez użytkownika (znajdujące się w obiekcie $Request).
linia 26: w instrukcji warunkowej uruchamiamy metodę $form->isValid(), która zwraca true/false w zależności, czy wszystkie pola formularza są wypełnione poprawnie. W tym momencie nie mamy żadnych warunków nałożonych na pola name i email, więc zostanie sprawdzony tylko token CSRF przypisany do formularza.
linia 28: jeżeli $form->isValid() zwróci true, to znaczy, ze wszystkie pola formularza są poprawne i możemy przejść do zapisania obiektu $Subscription. Dane z formularza zostały do niego zbindowane w linii 25.
Ostatnim zadaniem jest dodanie walidacji do pól name oraz email. Załóżmy, że oba pola nie mogą być puste, a dodatkowo chcemy mieć pewność że użytkownik poda poprawny adres e-mail. Aby dodać takie reguły użyjemy adnotacji w klasie encji:
<?php namespace Eduweb\TrainingBundle\Entity; use Symfony\Component\Validator\Constraints as Assert; class Subscription { /** * @Assert\NotBlank */ private $name; /** * @Assert\NotBlank * @Assert\Email */ private $email; [metody dostępowe] }
Myślę, że kod jest jasny, ale dla pewności go omówię:
linia 4: Importujemy przestrzeń nazw w której znajdują się wszystkie reguły walidacji dostępne w symfony. Ich listę możesz znaleźć w referencji. Dla tej przestrzeni nazw nadaliśmy alias Assert, z którego korzystamy później.
linia 9: Ustawiamy regułę walidacji NotBlank, która znajduje się w przestrzeni nazw Assert. Tyle wystarczy, aby sprawdzić czy pole nie jest puste.
linia 14,15: Ustawiamy regułę walidacji NotBlank (czy nie jest puste) oraz Email (czy poprawny adres e-mail) dla pola email.
Teraz możemy przejść do formularza i spróbować popełnić kilka błędów:
Jak widzicie, kod który musieliśmy napisać jest krótszy niż komentarz do niego. Dodatkowo, wydaje mi się, że kod jest na tyle czytelny, że nawet bez komentarza większość z Was nie miałaby problemu ze zrozumieniem poszczególnych etapów.