Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [Phalcon] Gdzie przeprowadzać walidację?
Forum PHP.pl > Forum > PHP > Frameworki
WebCM
Zobaczmy przykładowy kontroler:

  1. class UsersController extends Phalcon\Mvc\Controller
  2. {
  3. public function addAction()
  4. {
  5. $user = new User;
  6. $form = new UserForm($user);
  7.  
  8. if($this->request->isPost())
  9. {
  10. //Metoda także dokonuje $user->assign($_POST)
  11. $form->bind($_POST, $user);
  12.  
  13. //Walidacja nr 1 - formularz
  14. if($form->isValid())
  15. {
  16. //Walidacja nr 2 - model
  17. if($user->save())
  18. {
  19. $this->view->disable();
  20. $this->response->redirect('users');
  21. }
  22. else
  23. {
  24. //Walidacja nr 2 - odczyt błędów
  25. foreach($user->getMessages() as $msg)
  26. {
  27. $this->flash->error($msg);
  28. }
  29. }
  30. }
  31. else
  32. {
  33. //Walidacja nr 1 - błędy formularza
  34. foreach($form->getMessages() as $msg)
  35. {
  36. $this->flash->error($msg);
  37. }
  38. }
  39. }
  40. $this->view->form = $form;
  41. $this->view->user = $user;
  42. }
  43. }
Przykładowa klasa formularza - nieobowiązkowa
  1. <?php
  2.  
  3. use Phalcon\Forms\Form;
  4. use Phalcon\Forms\Element\Text;
  5. use Phalcon\Forms\Element\Password;
  6. use Phalcon\Forms\Element\Select;
  7. use Phalcon\Validation\Validator\Confirmation;
  8. use Phalcon\Validation\Validator\StringLength;
  9.  
  10. class UserForm extends Form
  11. {
  12. public function initialize()
  13. {
  14. //Niedogodność w Phalconie
  15. //Chociaż dodamy mu 100 walidatorów
  16. //Sam nie doda atrybutów, tylko trzeba wszystko ręcznie
  17. $login = new Text('login', array(
  18. 'maxlength' => 25,
  19. 'required' => 'required', //nie da się dodać pustych atrybutów
  20. 'pattern' => '.....',
  21. 'placeholder' => 'Wpisz login'
  22. 'autofocus' => 'autofocus' //już lepiej pisać czysty HTML w widoku
  23. ));
  24. $login->addValidator(new StringLength(array(
  25. 'min' => 200,
  26. 'max' => 25,
  27. 'messageMinimum' => 'Login musi miec min 2 znaki!',
  28. 'messageMaximum' => 'Login jest za dlugi - max 25!'
  29. )));
  30.  
  31. //Tutaj też ciekawy fragment
  32. $pass = new Password('pass');
  33. $pass->addValidators(array(
  34. new StringLength(array(
  35. 'min' => 5,
  36. 'messageMinimum' => 'Password too short'
  37. )),
  38. new Confirmation(array(
  39. 'with' => 'confirm',
  40. 'message' => 'Password confirmation not the same.'
  41. ))
  42. ));
  43.  
  44. //Wielką zaletą jest tworzenie pól select
  45. //Nie musimy męczyć się z ifami w widoku
  46. //Podajemy mu, skąd pobrać dane lub przekazujemy tablicę
  47. //Jednak nie musimy tego robić w klasie - można też w widoku
  48. $select = new Select('sasiad', User::find(), array(
  49. 'using' => ['ID', 'name']
  50. 'useEmpty' => true,
  51. 'emptyText' => 'Nie ma sąsiada'
  52. ));
  53.  
  54. //Add fields to the form
  55. $this->add($login);
  56. $this->add($pass);
  57. $this->add($select);
  58. }
  59. }
Za dużo kodu w kontrolerze? Za bardzo skomplikowany? Tak. Musimy osobno sprawdzać poprawność danych w formularzu i modelu. Prowadzi to do sytuacji, że wiele pól sprawdzamy 2 razy - najpierw w formularzu, a potem w modelu. Nie możemy pominąć walidacji w modelu - przecież błędne dane mogą doprowadzić do destabilizacji systemu! Zazwyczaj jeśli pole ma mieć określony format, musi taki mieć niezależnie, czy dane wejściowe pochodzą z formularza, czy z innego źródła.

