FW_Form einsatzbereit!

Die letzte Version von FW_Form konnte noch nicht viel. Es war zwar möglich, Formulare zu generieren, aber das wars dann auch schon. Die neue und aktuelle Version kann Formulare jetzt überprüfen und Fehler anzeigen. Klar, es ist noch nicht alles 100%ig fertig und perfekt, aber ein Anfang ist es auf jeden Fall!

Ich habe hier alle beteiligten Klassen in einem Zip-Archiv für euch zusammengestellt und würde mich über Feedback freuen! Download von fw_form_validator2.zip

Was können die Klassen momentan alles?

  • Man kann einem Formular unendlich viele Elemente (Eingabefelder, Auwahlmenüs, Buttons, …) hinzufügen und die Reihenfolge der Ausgabe bestimmen.
  • Man kann jedem dieser Elemente sogenannte Decorators anhängen. Dekoratoren sind Objekte, die z.B. einen Umbruch nach einem Element erzeugen. Der meistverwendete Dekorator ist wahrscheinlich FW_Label. Mit ihm kann man Labels (Beschriftungen) für Formularelemente definieren.
  • Jedes Element kann eine ungebrenzte Menge an Validatoren erhalten. Dabei ist es möglich, jedem Validator einen Text mitzugeben, der im Fehlerfall ausgegeben wird.
  • FW_Form erkennt von alleine, mit welcher Methode die Daten übertragen wurden (GET/POST) und gibt dementsprechend die Daten an die Validatoren weiter. So muss der Anwender der Klasse sich nicht darum kümmern und kann die Übertragungsart in wenigen Sekunden ändern.
  • FW_Form ist in der Lage, die Fehlermeldungen an die entsprechenden Stellen im Formular einzufügen. Das ist aber nicht Standart, kann aber durch einen einfachen Methodenaufruf bewerkstelligt werden. Alternativ kann FW_Form auch ein Array mit allen Fehlermeldungen zurückgeben und der Anwender der Klasse kann die Fehler selbstständig bearbeiten.

Ein kleines Beispiel

Ich habe hier ein kleines Beispiel für euch, falls ihr euch das noch nicht so richtig vorstellen könnt.

< ?php
class Controller_User extends FW_Controller_Abstract
{  
  private function getRegisterForm()
  {
    $form = new FW_Form("register", "post", FW_Tools::getInternalUrl("user", "register2"));
    $form->setParameter("id", "register");
    $form->addElement(new FW_Form_Element_Text("username"));                                     
    $form->addElement(new FW_Form_Element_Submit("submit_button", "Absenden"));
    
    $form->getElementByName("username")->setID("loel")->addDecorator(new FW_Form_Decorator_Label("Username"));
    $form->getElementByName("username")->addValidator(new FW_Validator_Length(array("min" => 3, "max" => 10)), "Username hat die falsche Länge");
   
    return $form;
  }
  
  public function register_Action()
  {     
    $this->view->addTemplateContent("content", array("errors" => null, "form" => $this->getRegisterForm()), "user/register.tpl.php");
  }
  
  public function register2_Action()
  {
    $form = $this->getRegisterForm();
    if(!$form->validate())
    {
      $form->addErrorMessages();
    }
    $this->view->addTemplateContent("content", array("errors" => $form->getErrors(), "form" => $form), "user/register.tpl.php");
  }
}


Es handelt sich um ein Registierungsformular mit nur einem einzigen Element: Ein Eingabefeld.
Die 3 Methoden machen folgendes:

  • getRegisterForm() liefert das Objekt des Formulars zurück. Das ist in einer seperaten Methode, weil das Formular sonst 2x definiert werden müsste.
  • register_Action() fügt das Formular dem View hinzu, um es auszugeben.
  • register2_Action() ist für die Überprüfung und Fehlerausgabe zuständig

Interessant ist hier vorallem diese Zeile (getRegisterForm()):

$form->getElementByName("username")->addValidator(new FW_Validator_Length(array("min" => 3, "max" => 10)), "Username hat die falsche Länge");

