Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

> Kontrola danych wejsciowych
kicaj
post
Post #1





Grupa: Zarejestrowani
Postów: 1 640
Pomógł: 28
Dołączył: 13.02.2003
Skąd: Międzyrzecz/Poznań

Ostrzeżenie: (0%)
-----


Jak optymalnie i bardziej uniwersalnie sprawdzac dane w kontrolerach (data input)?
Pisanie kontrolerow na zasadzie:
  1. <?php
  2. class NameController extends Controllers
  3. {
  4. function indexAction( $aParams )
  5. {
  6.  if( isset( $aParams['id'] ) 
  7.  {
  8. if( is_numeric( $aParams['id'] ) // ...
  9.  }
  10.  else
  11.  {
  12.  echo 'nie podano id!';
  13.  
  14. //...i tak w kazdej metodzie, sprawdzane rozne warunki z danych wejsciowych
  15. ?>


Sprawdzanie tego w kazdym kontrolerze i w jego kazdej metodzie mija sie z celem, jak to rozwiazac inaczej?
Go to the top of the page
+Quote Post
 
Start new topic
Odpowiedzi
splatch
post
Post #2





Grupa: Zarejestrowani
Postów: 487
Pomógł: 7
Dołączył: 7.01.2004
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


Kontrola danych wejściowych w miarę możliwości powinna być poza ciałem akcji. Jakkolwiek nie zależy zapominać że to konkretna akcja determinuje nam warunki walidacji.
Warunki typu czy to liczba, czy to jest wymagane i tak dalej można wyodrębnić do klas, które załatwią nam walidację. Jak zwykle w takim przypadku zaczynamy od interfejsu:
  1. <?php
  2. interface Validation {
  3. /**
  4. * @param $ctx Kontekst z informacjami na temat walidacji
  5. **/
  6. public function validate(ValidationContext $ctx);
  7. }
  8. ?>


Dobrze, teraz jak może wyglądać definicja kontekstu walidacji:
  1. <?php
  2. interface ValidationContext {
  3.  
  4. // tu mamy ważny błąd
  5. public function addError($key, ValidationMessage $msg);
  6.  
  7. // a tu powiedzmy, ostrzeżenie
  8. public function addNotice($key, ValidationMessage $msg);
  9.  
  10. public function addValidator(Validator $v);
  11.  
  12. // czy wszystko "ok"
  13. public function isPassed();
  14.  
  15. // mapa wiadomości gdzie kluczem jest nazwa pola a wartością kolekcja wiadomości
  16. public function getMessages();
  17.  
  18. // tutaj dane z requestu
  19. public function getArgument($key);
  20. }
  21. ?>


Dobra, dorzućmy do tego jeszcze wiadomość:
  1. <?php
  2. interface ValidationMessage {
  3.  
  4. // tutaj wiadomość, oczekujemy tylko tego - jak ona jest ustawiana
  5. // to już kwestia wtórna
  6. public function getMessage();
  7. }
  8. ?>


Może wydawać się dziwne to, że robię interfejs do takiego banału, aczkolwiek mam jeden cel:
  1. <?php
  2. abstract class AbstracMessage {
  3.  
  4. // nie można się dobrać do wiadomości nawet z potomków
  5. private $message;
  6.  
  7. public function __construct($message, array $args = array()) {
  8. $this->message = $this->transform($this->getMessageString($message), $args);
  9. }
  10.  
  11. protected abstract function getMessageString($message);
  12.  
  13. private function transform($string, array $args) {
  14.  // tu może być coś innego
  15.  return strtr($string, array_keys($args), array_values($args));
  16. }
  17.  
  18. // to nam wymusza odwołanie do konstruktora
  19. public final function getMessage() {
  20. return $this->message;
  21. }
  22. }
  23.  
  24. // ta wiadomość po prostu zwraca tekst, który był
  25. class RawMessage extends AbstractMessage {
  26. protected function getMessageString($message) {
  27. return $message;
  28. }
  29. }
  30.  
  31. // a ta wiadomość tłumaczy ładnie komunikaty
  32. class I18nMessage extends AbstractMessage {
  33.  
  34. private $resource;
  35.  
  36. public function __construct(I18nValidationContext $ctx, $message, array $args = array()) {
  37. $this->resource = $ctx->getI18Resource();
  38. parent::__construct($message, $args);
  39. }
  40.  
  41. protected function getMessageString($message) {
  42. return $this->resource->getString($message);
  43. }
  44. }
  45.  
  46. // to powtórka z rozrywki - mamy interfejs i dorabiamy implementacje
  47. interface MessageResource {
  48. public function getString($key);
  49. public function getStringArray($key);
  50. }
  51.  
  52. // implementacja do zrobienia
  53. interface I18nValidationContext extends ValidationContext {
  54. public function getResource();
  55. }
  56.  
  57. ?>


Dobra, wiadomości wiadomościami, ale przecież nie jest to najistotniejsze, najbardziej przecież nas interesuje powiązanie tego wszystkiego - czyli jak akcja "się waliduje". Zasadniczo znowu byłbym za interfejsem:
  1. <?php
  2. interface Action {
  3. public function execute();
  4. }
  5.  
  6. interface SecureAction {
  7. public function getCredentials();
  8. }
  9.  
  10. interface ValidationRequiredAction {
  11. public function validate(ValidationContext $ctx);
  12. }
  13. ?>

Mając takie definicje front controller, który ma podniesiony ValidationContext może rozróżnić co zrobić - czyli najpierw sprawdza czy akcja wymaga autoryzacji - jeśli tak sprawdza uprawnienia, następnie weryfikuje czy dane, które przychodzą są ok.

Teraz rzecz zasadnicza - czyli co z konfiguracją. Jak widać same walidatory to pestka - ale jak zrobić to ładnie. No więc - może przykładowa implementacja:
  1. <?php
  2.  
  3. class DefaultValidationContext implements ValidationContext {
  4.  
  5. private $errors = 0;
  6.  
  7. // tu mamy ważny błąd
  8. public function addError($key, ValidationMessage $msg) {
  9. $this->addMessage(new StackEntry('error', $key, $msg));
  10. ++$this->errors;
  11. }
  12.  
  13. // a tu powiedzmy, ostrzeżenie
  14. public function addNotice($key, ValidationMessage $msg) {
  15. $this->addMessage(new StackEntry('notice', $key, $msg));
  16. }
  17.  
  18. protected final function addMessage(StackEntry $msg) {
  19. if (!isset($this->message[$msg->getKey()])) {
  20. $this->message[$msg->getKey()] = array();
  21. }
  22. $reference = &$this->message[$msg->getKey()];
  23. // teraz w widoku/akcji mamy do dyspozycji info o "powadze" błędu
  24. // oraz powiązaną z nim wiadomość
  25. $reference[sizeof($reference)]['severity'] = $msg->getSeverity();
  26. $reference[sizeof($reference)]['msg'] = $msg->getValidationMessage();
  27. }
  28.  
  29. // czy wszystko "ok"
  30. public function isPassed() {
  31. $i = 0;
  32. $validatorsCount = sizeof($this->validators);
  33. do {
  34. // jeśli coś się tu stanie to zwiększy nam się rozmiar tablicy $errors
  35. $this->validators[$i]->validate($this);
  36. } while ($this->errors == 0 && ++$i < $validatorsCount);
  37.  
  38. // przeszliśmy wszystkie walidatory i nie ma błędów
  39. return ++$i == $validatorsCount && $this->erros == 0;
  40. }
  41.  
  42. // mapa wiadomości gdzie kluczem jest nazwa pola a wartością kolekcja wiadomości
  43. public function getMessages() {
  44. return $this->message;
  45. }
  46.  
  47. public function addValidator(Validator $v) {
  48. $this->validators[] = $v;
  49. }
  50.  
  51. }
  52.  
  53.  
  54. ?>



No i na końcu akcja
  1. <?php
  2. class MyAction implements SecureAction, ValidationRequiredAction {
  3.  
  4. // dorzucamy co trzeba do kontekstu - resztę załatwia za nas FrontController
  5. public function validate(ValidationContext $ctx) {
  6. $ctx->addValidator(new NotEmptyValidator('user_id'));
  7. $ctx->addValidator(new NumberFormatValidator('user_id'));
  8. }
  9.  
  10. }
  11. ?>


Aa i jeszcze walidatory:
  1. <?php
  2. class NotEmptyValidator implements Validator {
  3.  
  4. private $field;
  5.  
  6. public function __construct($field) {
  7. $this->field = $field;
  8. }
  9.  
  10. public function validate(ValidationContext $ctx) {
  11. if (empty($ctx->getArgument($field)) {
  12. $ctx->addError(new RawMessage('Wartość {0} jest pusta', array($field)));
  13. }
  14. }
  15. }
  16. ?>


Kilka uwag odnośnie kodu, który jest wyżej .. przede wszystkim walidatory dodają wiadomości - równie dobrze mogą bronić się wyjątkami, aczkolwiek mamy wówczas problem ze sformułowaniem komunikatu dla użytkownika. Można zatem rozważyć dwie opcje - wydelegować formatowanie wiadomości z AbstractMessage do ValidationContext - wówczas zmieniły by się nam sygnatury metod addError, addNotice. Druga opcja to przesunąć formatowanie wiadomości do akcji.
Dlaczego dodałem coś takiego jak notice w walidacji? Są to informacje, które nie wywalają całego procesu, a które można wyświetlić przy ponownym wyświetlaniu błędu - powiedzmy ktoś nie podał wartości w wymaganym polu a w innym gdzie był powinien trafić float dostaliśmy int - dodajemy notice o konwersji.

Aaa i jeszcze walka z tworzeniem walidatorów:
  1. <?php
  2. class XmlValidationForm implements Action {
  3.  
  4. public final function validate(ValidationContext $ctx) {
  5. $path = $this->getContextPath();
  6. $binder new XmlValidatorBinder($path, $ctx);
  7. }
  8.  
  9. public function getContextPath() {
  10. return get_class($this) .'-validation.xml';
  11. }
  12.  
  13. }
  14.  
  15. class MyAction extends XmlValidationForm implements SecureAction, ValidationRequiredAction {
  16.  
  17. // tu już nic nie musimy dodawać - na podstawie nazwy klasy zostanie wczytana
  18. // konfiguracja walidatorów
  19.  
  20. }
  21. ?>


No i kolejna ciekawa wariacja nad którą można pomyśleć:
  1. <?php
  2. $orValidator = new OrValidationCondition($validator1, $validator2);
  3. $ctx->addValidator($orValidator);
  4. ?>


Kod który przedstawiłem nie jest ani kompletny ani nadzwyczajnie spójny. Może stanowić podstawę do rozpoczęcia prac, aczkolwiek pozostaje jeszcze kilka problemów, które wymagają niezłej gimnastyki. (IMG:http://forum.php.pl/style_emoticons/default/smile.gif)
Go to the top of the page
+Quote Post

Posty w temacie


Reply to this topicStart new topic
2 Użytkowników czyta ten temat (2 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Aktualny czas: 11.10.2025 - 07:14