Gdzie przeprowadzać walidację? Czy wszystko sprawdzać w modelu?

Tak byłoby najwygodniej i bez powtórzeń.

Mamy formularz rejestracji. Trzeba powtórzyć hasło. Gdzie sprawdzimy, czy oba hasła zgadzają się? Jeśli przekażemy całą tablicę $_POST do modelu, możemy tam odczytać pole "confirm". Niektórzy powiedzą, że tak nie wolno, bo wychodzimy poza kompetencje modelu, a ten przypadek powinien sprawdzić kontroler. Co o tym myślicie?

Gdzie przeprowadzać kontrolę poprawności danych?

Kolejne zagadnienie to... formularze.

Czysta klasa Form w Phalconie jest bez sensu. Żadnych korzyści. Więcej problemów. Pola możemy sprawdzić w akcji kontrolera, cały kod HTML napisać ręcznie (metoda render() jedynie wypluje kod pojedynczej kontrolki), funkcję pomocniczą {{select()}} wywołać w widoku, kilka plików mniej. Jedyna zaleta to możliwość większej automatyki po rozszerzeniu klasy Form inną klasą bazową lub stworzenie uniwersalnego szablonu formularza (niektóre i tak będą wymagać osobnego kształtu):
  1. {{content()}}
  2.  
  3. <form method="post">
  4. <h1>{{title|e}}</h1>
  5. {% for field in form %}
  6. <tr>
  7. <td>{{field.getLabel()}}</td>
  8. <td>{{field.render()}}</td>
  9. {% if field.hasMessages() %}
  10. <td>
  11. {% for msg in field.getMessages() %}
  12. <div class="msg">{{msg}}</div>
  13. {% endfor %}
  14. </td>
  15. {% endif %}
  16. </tr>
  17. {% endfor %}
  18. <tr>
  19. <td colspan="2">
  20. <input type="submit" value="Zapisz">
  21. </td>
  22. </table>
  23. </form>
LOL biggrin.gif Kod PHP wyglądałby lepiej. Tu można przenieść komunikaty o błędach. Czy opłaca się używać klas formularzy w Phalconie? Są one rzeczywiście przydatne, czy tylko niepotrzebnie komplikują kod?
Crozin
Formularz bardzo często nie operuje bezpośrednio na modelu aplikacji, a na swoim własnym, stąd wymaga on innego zestawu reguł walidacji. Przykład z podwójnym hasłem jest całkiem dobrym przykładem. Z punktu widzenia logiki aplikacji nie istnieje coś takiego jak "powtórzone hasło użytkownika" - istnieje jedynie hasło. W dodatku taki formularz zainteresowany jest jedynie małym fragmentem danych dot. użytkownika.
Jeżeli całą walidację przeniósłbyś do warstwy modelu, skończyłbyś z czymś tak bezsensownym w przykładowym mechanizmie tworzenia nowego konta z losowym hasłem:
  1. $password = getSomeRandomPassword();
  2.  
  3. $userData = array(
  4. 'username' => 'Crozin',
  5. 'password' => $password,
  6. 'repeatedPassword' => $password
  7. );
Trochę bez sensu, prawda?

Takie coś jak komunikaty błędów, faktycznie powinno zostać zrealizowane przez widok/szablon. Generując każdy z elementów formularza mogący posiadać jakieś błędy (formularz sam w sobie, grupy pól czy w końcu pojedyncze pola) powinieneś generować jego:
- nazwę,
- listę błędów, jeżeli jakieś wystąpiły,
- kontrolę formularza.
Taki schemat generowania zapewnia pewnie klasa ...\Form z frameworka.

Wtedy Twój kontroler będzie wyglądać tak:
  1. [...]
  2.  
  3. if ($request->isPost()) {
  4. $form->bind($_POST, $user);
  5.  
  6. if ($form->isValid() && $user->save()) {
  7. // przekierowanie
  8. }
  9. }
  10.  
  11. [...]