Per getElementByName wird das Eingabefeld „username“ zur Bearbeitung angefordert. Danach wird der Validator FW_Validator_Length mit einer Mindestlänge von 3 Zeichen und einer Maximallänge von 10 Zeichen eingehängt. Der letzte Parameter von addValidator() ist der Text, der im Fehlerfall ausgegeben wird.

Ein bisschen Code
Hier noch die wichtigsten Klassen für euch!

(FW_Form)

class FW_Form
{
  protected $parameters = array();
  protected $elements = array();
  
  protected $errors = array();
  
  public function __construct($name, $method, $action)
  {
     if(strtolower($method) != "post" && strtolower($method) != "get")
     {
       throw new FW_Form_Exception("Der 2. Parameter des Konstruktors muss get oder post sein");
     }
     $this->parameters["name"] = $name;
     $this->parameters["method"] = $method;
     $this->parameters["action"] = $action;
  }
  
  public function setParameter($name, $value)
  {
    $name = strtolower($name);
    $this->parameters[$name] = $value;
  }
  
  public function getParameter($name)
  {
    $name = strtolower($name);
    
    if(isset($this->parameters[$name]))
    {
      return $this->parameters[$name];
    }
    return null;    
  }
  
  
  public function addElement(FW_Form_Element_Abstract $element, $position = null)
  {
    if($position === null || !is_int($position))
    {
      $this->elements[] = $element;
    }
    else
    {
      $this->elements[$position] = $element;
    }
    return $this;
  }
  
  public function getElementByPosition($position)
  {
    if(is_int($position) && isset($this->elements[$position]))
    {
      return $this->elements[$position];
    }
    else
    {
      throw new FW_Form_Exception("Diese Position hat einen ungültigen Wert");
    }
  }
  
  public function getElementByName($name)
  {
    foreach($this->elements as $element)
    {
      if($name == $element->getName())
      {
        return $element;
      }
    }
    return null;
  }
  
  public function validate()
  {
    $error_occured = false;
    foreach($this->elements as $element)
    {
      if(!$element->validate($this))
      {
        $this->errors[$element->getName()] = $element->getErrors();
        $error_occured = true;
      }
    }
    return !$error_occured;
  }
  
  public function getErrors()
  {
    return $this->errors;
  }
  
  public function addErrorMessages()
  {
    //Alle Elemente des Formulars durchlaufen
    foreach($this->elements as $element)
    {
      //Gibt es Fehler in diesem Element?
      if(isset($this->errors[$element->getName()]))
      {
          //Alle Fehler in diesem Element durchlaufen
          foreach($this->errors[$element->getName()] as $error)
          {
            //Fehler hinzufügen: FW_Form_Decorator_Error
            $element->addDecorator(new FW_Form_Decorator_Error($error));
          }
      }
    }
  }
  
  public function __toString()
  {
    //ksort ordnet das Array PHP-intern nach dem Index
    ksort($this->elements);
    
    $html = "
parameters as $name => $value) { $html .= " ".$name."='".$value."'"; } $html .= ">"; foreach($this->elements as $k => $element) { $html .= "\n ".$element->getHTML()."\n"; } $html .="
"; return $html; } }

(FW_Form_Element_Abstract)

abstract class FW_Form_Element_Abstract
{
  protected $validators = array();
  protected $decorators = array();
  protected $parameters = array();
  protected $error_msg = array();
  
  public function addValidator(FW_Validator_Abstract $validator, $error_msg)
  {
    $this->validators[] = array("validator" => $validator, "error_msg" => $error_msg);    
    return $this;
  }

  final public function getHTML()
  {
    $html = "";
    foreach($this->decorators as $decorator)
    {
      $html .= $decorator->getHTMlBefore($this);
    }
    $html .= $this->getHTMLofElement();
    foreach($this->decorators as $decorator)
    {
      $html .= $decorator->getHTMlAfter($this);
    }
    return $html; 
  }
  
  abstract public function getHTMLofElement();
  
