Tag Archives: walidacja danych

webmastering

Wygrzebane z GitHuba (3) : Validation

W dzisiej­szym odcinku weźmie udział „najwspa­nial­szy z kiedy­kol­wiek stwo­rzo­nych silnik do wali­da­cji danych w PHP” — Vali­da­tion. Projekt rozwija groź­nie brzmiąca grupa progra­mi­stów o nazwie Respect z Brazy­lii. Mam nadzieję, że po tym wpisie nie wylą­duję w lesie wycią­gnięty z bagaż­nika jakie­goś starego Mercedesa…

Projekt speł­nia normy, które są oczy­wi­ste przy czyta­niu kodu takich gigan­tów jak Zend Frame­work 2 czy Symfony, a więc używa­nie name­spa­ces, drze­wia­sta struk­tura klas i kata­lo­gów, kompa­ty­bil­ność ze stan­dar­do­wym auto­lo­ade­rem klas SplC­las­sLo­ader, własne rozsze­rze­nia wyjąt­ków. Jedyny zgrzyt to poziom doku­men­ta­cji. Auto­mat raczej nie da rady w pełni wyge­ne­ro­wać typów para­me­trów metod i zwra­ca­nych warto­ści. W nagrodę za to dostęp­nych jest dzie­siątki testów jednost­ko­wych. Co jak co, ale wali­da­tor spraw­dza­jący czyjeś dane sam powi­nien być grun­tow­nie sprawdzony ;-)

Powszech­nie wiadomo, że w PHP nie można użyć konstruk­cji typu

new Obiekt()->metoda();

Zamiast tego można stoso­wać metody statyczne, które w rezul­ta­cie zwrócą nam obiekt lub też można zasto­so­wać tak lubia­nego przez was Harrego Pottera. Twórcy biblio­teki czarują często i gęsto.

Auto­rzy Vali­da­tion propo­nują używa­nie w nastę­pu­jący sposób:

// deklarujemy uzywany namespace i robimy alias do klasy Validator
use Respect\Validation\Validator as v;
 
// mozemy albo najpierw przygotowac obiekt walidatora
$walidator = v::string()->between('a', 'c');
 
// albo tez bezposrednio uzywac w instrukcjach warunkowych, np.
if (v::numeric()->positive()->validate($cos)) echo 'prawda';
 
//i potem uzywac do sprawdzania jednej z trzech metod:
 
// zwroci tylko true albo false
$walidator->validate($cos);
 
// wypluje wyjatek z pierwszym bledem
$walidator->check($cos);
 
// wypluje wyjatek ze wszystkimi bledami
$walidator->assert($cos);

Warun­ków, które możemy spraw­dzać jest kilka­dzie­siąt. Można popa­trzeć po plikach. Z ciekaw­szych to wymie­niony wyżej

between()

dla zakresu znaków,

in(array('wartosc1' , 'wartosc2))

dla dozwo­lo­nych warto­ści oraz konstrukcja

v::each(v::warunek())->validate(array('wartosc1','wartosc2'));

dla spraw­dze­nia wszyst­kich warto­ści w tablicy. Ponadto można tworzyć kompo­zyty różnych warun­ków za pomocą

v::allOf($walidator1,$walidator2);

(jeżeli wszyst­kie muszą przejść) oraz

oneOf()

(jeżeli wystar­czy nam, że przej­dzie tylko jeden z warun­ków żeby wali­da­cja była poprawna). Dodat­kowo można używać wali­da­to­rów z Zend Frame­worka i Symfony.

Tyle było chwa­le­nia, teraz wady:

  • zamie­sza­nie w wyjąt­kach — często widzimy {{failed}} zamiast komu­ni­katu błędu,
  • zachę­ca­nie do bezpo­śred­niego majstro­wa­nia przy niepu­blicz­nych polach obiek­tów za pomocą metody attri­bute() (w bebe­chach widać, że metoda zmie­nia dostęp do zmien­nej za pomocą klasy Reflec­tion­Pro­perty) zamiast tego attri­bute() powinno przyj­mo­wać ewen­tu­al­nie nazwy metod dostę­po­wych do zmien­nych i w ten sposób sprawdzać.

Tak czy siak, projek­tanci idą w dobrym kierunku. Wszystko ładnie ze sobą współ­pra­cuje, ja kilku nowych trików się nauczy­łem. Pozo­staje bacz­nie obser­wo­wać postępy. Pozdro dla pozo­sta­łych GitHu­bo­wych podglądaczy ;-)

webmastering

Waliduj z Harrym Potterem

Począw­szy od wersji 5.3.0 dosta­li­śmy nową fajną funk­cję do czaro­wa­nia w PHP. Do tej pory jeżeli chcie­li­śmy wywo­ły­wać funk­cję, która nie istnieje, musie­li­śmy używać __call. Teraz dodano __callStatic. Przy­znam się, że jakoś mi umknęła. Jak sama nazwa mówi, służy do wywo­ły­wa­nia nieist­nie­ją­cych metod statycz­nych. Pomy­śla­łem, że warto wymy­ślić dla niej jakieś sensowne zadanie.

W ilu miej­scach kodu spraw­dza­cie wielo­krot­nie zmienne czy nie są nulami, czy nie są puste, a jak speł­nią te dwa warunki, to czy są licz­bami całko­wi­tymi i czy nie są więk­sze niż np. 5? Spraw­dze­nie tych wszyst­kich warun­ków powo­duje masę IFów. Nie lepiej byłoby spraw­dzać tak

if (Walidator::check_notNull_notEmpty_isInt_gt5($zmienna)) {
// poprawna wartosc
}

Fajne, co? Bez tworze­nia obiek­tów, złożo­nych funk­cji warun­ko­wych itp. Dla sette­rów jak znalazł :-) Kolejne elementy nazwy zawę­żają waru­nek poprawności.

