Nowości

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:

sf2-rendered-form

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!

sf2-form-csrf-token

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:

sf2-form-errors01 sf2-form-errors02

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.

UDOSTĘPNIJ ARTYKUŁ:

Powiązane artykuły

Nowości

Kurs Laravel – Techniki Pracy już dostępny!

Nowości

[PREMIERA] Kurs Laravel – Techniki Pracy już dostępny!

Nowości

Kurs PHP już wkrótce!

Pozostań na bieżąco!

Już nigdy nie przegapisz ważnych informacji, promocji oraz nowych kursów. Zapisz się na newsletter już teraz!

Zapisując się do newslettera akceptujesz naszą politykę prywatności