  final public function validate($form_object)
  {
    $error_occured = false;
    foreach($this->validators as $validator)
    {
      $validator["validator"]->setValue("to_check", $this->getElementValue($form_object->getParameter("method")));
      if(!$validator["validator"]->isValid())
      {
        $this->error_msg[] = $validator["error_msg"];
        $error_occured = true;
      }
    }
    return !$error_occured;
  } 
  
  public function getErrors()
  {
    return $this->error_msg;
  }
  
  public function getElementValue($method)
  {
    $request_obj = FW_Http_Request::getInstance();
    
    switch($method)
    {
      case "post": return $request_obj->getPost($this->parameters["name"]);
      case "get": return $request_obj->getGet($this->parameters["name"]);
    }
    return $request_obj->getPost($this->parameters["name"]);
  }
  
  public function setParameter($name, $value)
  {
    $this->parameters[$name] = $value;
    return $this;
  }
  
  public function setName($name)
  {
    $this->setParameter("name", $name);
    return $this;
  }
  
  public function getName()
  {
    return $this->getParameter("name");
  }
  
  public function getID()
  {
    return $this->getParameter("id");
  }
  
  public function setID($id)
  {
    $this->setParameter("id", $id);
    return $this;
  }
  
  public function setValue($value)
  {
    $this->setParameter("value", $value);
    return $this;
  }
  
  public function getValue()
  {
    return $this->getParameter("value");
  }
  
  public function getParameter($name)
  {
     return (isset($this->parameters[$name])) ? $this->parameters[$name] : null;
  }
  
  protected function getFurtherParameters()
  {
    $html = "";
    foreach($this->parameters as $name => $value)
    {
      $html .= " ".$name."='".$value."'";
    }
    return $html;
  }
  
  public function addDecorator(FW_Form_Decorator_Abstract $decorator, $amount = 1)
  {
    for($i = 0; $i < (int)$amount; $i++)
    {
      $this->decorators[] = $decorator;
    }  
    return $this;
  }
}

(FW_Validator_Abstract)

abstract class FW_Validator_Abstract
{
  protected $validators = array();
  protected $values = array();
  
  final public function isValid()
  {
    if(!isset($this->values["to_check"]))
    {      
      throw new FW_Validator_Exception("Der Wert 'to_check' muss angegeben werden und das zu überprüfende Element enthalten.");
    }
    
    
    //Erst prüfen, ob die hinzugefügten Validatoren alle true zurückgeben...
    foreach((array)$this->validators as $validator)
    {
      //... wenn nicht, abbruch der Schleife und Rückgabe von false
      if(!$validator->isValid())
      {
        return false;
      }
    }
  
  //Wenn die bisherigen Validatoren alle true ergaben, entscheidet der letzte (der "Elternvalidator"),
  //ob die Prüfung true oder false ergibt.
   return $this->isThisValid();
  }
  
  final public function addValidator(FW_Validator_Abstract $validator)
  {
    $validator->setValues($this->values);
    $this->validators[] = $validator;
  }
  
  abstract public function isThisValid();
  
  final public function setValues(array $values = array())
  {
    $this->values = array_merge($values, $this->values);
    //bei allen hinzugefügten validatoren werte mitteilen
    foreach((array)$this->validators as $validator)
    {
      $validator->setValues($this->values);
    }    
    return $this;
  }  
  
  final public function setValue($name, $value)
  {
    $this->values[$name] = $value;
    foreach((array)$this->validators as $validator)
    {
      $validator->setValue($name, $value);
    }
    return $this;
  }
  
  final public function __construct(array $values = array())
  {
    try
    {
      $this->setValues($values);
    }
    catch(FW_Validator_Exception $e)
    {
      throw $e;
    }
    $this->init();
  }  
  
  public function init()
  {
  }
}

Zukunftspläne
Wie schon oben gesagt, bin ich mit manchen Sachen noch nicht ganz zufrieden.