Klasa powstała w ok. 1 godz., dlatego używaj­cie na własną odpo­wie­dzial­ność. W ciągu kilku dni wrzucę klasę na githuba i trochę pote­stuję w kierunku błędów, dorzucę kilka inte­re­su­ją­cych warun­ków itp. Może się komuś przyda. Aktu­ali­za­cja: klasa już jest.

<?php
class Walidator {
  /**
   * Reaguje na funkcje rozpoczynajace sie od 'check_'.
   * Jako separatora uzywac znaku _
   * @param string $name nazwa nieisniejacej funkcji
   * @param array $args podane parametry
   * @return bool
   */
  public static function __callStatic($name, $args) {
    if (stripos($name, 'check_') === 0) {
      // obcinamy poczatek
      $name = substr($name, 6);
      // rozdzielamy skladowe nazwy
      $extractedNames = explode('_', $name);
      // przelatujemy po wszystkich rozdzielonych funkcjach
      foreach($extractedNames as $funcName) {
        // rozpracowujemy nazwe funkcji
        $funcName = self::extractFunction($funcName);
        // sprawdzamy czy rozpracowana funkcja istnieje
        if (!method_exists(__CLASS__, $funcName['funkcja'])) {
          throw new Exception("Funkcja {$funcName} nie istnieje!");
        }
        // sprawdzamy czy w parametrze podano
        // tablice z wartosciami do sprawdzenia
        if (is_array($args[0])) {
          foreach ($args[0] as $arg) {
            // gwozdz programu! wywolujemy
            // rozpracowana funkcje i podajemy ewentualne
            // dodatkowe parametry
            if (self::$funcName['funkcja']($arg, $funcName['args']) == false) {
              return false;
            }
          }
        }
        // jezeli parametr nie jest tablica
        else {
          if (self::$funcName['funkcja']($args[0], $funcName['args']) == false) {
            return false;
          }
        }
      }
      // jezeli dolecialo az tutaj tzn., ze wartosc poprawnie zwalidowana
      return true;
    }
    // wylapie zle skonstruowany poczatek nazwy funkcji
    throw new Exception("Próba wywołania nierozpoznanej funkcji {$name}");
  }
 
