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 :-)

Podobne wpisy:

  1. Wygrze­bane z GitHuba (3) : Validation
  2. Wzorzec projek­towy memento w PHP
  3. 10 trików w Smarty
  4. Stan­dardy kodo­wa­nia zgodne z Zend, cz. 1/3

12 Comments

  • 18 lutego 2011 - 07:26 | Permalink

    Ten kod to kolejny dosko­nały dowód na to, że magia jest bez sensu. Zamiast sobie po ludzku prze­ka­zać nazwę funk­cji spraw­dza­ją­cej (lub tablicę takich nazw) jako zwyczajny argu­ment, napi­sa­łeś kupę kodu do jej wypa­ko­wy­wa­nia z nazwy innej funk­cji i skle­ja­nia. Z mojej strony: __callStatic() idzie do śmiet­nika i innym progra­mi­stom PHP zale­cam zrobić to samo.

  • renq
    18 lutego 2011 - 07:39 | Permalink

    Niestety Zyx ma rację, tego się nie powinno używać. Kod, który napi­sa­łeś to fajna cieka­wostka, ale czy za pół roku będziesz pamię­tał jak wywo­łać te magiczne funk­cje? Poza tym edytory nie podpo­wia­dają magii.

  • Śpiechu
    18 lutego 2011 - 08:06 | Permalink

    Ale potra­fi­cie zbić z tropu. Ja mimo wszystko spró­buję w swoich rzeczach trochę tego używać i sam ocenię.

  • 18 lutego 2011 - 09:31 | Permalink

    Śpie­chu, a może zamiast wianuszka poje­dyn­czych funk­cji wali­du­ja­cych zasto­so­wał być „stra­tegy pattern” :> ?

  • Śpiechu
    18 lutego 2011 - 09:49 | Permalink

    @sokzzuka
    Tak ale to spowo­do­wa­łoby powsta­nie masy małych klas opako­wu­ją­cych wbudo­wane w PHP funk­cje (jesz­cze więk­sze marno­wa­nie zaso­bów niż teraz). Mnie chodziło o same metody statyczne.
    Rach ciach i masz walidator :-)

  • Grzes
    18 lutego 2011 - 10:58 | Permalink

    Faktycz­nie, przy­da­łoby się porów­nać czas pracy takiego ‚patentu’, bo pewnie proste ify zajeły by sporo mniej czasu proce­sora.
    Wiem, że dla małej apli­ka­cji to pewnie nie miałoby takiego znacze­nia, ale przy sporym ruchu narzut dodat­ko­wej logiki solid­nie da się we znaki.

  • daniel1302
    18 lutego 2011 - 20:27 | Permalink

    Witam, Bardzo dobry art aczkol­wiek wolę rozwią­za­nia trady­cyjne w tym przy­padku jak powie­dział Zyx w pierw­szym poście. Przy­naj­mniej się nie pogubię

  • Quasar
    19 lutego 2011 - 02:37 | Permalink

    Dobra rzecz. Co prawda nie nadaje sie do wali­da­cji formu­la­rzy, ale po stro­nie logiki powinna ulatwic prace.

  • 19 lutego 2011 - 08:52 | Permalink

    Śpie­chu, jak chcesz magię, to masz całą masę frame­wor­ków, które są w ten sposób napi­sane, z ZF-em i Symfony 1.x na czele. Teraz robię projekt z wyko­rzy­sta­niem tego drugiego i to, co się tam dzieje, to nawet nie czarna magia, ale czyste czar­no­księ­stwo. Jak coś nie chce dzia­łać, to nie wiadomo, gdzie zacząć szukać, bo praw­do­po­dob­nie sami twórcy nie wiedzą, z czego tak właści­wie konkretny obiekt korzy­sta i co właści­wie robi dane wywo­ła­nie. Naprawdę łudzisz się, że __callStatic repre­zen­tu­jący dokład­nie takiego samego ducha projek­to­wa­nia coś zmieni?

  • 20 lutego 2011 - 23:30 | Permalink

    U mnie cała „magia” bez wyjątku ląduje w koszu, poza naprawdę niewie­loma dobrze udoku­men­to­wa­nymi i uzasad­nio­nymi miej­scami, po prostu nie widzę dla niej miej­sca w dobrze zapro­jek­to­wa­nej struk­tu­rze kodu. Progra­mo­wać zaczy­na­łem od Pascala i C++, więc jakoś nie ciągnie mnie w tą stronę. ;]

  • 22 lutego 2011 - 10:07 | Permalink

    Wygląda pomysłowo…ale używa­nie ;) to nie dla mnie.
    Jak pisali poprzed­nicy, są o wiele czyst­sze metody na takie sprawdzenie.

  • Śpiechu
    23 lutego 2011 - 08:46 | Permalink

    @Zyx
    ZF sztan­da­ro­wym przy­kła­dem jest $row->find<Tabela>Via<Posredniczaca>By<CosTam>And<CosTam> przy zapy­ta­niach w rela­cjach wiele do wielu.
    Praw­do­po­dob­nie twórcy PHP rodzinę __call trak­tują jako następny krok „uela­stycz­nia­nia języka”. Dla każdego coś miłego. Domy­ślam się, że ktoś czyta­jący magiczny kod pozba­wiony komen­ta­rzy powi­nien nie mieć w pobliżu żadnych ostrych przedmiotów.

  • Dodaj komentarz

    Twój adres e-mail nie zostanie opublikowany.

    Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <p> <pre lang="" line="" escaped=""> <q cite=""> <strike> <strong>