  • Fehlertext wird im Element selbst abgelegt. Ich bin mir nicht sicher, ob das nicht im Validator besser aufgehoben wäre…
  • Man kann noch keinen eigenen Decorator für Fehler festlegen. Das ist aber kein großes Problem und wird bald ausgebessert.
  • Fehlermeldung kann nicht aus anderen Quellen ausgelesen werden (Was bei Mehrsprachigkeit nützlich wäre)
  • Es gibt noch keine Elementübergreifenden Validatoren, die z.B. Felder auf Gleichheit prüfen (Bei Passwort wichtig)

Eure Meinung
ist gefragt! In den Kommentaren 🙂

1 Star2 Stars3 Stars4 Stars5 Stars (Wurde noch nicht bewertet)
Loading...


12 Kommentare zu “FW_Form einsatzbereit!”

  1. […] FW_Form einsatzbereit! […]

  2. Hallo Simon,

    ich experimentiere gerade mit der Formular Klasse.
    Das Testen macht echt Spass. 🙂

    Hast Du schon einige der Zukunftspläne umgesetzt?

    Gruß
    Magic

  3. Hi,

    ich habe ein wenig weitergemacht, aber noch nicht alles fertig. Demnächst gibt es eine neue Version zum Downloaden hier!

    Falls du noch Ideen für die Klasse hast, nur her damit? 😉 🙂

    Gruß
    Simon

  4. Sobald Du etwas Neues hast – nur her damit. Ich lechze nach neuem Stoff zum Testen. 😉

    Magic

  5. Du könntest ja die ACL testen 😉
    http://www.net-developers.de/2008/12/23/erste-version-der-acl-access-control-list/
    http://www.net-developers.de/2009/02/24/anderungen-in-der-acl-meines-frameworks/

    Vorallem bei der Vererbung bin ich mir nicht sicher, ob ich wirklich alle Fälle durchgetestet habe und ob auch alles funktioniert.

    Wenn du Zeit und Lust hast, kannst du dir das ja mal anschauen und Feedback dazu geben.

    Simon

  6. Vielleicht passt es nicht gerade in dieses Thema, aber ich hatte gerade einen eventuellen „Bug“ mit der Controller/Base.class.php.

    Wenn man in der URL eine action (also z.B. index.php?module=test&action=nichtExistent) aufruft, die nicht existiert wird eine PHP Fehlermeldung ausgegeben. Sollte man die nicht über den Debugger abfangen?! Bei einem nicht existenten Modul greift der eingebaute Debugger im Frontcontroller.

    Was meinst Du?

    Magic

  7. Danke für den Bugreport. 🙂
    Habe es gerade getestet und du hast Recht! So kann das nicht bleiben.

    Ich schreibe es auf meine ToDo-Liste. Wird so schnell wie möglich geändert…

    Simon

  8. Hallo Simon,

    ist der Bug eigentlich schon gefixt?

    Beste Grüsse
    Magic

  9. Hi,

    ja, der Bug ist behoben.
    Es wird im Frontcontroller geprüft, ob die Methode aufrufbar ist. Andernfalls wird ein Error 404 erzeugt.

    MfG
    Simon

  10. Oh, super.

    Dann mache ich mal dran und teste weiter.
    Vielen Dank.

    PS: Toll auch die SVN-Sache.

    Gruss
    Magic

  11. Viel Spass 🙂

    Und danke für dein Lob!
    In 2 Wochen hab ich Ferien und somit wieder Zeit für das FW. Dann wird sich auch im SVN was tun

  12. Die aktuellste Version meines HMVC-Frameworks erhaltet ihr ab sofort immer hier: http://www.net-developers.de/blog/2011/02/13/download-info-shfw-hmvc-framework-in-php/

Hinterlasse einen Kommentar!

Time limit is exhausted. Please reload the CAPTCHA.

»Informationen zum Artikel

Autor: Simon
Datum: 14.03.2009
Zeit: 00:21 Uhr
Kategorien: Mein MVC-Framework
Gelesen: 5433x heute: 2x

Kommentare: RSS 2.0.
Diesen Artikel kommentieren oder einen Trackback senden.

»Meta