  /**
   * Szuka znanych sobie funkcji skladowych do wywolania.
   * @param string $name
   * @return array tablica z nazwa funkcji i parametrami
   */
  protected static function extractFunction($name) {
    // calosc na male litery
    $name = strtolower($name);
    // jezeli zaczyna sie od 'not'
    if (stripos($name, 'not') === 0) {
      // ciachnij 'not'
      $name = substr($name, 3);
      // to co zostalo napisz z duzej litery
      $name = 'not' . ucfirst($name);
      return array(
        'funkcja' => $name,
        'args' => null);
    }
    // to samo jezeli zaczyna sie od 'is'
    elseif (stripos($name, 'is') === 0) {
      $name = substr($name, 2);
      $name = 'is' . ucfirst($name);
      return array(
        'funkcja' => $name,
        'args' => null);
    }
    elseif (stripos($name, 'gt') === 0) {
      return array(
        'funkcja' => 'gt',
        // to co zostalo musi byc parametrem gt
        'args' => substr($name, 2));
    }
    elseif (stripos($name, 'eq') === 0) {
      return array(
        'funkcja' => 'eq',
        'args' => substr($name, 2));
    }
    elseif (stripos($name, 'between') === 0) {
      $name = substr($name, 7);
      return array(
        'funkcja' => 'between',
        // z tego co zostalo
        // rozwalamy wartosci przy 'and'
        'args' => explode('and', $name));
    }
    elseif (stripos($name, 'maxlength') === 0) {
      return array(
        'funkcja' => 'maxLength',
        'args' => substr($name, 9));
    }
    // jezeli doszlo az tutaj to
    // nierozpoznano funkcji i
    // wywalam wyjatek
    else {
      throw new Exception("Nie rozpoznano funkcji {$name}");
    }
  }

Skoro mamy szkie­let do rozpo­zna­wa­nia funk­cji to wrzu­cimy sobie trochę funk­cji walidujących.

public static function isNull($var) {
  return is_null($var);
}
 
public static function notNull($var) {
  return !is_null($var);
}
 
public static function isEmpty($var) {
  return empty($var);
}
 
public static function notEmpty($var) {
  return !empty($var);
}
 
public static function isInt($var) {
  return is_int($var);
}
 
public static function isString($var) {
  return is_string($var);
}
 
/**
 * Sprawdza czy $var jest wieksze lub rowne $arg.
 * @param numeric $var
 * @param numeric $arg
 * @return bool
 */
public static function gt($var, $arg) {
  if (!is_numeric($var)) throw new Exception("Sprawdzana wartosc {$var} nie jest liczba!");
  if (!is_numeric($arg)) throw new Exception("Warunek {$arg} nie jest liczba!");
  return ($var > $arg);
}
 
/**
 * Sprawdza czy $var jest rowne $arg.
 * @param numeric $var
 * @param numeric $arg
 * @return bool
 */
public static function eq($var, $arg) {
  if (!is_numeric($var)) throw new Exception("Sprawdzana wartosc {$var} nie jest liczba!");
  if (!is_numeric($arg)) throw new Exception("Warunek {$arg} nie jest liczba!");
  return ($var == $arg);
}
 
/**
 * Sprawdza czy $var znajduje sie w przedziale $arg[0] <= $var <= arg[1].
 * @param numeric $var
 * @param array $arg $arg[0] i $arg[1] typu numeric
 * @return bool
 */
public static function between($var, array $arg) {
  if (!is_numeric($var)) throw new Exception("Sprawdzana wartosc {$var} nie jest liczba!");
  if (!is_numeric($arg[0]) || !is_numeric($arg[1])) throw new Exception("Warunek {$arg[0]} lub {$arg[1]} nie jest liczba!");
  return (($var >= $arg[0]) && ($var <= $arg[1]));
}
 
/**
 * Sprawdza max dlugosc ciagu $var.
 * @param string $var
 * @param int $arg
 * @return bool
 */
public static function maxLength($var, $arg) {
  if (!is_string($var)) throw new Exception("Sprawdzana wartosc {$var} nie jest stringiem!");
  if (!is_numeric($arg)) throw new Exception("Warunek {$arg} nie jest liczba calkowita!");
  return (strlen($var) <= (int) $arg);
}

Skoro mamy „klocki”, z których możemy budo­wać sobie warunki wali­da­cji, poni­żej kilka testów

if (Walidator::check_notempty_isint_gt5_between1and9($test)) {
  echo 'wartosc poprawna';
}
else {
  echo 'BLAD!';
}

Dla warto­ści 6.5 funk­cja zwróci błąd (isInt), dla tablicy array(6,7,8,9) wartość poprawną, z kolei Walidator::check_maxLength4(array('ala','miala','kota')) zwróci również błąd, bo ‚miala’ ma 5 znaków :-)

webmastering

Własny walidator w Zend Framework

Dzisiaj będzie coś dla wiel­bi­cieli Zend Frame­worka. A dokład­niej coś, z czym mamy do czynie­nia prze­twa­rza­jąc jaki­kol­wiek formu­larz, czyli wali­da­tory. Wali­da­cja, a więc spraw­dza­nie czy wpro­wa­dzone przez użyt­kow­nika dane odpo­wia­dają pewnemu sche­ma­towi jest obok filtra­cji, czyli wyrzu­ca­niu zbędnych/niebezpiecznych rzeczy z danych chyba najważ­niej­szą sprawą przy tworze­niu witryn WWW.