Teraz jest już znacznie prościej i wygodniej.
WebCM
Czyli ostateczny kształt akcji jak poniżej?
  1. class UsersController extends Phalcon\Mvc\Controller
  2. {
  3. public function addAction()
  4. {
  5. $user = new User;
  6. $form = new UserForm($user);
  7.  
  8. if($this->request->isPost())
  9. {
  10. $form->bind($_POST, $user);
  11. if($form->isValid())
  12. {
  13. if($user->save())
  14. {
  15. $this->view->disable();
  16. $this->response->redirect('users');
  17. }
  18. else
  19. {
  20. //Odczyt błędów modelu
  21. foreach($user->getMessages() as $msg)
  22. {
  23. $this->flash->error($msg);
  24. }
  25. }
  26. }
  27. }
  28. $this->view->form = $form;
  29. $this->view->user = $user;
  30. }
  31. }
Odczyt błędów formularza przenosimy do widoku jak w pierwszym poście. Może odczyt błędów modelu też da się przenieść do widoku, bo można odczytać błędy dla konkretnego pola. Na razie zostawmy tak jak powyżej. Niestety, nie działa to prawidłowo - field.hasMessages() zwraca fałsz, a field.getMessages() pustą tablicę. Błąd w Phalconie lub coś mam nie tak. Pozostaje kwestia klucza CSRF. Też sprawdzamy go w kontrolerze?
Crozin
1. Nigdy nie korzystałem z Phalcona, nie znam go więc ciężko mi się tutaj wypowiadać.
2. Sprawdzanie tokena CSRF powinno zostać obsłużone przez samą bibliotekę zajmującą się formularzami. Jednak z tego co widzę w dokumentacji chyba trzeba to robić ręcznie.
3. Jeżeli dane przeszły pomyślnie walidację formularza model nie powinien już wyrzucić żadnych błędów-komunikatów dla użytkownika - dane powinny być w pełni poprawne. Tutaj powinien już raczej lecieć wyjątek wywalający całą aplikację.
WebCM
Ad 2. Formularze w Phalconie to poczwarka. Może kiedyś wyrośnie motyl. Tak, aktualnie trzeba generować klucz CSRF ręcznie. Można napisać nakładkę i dodawać pole w initialize().
Ad 3. Można ustawić tryb, aby model wyrzucał wyjątek. Czy to rzeczywiście lepsze wyjście?
ShadowD
Korzystałem w kilku apkah z phalcona, z punktu widzenia mojego klasy formularzy są odpowiedzialne za filtrowanie danych, a nie modele. Przykład jest banalny, admin ma często większe możliwości edycji jakiś danych niż user, a fizycznie korzystają z jednego modelu.

Jeśli chodzi o stwierdzenie, że formy to poczwarka to się zgodzę, moja tymaczasowa mam nadzieje rezygnacja z phalcona jest podyktowana wieloma niedoróbkami jakie ten FW aktualnie posiada, a z klasą formów szybciej jest podciąć sobie żyły z złości niż zbódować jakiś większy form. Aczkolwiek Framework jest na prawdę domry i miejmy nadzieje, że wersja 2 będzie już czymś bardziej dopracowanym. ;-)
WebCM
Programiści Phalcona wymyślili normalizację wartości pól formularzy do UTF-32. Niestety w PHP funkcja mb_convert_encoding psuje kodowanie.

  1. // ą ć ę ł ń ś ź ż ? ?
  2. Phalcon\Tag::textField(['test','value'=>'ą ć ę ł ń ś ź ż Ó ó']);
Zepsute są litery Ó i ó, ale po wielokrotnym wysyłaniu formularza psują się wszystkie polskie znaki. Czy znacie rozwiązanie tego problemu? Można całkowicie wyłączyć funkcję autoescape, ale wtedy kod staje się podatny na XSS.

Kod źródłowy: https://github.com/phalcon/cphalcon/blob/ma.../escaper.c#L261

Jeśli nie da się z tym nic zrobić, usunę wspólny szablon do generowania formularzy i powrócę do osobnych widoków dla każdego formularza, gdzie wstawię <input> ręcznie. Jaki ma sens wspólny szablon z tysiącami IF-ów, aby obejść problem?

PS. Forum zamienia encje na znaki, ale w rzeczywistości polskie znaki są zakodowane, np. & # x105;
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.