Zend Frame­work dyspo­nuje zesta­wem kilku­dzie­się­ciu wali­da­to­rów, które można łączyć w łańcu­chy przy wali­do­wa­niu poszcze­gól­nych pól formu­la­rza, np. pyta­jąc o wiek użyt­kow­nika pole musi zawie­rać wyłącz­nie liczby, czyli aż prosi się o Zend_Validate_Digits, a następ­nie należy spraw­dzić czy podana liczba znaj­duje się w jakimś ludz­kim prze­dziale, tzn. powiedzmy pomię­dzy 1 a 100, co zała­twi za nas Zend_Validate_Between.

Co w przy­padku jeżeli musimy dopa­so­wy­wać coś, czego nie prze­wi­dzieli twórcy ZF? Możemy napi­sać własny walidator.

Każdy wali­da­tor musi imple­men­to­wać inter­fejs Zend_Validate_Interface, czyli dwie publiczne metody:

  • isValid($value) — zwra­ca­jąca buliona prawda/fałsz
  • getMessages() — zwra­ca­jąca tablicę komu­ni­ka­tów co jest nie tak z warto­ścią testo­waną w isVa­lid(). Komu­ni­katy zwra­cane są w postaci kod_błędu => komunikat_w_ludzkiej_formie

W celu unik­nię­cia babra­nia się obsługą błędów i komu­ni­ka­tów twórcy ZF stwo­rzyli abstrak­cyjną klasę Zend_Validate_Abstract, która zrobi za nas więk­szość brud­nej roboty. Pozo­sta­nie nam tylko napi­sa­nie właści­wej logiki co ma być spraw­dzane i ewen­tu­al­nie jaki błąd zgłaszany.

Na potrzeby swojej apli­ka­cji napi­sa­łem własny w miarę uniwer­salny wali­da­tor, który spraw­dzi czy klucz podsta­wowy danej tablicy istnieje. Może mieć zasto­so­wa­nie jeżeli ktoś spraw­dza czy np. iden­ty­fi­ka­tor wybra­nej przez użyt­kow­nika kate­go­rii istnieje w bazie.

Poni­żej przed­sta­wiem kod z obja­śnie­niami po co coś jest. Po prze­czy­ta­niu stwier­dzi­cie, że pisa­nie wali­da­to­rów to fajna zabawa :-)

<?php
// rozszerzamy klasę abstrakcyjną dla walidatorów
class Spiechu_Validators_IsIdExists extends Zend_Validate_Abstract {     
 
  // definiujemy kody błędów i jakieś wartości dla nich
  const IDNOTEXISTS = 'idnotexists';
  const EMPTYSTRING = 'emptystring';
  const NULLVALUE = 'nullvalue';
 
  // definiujemy tablicę komunikatów błędów dla poszczególnych kodów
  // zwracam uwagę na '%value', które może być w locie zamienione na sprawdzaną wartość
  protected $_messageTemplates = array(
    self::IDNOTEXISTS   => "id '%value%' not exists",
    self::EMPTYSTRING  => 'given value is empty',
    self::NULLVALUE     => 'given value is null'
  );
 
  // zmienna typu Zend_Db_Table_Abstract, której klucz będziemy testować
  protected $_table;
 
  public function __construct(Zend_Db_Table_Abstract $table) {
    $this->_table = $table;
  }
 
  public function isValid($value) {
 
    // sprawdzamy czy zmienna nie jest nulem, a jeżeli tak to ustawiamy błąd
    if ($value === null) {
      $this->_error(self::NULLVALUE);
      return false;
    }
 
    // to samo dla pustego stringa
    if ($value === '') {
      $this->_error(self::EMPTYSTRING);
      return false;
    }
 
    $value = (int) $value;
 
    // ustawiamy wartość dla ewentualnego komunikatu %value%
    $this->_setValue($value);
 
    // metoda find szuka po kluczu podstawowym i zwraca rowset wyników
    $foundRows = $this->_table->find($value);
 
    // jeżeli nic nie znaleziono tzn. że sprawdzany klucz nie istenieje w tabeli bazy
    if (count($foundRows) == 0) {
      $this->_error(self::IDNOTEXISTS);
      return false;
    }
    else {
      return true;
    }
  }
}

Wszystko działa na tyle auto­ma­gicz­nie, że tworząc jakiś element formu­la­rza wystar­czy dodać stwo­rzony wali­da­tor np. tak:

$formElement = new Zend_Form_Element_Select('pole_formularza');
$formElement->setRequired(true)
  ->setLabel('Jakaś etykieta')
  ->setMultiOptions($dane_do_wyboru)
  ->addValidator(new Spiechu_Validators_IsIdExists(new Sprawdzana_Tabela()));