php.de

Zurück   php.de > php.de Intern > Wiki Diskussionsforum > Tutorials

Tutorials Hier findest Du Tutorials, welche nach und nach ein fertiges Script ergeben. Sehen, lernen & verstehen!

Antwort
 
LinkBack Themen-Optionen Thema bewerten
Alt 14.09.2009, 00:22  
Moderator
 
Benutzerbild von Chriz
 
Registriert seit: 11.05.2008
Beiträge: 6.069
Chriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer Anblick
Standard Anregungen zu PHP und OR-Mapping: Teil 1: Daten-Objekte

Hallo,

ich greife das Thema OR-Mapping mal wieder auf, da ich einige Verbesserungen zum vorherigen Thread:
http://www.php.de/tutorials/54558-tu...l-und-oop.html (Tutorial: PHP/MySQL und OOP)


vorgenommen habe. Dabei unterteile ich wieder in
  • Daten-Objekte
  • Daten-Listen
  • Daten auslesen und speichern
  • PHP-Quelltext generieren

Bei den Daten-Objekten geht es wieder darum, unabhängig von der Datenquelle (CSV, Datenbank, Formular, ..) Objekte zu erzeugen, mit denen in der Anwendung gearbeitet werden kann. Da wir in einigen Fällen gleich mehrere Daten-Objekte haben (bei Such-Ergebnissen, Übersichts-Seiten, ..), gehe ich kurz auf Objekte ein, die diese Daten-Objekte wiederum kapseln und als Listen zur Verfügung stellen. Danach reden wir darüber, woher die Daten-Objekte ihre Daten erhalten, wo sie gespeichert werden und wie das ganze einfach austausch- und filterbar gemacht wird. Am Ende erfolgt ein kleiner Ausblick darauf, wie man sich diese Muster generieren lassen kann.

Daten-Objekte

Unsere Daten-Objekte kapseln die variablen Daten unserer Anwendung, kennen ihre Datenquelle jedoch nicht. Das ist wichtig, damit man die Datenquelle wechseln kann. Man mag meinen, dass das seltenst der Fall ist, aber ein Formular und eine Datenbank sind bereits verschiedene Datenquellen und von beiden (und noch anderen) soll unser Daten-Objekt gefüllt werden können.
In der prozeduralen Programmierung schreiben wir die Daten oft in einen Array:

PHP-Code:
<?php
array("nickname" => "Weihnachtsmann""birthDate" => "24.12.1931");
Das ist schnell und einfach. Der Nachteil an dieser Art der Datenvorlage ist, dass sie nicht verlässlich ist. Es kann sein, dass der Schlüssel "nickname" fehlt, dass das Geburtsdatum ungültig oder garnicht vorhanden ist. Das Problem ist hierbei, dass der Array oftmals nachträglich erweitert, weitergereicht und bearbeitet wird, so dass man nicht mehr sicher sein kann, welche Schlüssel da sind, welche Werte noch gültig sind, auch wenn die Daten es ursprünglich noch waren. Darum benötigen wir ein Format, in dem garantiert Schlüssel verfügbar sind und deren Daten garantiert valide sind. Deshalb erstellen wir mal eine Klasse User, die erstmal folgende vier Methoden enthält:
PHP-Code:
<?php
class User {
  public function 
setNickname($nickname);
  public function 
getNickname();

  public function 
setBirthDate($birthDate);
  public function 
getBirthDate();
}
Unseren Weihnachtsmann erstellen wir dann also so:
PHP-Code:
<?php
$user 
= new User();
$user->setNickname("Weihnachtsmann");
$user->setBirthDate("24.12.1931");
?>
Jeder, der unser $user-Objekt nun in die Finger bekommt, kann sicher sein, dass er mit setNickname() einen Nickname setzen und mit getNickname() diesen wieder auslesen kann. Und eine moderne IDE (z.B. Eclipse PDT) stellt bei Verwendung des $user-Objektes sogar eine Autovervollständigung der Methodennamen bereit.

Bleibt also noch die Frage nach den Daten und der Validierung. Es kann natürlich sein, das $user noch gar keinen Nickname zugewiesen bekommen hat. Man mag meinen, dass ein User immer einen Nickname haben muss und dieser Wert also an den Konstruktor übergeben werden sollte, aber man muss da sehr vorsichtig sein. Erinnern wir uns daran, dass die Datenquelle unbekannt ist, also könnte der User auch aus einem Formular stammen. Vergisst der Surfer hier die Eingabe des Feldes, wollen wir trotzdem ein User-Objekt haben, um damit das Formular erneut füllen zu können. Oder aber wir haben anonyme Gast-Benutzer. Eine ganz andere Frage ist es, ob der Benutzer einen Nicknamen haben muss, bevor er in unserer Datenbank verewigt wird. Da unser Daten-Objekt aber niemals selbst Kontakt zur Datenquelle aufnimmt, ist diese Prüfung sicherlich nicht Aufgabe des Daten-Objektes. Genauso wenig wie das Prüfen auf einen doppelte belegten Nicknamen. Das klären wir dann im 3. Kapitel. Da die Information, ob ein Nickname vorliegt, trotzdem wichtig ist, fügen wir der User-Klasse noch zwei weitere Methoden hinzu:
PHP-Code:
<?php
class User {
  public function 
setNickname($nickname);
  public function 
getNickname();
  public function 
hasNickname(); // neu

  
public function setBirthDate($birthDate);
  public function 
getBirthDate();
  public function 
hasBirthDate(); // neu
}
So haben wir eine einfache Möglichkeit zu prüfen, ob ein Nickname oder Geburtsdatum vorhanden ist. Es ist wichtig, diese Funktionalität in unsere User-Klasse zu packen und dies nicht nach ausserhalb zu verlegen:
PHP-Code:
<?php
$nickname 
$user->getNickname();
if (
mb_strlen($nickname) < 3) {
  
// has no nickname
}
Warum? Erstens, weil diese Prüfung dann vermutlich bei jedem Zugriff auf unser $user-Objekt durchgeführt werden müsste, somit doppelter Code erzeugt wird und zweitens wir Implementierungs- oder Validierungs-Details kennen müssen. Hier speziell die Mindestlänge des Nicknamen. Wenn wir diese nachträglich ändern wollten, müssten wir das überall verstreut im Code tun. Gerade das wollen wir ja aber vermeiden.

Entsprechend reicht uns die einfache Aussage hasNickname() nicht aus. Wir wollen wissen, ob eine mögliche Eingabe valide ist. Ich erwähne hier noch kurz Konventionen zur Schreibweise und Funktionalität:
set*() Methoden sind Setter-Methoden, deren Wert als Parameter übergeben wird und die selbst keinen Rückgabewert haben.
get*() Methoden sind Getter-Methoden, deren Rückgabewert valide Objekteigenschaften sind. Hinzu kommen die boolschen Getter has*(), is*() und gegebenenfalls can*().
Wem aufgefallen ist, dass wir scheinbar anstandslos Eigenschaften setzen, aber plötzlich valide Eigenschaften auslesen können, hat gut aufgepasst und mag sich noch etwas gedulden.

Denn was unserer User-Klasse noch fehlt und was der entscheidende Unterschied zum Array ist, ist eine Validerung und Filterung der Daten. Das Erreichen wir mit Validierungs-Filtern. Diese sagen aus, ob $nickname ein gültiger Nickname ist und ob $birthDate auch ein Datum ist, das wir akzeptieren.

Ein weiteres mal erweitern wir unsere User-Klasse:
PHP-Code:
<?php
class User {
  public function 
setNickname($nickname);
  public function 
getNickname();
  public function 
hasNickname();
  public function 
isValidNickname($nickname); // neu

  
public function setBirthDate($birthDate);
  public function 
getBirthDate();
  public function 
hasBirthDate();
  public function 
isValidBirthDate($birthDate); // neu
}
Nun haben wir eine Funktionalität um Eingabewerte zu überprüfen, bevor wir sie setzen:
PHP-Code:
<?php
$nickname 
$_POST["nickname"];
$birthDate $_POST["birthDate"];
$user = new User();
if (
$user->isValidNickname($nickname)) {
  
$user->setNickname($nickname);
}
if (
$user->isValidBirthDate($birthDate)) {
  
$user->setBirthDate($birthDate);
}
Drei Fragen drängen sich auf:
Warum ist isValid*() nicht statisch?
Was passiert beim Weglassen der isValid*() Prüfung?
Wie kommt man an Informationen über die verwendeten Validierungs-Filter heran?

Warum ist isValid*() nicht statisch?
Eine Validierung ist nicht zwingend User-unabhängig. Daten untereinander können sich widersprechen und das möchten wir vermeiden. Wenn ich angebe vor 25 Jahren geboren worden zu sein, kann ich nicht behaupten seit 20 Jahren den Führerschein zu besitzen. Oftmals wird solch eine Querprüfung nicht benötigt, aber ein statischer Aufruf würde uns zumindest die Möglichkeit dafür nehmen.

Was passiert beim Weglassen der isValid*() Prüfung?
Ganz einfach: Der Programmierer hat einen Fehler gemacht und somit tritt ein, was eintreten muss: Es wird eine Exception geworfen, denn wir hatten ja vereinbart, dass nur gültige Werte mit set*() gesetzt werden dürfen. Wenn der Programmierer die Prüfung nun unterschlagen hat, stellt dies einen Fehler (eine Ausnahme) dar und die Ausführung muss an dieser Stelle unterbrochen werden. Schließlich hätten wir fast nichts gewonnen, wenn $user nun plötzlich nicht-valide Daten annehmen würde.

Wie kommt man an Informationen über die verwendeten Validierungs-Filter heran?
Diese Frage wird oft unterschlagen, es ist aber wichtig zu wissen, warum eine Validierung/Filterung fehlgeschlagen ist. Sei es um den Surfer, der das Formular falsch ausgefüllt hat darüber zu informieren, oder um selbst festzustellen, welches Element beim CSV-Import ungültig ist, um es möglicherweise manuell zu korrigieren oder einen Validierungs-Filter anzupassen. Kurz gesagt, wir verwenden für die Validierung/Filterung selbst wieder Objekte und diese können wir auch bei Bedarf dem Benutzer zur Verfügung stellen:
PHP-Code:
<?php
class User {
  public function 
setNickname($nickname);
  public function 
getNickname();
  public function 
hasNickname();
  public function 
isValidNickname($nickname);
  public function 
getValidateFilterNickname(); // neu

  
public function setBirthDate($birthDate);
  public function 
getBirthDate();
  public function 
hasBirthDate();
  public function 
isValidBirthDate($birthDate);
  public function 
getValidateFilterBirthDate(); // neu
}
Mithilfe des ValidateFilter-Objektes können wir auslesen, welche Eingabe erforderlich ist und es entsprechend in einer Fehlermeldung vermerken. Bevor ich dazu weiter aushole, möchte ich den vorerst letzten, wichtigen Vorteil der Daten-Objekte vorstellen, um danach ungestört die Implementierung der User-Klasse und der Validierungs-Filter erklären zu können:
Wir können nämlich im Daten-Objekt Aufgaben durchführen, die wir in einem Array nicht oder nur als dessen Ergebnis unterbringen könnten. Beispielsweise die Berechnung des Alters für unseren User. Selbstverständlich können wir die Information auch in unserem Array hinterlegen, aber erstens können dadurch inkonsistente Daten entstehen:
PHP-Code:
<?php
array("birthDate" => "24.12.1931""age" => 12);
und wir können diese Information nicht parametrisieren oder cachen. Denn was wäre, wenn wir selbst bestimmen möchten, zu welchem Referenzdatum der User Geburtstag hat, zum Beispiel wenn wir wissen möchten, ob der Benutzer morgen Geburtstag hat, um es seinen Freunden mitzuteilen? Hier ist unser Array langsam überfordert, Funktionalitäten auf einer gemeinsamen Datenmenge würden verteilt (get_user_data(), has_user_birthday()). Bei uns hingegen bleibt alles wo es hingehört und wo man es spontan vermutet, auch ohne Hintergrundwissen: In unsere User-Klasse.
PHP-Code:
<?php
class User {
  public function 
setNickname($nickname);
  public function 
getNickname();
  public function 
hasNickname();
  public function 
isValidNickname($nickname);
  public function 
getValidateFilterNickname();

  public function 
setBirthDate($birthDate);
  public function 
getBirthDate();
  public function 
hasBirthDate();
  public function 
isValidBirthDate($birthDate);
  public function 
getValidateFilterBirthDate();
  public function 
getAge($when "today"); // neu
  
public function hasBirthDay($when "today"); // neu
}
Neben der zugesicherten Schnittstelle, der Validierung und Filterung also unser nächster Vorteil: Wiederkehrende Funktionalitäten auf gemeinsamen Daten werden direkt in der Daten-Klasse untergebracht (nennt sich OOP ). Erweitern wir später unsere User-Klasse, ist die Änderung garantiert überall dort verfügbar und einsetzbar, wo wir bereits mit Daten-Objekten arbeiten (im Gegensatz zum Array). Wir müssen sie nur noch verwenden.

Bevor ich jetzt tatsächlich zur kompletten Implementierung der User-Klasse komme, muss ich Validatoren und Filter genauer erklären.

Validatoren und Filter

Meine ValidateFilter sind intern Arrays, die Objekte (Validator- und Filter-Objekte) zusammenfassen (siehe 2. Kapitel) und deren Implementierung ich deshalb nicht vorweg nehmen kann. Ein ValidateFilter-Objekt hat zwei essentielle Methoden: validate() und filter().

validate() auf der einen Seite validiert lediglich, dies kommt bei den isValid*() Methoden zum Zug. Wie der Name der Methode schon andeutet (is*()) liefert die Methode nur TRUE oder FALSE. Natürlich werden hierbei die Filter auch miteinbezogen, da bei einer gemischten Kombination (wie es üblich ist) ein Validate-Objekt auf die vorhergehende Filter-Operation angewiesen ist (z.B. wenn ein deutsches Eingabedatum (d.m.Y) in ein normiertes (Y-m-d) umgewandelt wurde, um letztlich die Validität des Datums überprüfen zu können). Das hat die Konsequenzen, dass ein Filter darauf angewiesen ist, dass das vorherige Validate-Objekt eine bestimmte Datengrundlage garantiert. Bleiben wir beim Datum: wenn also das deutsche Datum beim "." aufgetrennt wird und mit "-" wieder umgedreht zusammengesetzt wird, muss auch garantiert sein, dass das Datum auch wirklich im "d.m.Y"-Format vorliegt. Das hat die Konsequenz, dass bereits beim ersten FALSE eines Validators, die gesamte validate() Methode abbricht und FALSE zurückliefert. Weiterhin ist garantiert, dass die Methode keine Exceptions wirft, so dass man bei einer "fairen" Vorprüfung auch nicht damit rechnen muss, dass eine fehlerhafte Eingabe das Skript bis zum nächsten (try-)catch abbricht.

filter() wiederum liefert üblicherweise kein TRUE oder FALSE, sondern liefert den gefilterten und validierten Eingabewert, durchläuft also selbst wiederum alle Validatoren und Filter. Das hat zur Folge, dass nicht valide Eingaben mit einer Exception abgebrochen werden müssen. Darum wird filter() auch nur durch die set*() Methoden aufgerufen, da bei diesen davon ausgegangen wird, dass validate() bereits erfolgreich durchlaufen wurde. Aufgrund der möglichen Umformungen innerhalb des ValidateFilter-Objektes durch filter() kann eine Eingabe deshalb auch nicht teilweise gefiltert zurückgeliefert werden, um "fast" richtige Eingaben schonmal "fast" vorzuformatieren. Denken wir nur daran, was passiert wenn man das Datum "32.12.2009" angibt, die ersten Filter für das Datum durchlaufen sind, der String ins normierte Format gebracht wurde und dann festgestellt wird, der 32.12. existiert garnicht? Sollte dann 2009-12-32 zurückgeliefert werden? Sicherlich nicht.

Hier die zusammengefasste Implementierung von Anti_ValidateFilter_String_Date:
PHP-Code:
<?php
class Anti_ValidateFilter_String_Date /* extends .. implements .. */
{
    public function 
__construct($trim true)
    {
        
$this->push("is_string"); // PHP-Funktion als Validator
        
if ($trim) {
            
$this->push("trim"); // .. als Filter
        
}
        
$this->push(new Anti_Validate_String_Date()); // eigene Klasse als Validator
        
$this->push(new Anti_Filter_String_Date()); // .. als Filter
    
}
    
    public function 
validate($value)
    {
        return 
$this->_invoke($valuetrue);
    }

    public function 
filter($value)
    {
        return 
$this->_invoke($valuefalse);
    }

    protected function 
_invoke($value$validate)
    {
        
// $this als Array durch das SPL Interface Iterator (siehe 2. Kapitel)
        
foreach ($this as $element) {
            if (
$element instanceof Anti_Validate_Interface) {
                if (
$validate) { // Aufruf durch validate()
                    
if (!$element->isValid($value)) {
                        return 
false;
                    }
                } else { 
// Aufruf durch filter()
                    
$element->check($value);
                }
                continue;
            }
            if (
$element instanceof Anti_Filter_Interface) {
                
$value $element->filter($value);
                continue;
            }
            if (
is_string($element) && is_callable($element)) {
                if (
strpos($element"is_") === 0) { // z.B. is_string()
                    
if (!call_user_func($element$value)) {
                        return 
false;
                    }
                } else { 
// z.B. trim()
                    
$value call_user_func($element$value);
                }
                continue;
            }
            if (
$element instanceof Anti_ValidateFilter_Interface) {
                if (
$validate) {
                    if (!
$element->validate($value)) {
                        return 
false;
                    }
                } else {
                    
$value $element->filter($value);
                }
                continue;
            }
        }
        return 
$validate
             
true
             
$value;
    }
}
Die verwendeten Validate- und Filter-Klassen:
PHP-Code:
<?php
class Anti_Validate_String_Date /* implements .. */
{
    public function 
check($value)
    {
        if (!
$this->isValid($value)) {
            
$exception = new Anti_Validate_Exception("invalid date");
            
$exception->setValidateObject($this);
            
$exception->setValidateValue($value);
            throw 
$exception;
        }
    }

    public function 
isValid($value)
    {
        return 
strtotime($value) !== false;
    }
}
PHP-Code:
<?php
class Anti_Filter_String_Date /* implements .. */
{
    public function 
filter($value)
    {
        return new 
Anti_DateTime($value);
    }
}
Um die Funktionalität erklären zu können, nun noch die zusammengefasste User-Klasse:
PHP-Code:
<?php
class Anti_User /* extends .. */
{
    protected 
$_nickname;
    protected 
$_birthDate;
    protected 
$_validateFilters = array();

    public function 
__construct()
    {
        
$this->_setupValidateFilters();
    }

    public function 
setNickname($nickname)
    {
        
$this->_nickname $this->_filter(compact("nickname"));
    }

    public function 
getNickname()
    {
        return 
$this->_nickname;
    }

    public function 
hasNickname()
    {
        return 
$this->isValidNickname($this->_nickname);
    }

    public function 
isValidNickname($nickname)
    {
        return 
$this->_validate(compact("nickname"));
    }

    public function 
getValidateFilterNickname()
    {
        return 
$this->_getValidateFilter("nickname");
    }

    public function 
setBirthDate($birthDate)
    {
        
$this->_birthDate $this->_filter(compact("birthDate"));
    }

    public function 
getBirthDate()
    {
        return 
$this->_birthDate;
    }

    public function 
hasBirthDate()
    {
        return 
$this->_birthDate instanceof Anti_DateTime;
    }

    public function 
isValidBirthDate($birthDate)
    {
        return 
$this->_validate(compact("birthDate"));
    }

    public function 
getValidateFilterBirthDate()
    {
        return 
$this->_getValidateFilter("birthDate");
    }

    public function 
hasBirthDay($when "today")
    {
        
// ..
    
}

    public function 
getAge($when "today")
    {
        
// ..
    
}

    protected function 
_setupValidateFilters()
    {
        
$this->_setupValidateFilterNickname();
        
$this->_setupValidateFilterBirthDate();
    }

    protected function 
_setupValidateFilterNickname()
    {
        
$this->_setValidateFilter("nickname",  new Anti_ValidateFilter_String(true250));
    }

    protected function 
_setupValidateFilterBirthDate()
    {
        
$this->_setValidateFilter("birthDate", new Anti_ValidateFilter_String_Date());
    }

    protected function 
_validate(array $property)
    {
        
$value    current($property);
        
$property key($property);

        if (!
array_key_exists($property$this->_validateFilters)) {
            throw new 
Anti_Exception_InvalidArgument("property <var>$property</var> has no validator");
        }
        return 
$this->_validateFilters[$property]->validate($value);
    }

    protected function 
_filter(array $property)
    {
        
$value    current($property);
        
$property key($property);

        if (!
array_key_exists($property$this->_validateFilters)) {
            throw new 
Anti_Exception_InvalidArgument("property <var>$property</var> has no filter");
        }
        return 
$this->_validateFilters[$property]->filter($value);
    }

    final protected function 
_setValidateFilter($propertyAnti_ValidateFilter_Interface $validateFilter)
    {
        
$this->_validateFilters[$property] = $validateFilter;
    }

    final protected function 
_getValidateFilter($property)
    {
        return 
array_key_exists($property$this->_validateFilters)
             ? 
$this->_validateFilters[$property]
             : 
null;
    }
}
Gehen wir das ganze an folgendem, fehlerhaften Skript durch:
PHP-Code:
<?php
$birthDate 
"2009-12-32";
$user = new User();
if (!
$user->isValidBirthDate($birthDate)) {
  
$user->setBirthDate($birthDate);
}
Wir testen also, ob das Geburtsdatum im normierten Format (der Einfachheit halber), das eindeutig nicht gültig ist, valide ist und wenn nicht (hier der absichtliche Fehler) möchten wir das Datum an setBirthDate() übergeben.

Zunächst wird der Konstruktor der User-Klasse (__construct()) aufgerufen, der unsere ValidateFilter erstellt. Das heißt diese werden in der geschützten User-Eigenschaft $_valideFilters abgelegt. Für unser Geburtsdatum wurde der ValidateFilter Anti_ValidateFilter_String_Date instanziert, der die PHP-internen Funktionen is_string() und trim() als Validatoren bzw. Filter verwendet und zusätzlich eine Anti_Validate_String_Date-Klasse als Validator und eine Anti_Filter_String_Date-Klasse als Filter. Beim Aufruf von $user->isValidBirthDate("2009-12-32") wird nun die validate()-Methode bzw. die interne Methode _invoke("2009-12-32", true) von Anti_ValidateFilter_String_Date aufgerufen und die Validierung kann beginnen. Hierbei wird bei _invoke() $this in foreach verwendet, denkt euch dafür, wenn ihr das Interface Iterator noch nicht kennt, einfach einen Array mit den an push() übergebenen Elementen:
array("is_string", "trim", new Anti_Validate_String_Date(), new Anti_Filter_String_Date())

Unsere ersten beiden Elemente kommen im 3. if-Block zum Zug (is_callback). Dabei wird geprüft, ob der Funktionsname ein String und aufrufbar ist. Beides ist jeweils der Fall. "is_string" wurde mit dem "is_"-Test als Validator erkannt und wird auf die Eingabe losgelassen. Sie ist nicht FALSE (is_string("2009-12-32") ist ja TRUE), also landet PHP schließlich beim continue, die Validierung kann weitergehen. "trim" landet ebenfalls im 3. if-Block des foreach() und wird als Filter erkannt, da "trim" nicht mit "is_" beginnt. Daher wird trim("2009-12-32") ausgeführt und liefert den selben Wert zurück, da keine vor- oder nachgestellten Leerzeichen (bzw. Whitespaces) vorkommen. Falls doch, wären diese jetzt entfernt worden. Das Ergebnis wird in jedem Fall zurück in $value geschrieben.

Nun wird auf Anti_Validate_String_Date die Methode isValid() ausgeführt. Diese, so können wir nachlesen, testet die Eingabe mit return strtotime($value) !== false; Diese liefert uns nun endlich FALSE für das ungültige Datum, _invoke(..) wird beendet und validate() liefert uns das erwartete Ergebnis FALSE zurück. Ich denke nun wird auch klar, in welche Blöcke wir gelangen, wenn das ganze nun an setBirthDate() übergeben wird.

$user->setBirthDate("2009-12-32") wird aufgerufen, wir kommen wieder zum Anti_ValidateFilter_String_Date, nur diesmal zur filter() Methode, die ebenfalls _invoke(..) aufruft, nun allerdings mit dem 2. Parameter ($validate = ) FALSE. Wir durchlaufen wieder unseren Array, durchlaufen die Validierung mit "is_string" und die Filterung mit "trim" erfolgreich, bis nun die Methode check() von Anti_Validate_String_Date aufgerufen wird. Diese macht nichts als isValid() aufzurufen (wie es bereits beim ersten isValidBirthDate() direkt geschehen ist) und wirft im Fehlerfall eine spezielle Exception.

Genau wie vorhergesagt hat also isValidBirthDate() funktioniert und FALSE zurückgeliefert, wir haben dies jedoch falsch programmiert und versehentlich trotzdem setBirthDate() übergeben. Das Ergebnis, eine Exception, ist korrekt und notwendig. Korrigieren wir also Eingabe und Abfrage:
PHP-Code:
<?php
$birthDate 
"2009-12-31"// 31 statt 32
$user = new User();
if (
$user->isValidBirthDate($birthDate)) { // Negierung durch ! entfernt
  
$user->setBirthDate($birthDate);
}
und machen dort weiter, wo unsere Exception geworfen wurde. Da Anti_Validate_String_Date->isValid() nun TRUE liefert kommt Anti_Filter_String_Date zum Zug, die Eingabe wird an Anti_DateTime (einer Ableitung von PHP's DateTime-Klasse) übergeben und wir haben das beste Format für ein Datum, das man sich wünschen kann: ein garantiert gültiges Datum und alle Formatierungsmöglichkeiten, die PHP's DateTime bietet:
PHP-Code:
<?php
if ($user->hasBirthDate()) {
  echo 
$user->getBirthDate()->format("d.m.Y");
} else {
  echo 
"- unbekannt -";
}
Ich muss zugeben ich habe euch und mich selbst etwas reingelegt, in dem ich mit den Vorzügen der ValidateFilter beim Umwandeln deutscher Datumsformate gesprochen habe. Eigentlich (ohne Ausnahme) sollten die Daten-Klassen unabhängig von Sprache und Formatierungen sein. Das heißt die Annahme, ein Datum wird "deutsch" übergeben schränkt die Anwendung der Klasse bereits so sehr auf die Verwendung für deutsche Anwendungen ein, dass es nicht ohne weiteres möglich ist, sie in französischer oder englischer Lokalisierung zu verwenden. Daher sollten länder-spezifische Eingaben gesondert, außerhalb der Daten-Klasse behandelt werden. Das könnte man recht einfach wieder durch Manipulation des ValidateFilter-Objektes erreichen:
PHP-Code:
<?php
$user 
= new User();
$vfBirthDate $user->getValidateFilterBirthDate();
$vfBirthDate->setInputFormat("%d.%m.%Y");
$birthDate "32.12.2009";
if (
$user->isValidBirthDate($birthDate)) {
 
// ..
}
Die konkrete Implementierung spare ich mir an dieser Stelle

Fazit

So was haben wir gewonnen? Wir haben unsere unsicheren Arrays durch ein Daten-Objekte ersetzt, die uns nun Schnittstellen, eine Prüfung und Filterung, und damit valide Daten bieten. Zusätzlich können wir Berechnungen auf diesen Daten dort zentral unterbringen. Sicherlich kann & sollte nicht jeder Array in der Anwendung durch eine Klasse ersetzt werden, aber dort wo wir verlässlichen Zugriff auf unsere komplexen Daten wollen, sollten wir darüber nachdenken.

Wenn ihr die komplette, lauffähige Version mal bei euch ausprobieren wollte, könnt ihr sie im Anhang für hoffentlich eine längere Zeit downloaden. Ihr findet im Anhang zusätzlich eine Auflistung der verwendeten Klassen.

Einfach so einrichten, dass index.php aus dem public-Ordner auf euren DOCUEMNT_ROOT zeigt. Am einfachsten geht das wohl über einen eigenen Virtual Host. Wie ihr den einrichtet ist eine andere Geschichte:
http://www.php.de/tutorials/42725-vi...r-windows.html (Virtual Hosts (vhosts) einrichten unter Windows)


Wundert euch nicht, wenn der Aufbau der Klassen etwas auseinandergezogen wurde und nun Ableitung auf Ableitung folgt, aber so fand ich das aus Programmierer-Sicht am saubersten, zum Erklären aber am schwierigsten. Daher hier im Thread nur die grobe Version!

Zum Schluß noch der Hinweis, dass dieser Thread nicht als Anleitung zu verstehen ist, sondern lediglich als Anregung. Ich habe selbst noch kleine Fehler gefunden, beispielsweise wollte ich hasBirthDate() ebenso wie die anderen has*() Methoden schön generisch mit return $this->isValidBirthDate($this->_birthDate) aufrufen, allerdings erlauben die momentan verwendeten Filter keine Verzweigung (getBirthDate() liefert ja ein Anti_DateTime-Objekt, was eindeutig kein String ist). Sprich $user->setBirthDate($user->getBirthDate()) funktioniert nicht, was eigentlich nicht sein sollte. Vielleicht entwickelt sich ja im Lauf der Zeit hierfür eine Lösung, allzu schwer sollte _invoke() ja nicht umzubauen sein.

Danke fürs Lesen und vor allem eure Kritiken.

Das zweite Kapitel (Daten-Listen) werde ich wohl in den kommenden zwei Wochen posten. Für Kapitel 3 setze ich etwas mehr Zeit an und Kapitel 4 folgt irgendwann
Angehängte Dateien
Dateityp: zip db-to-object.local.zip (23,9 KB, 46x aufgerufen)
Dateityp: zip Klassenuebersicht.zip (34,7 KB, 38x aufgerufen)
__________________
"Nuschel ich?" - "Was?"

Geändert von Chriz (14.09.2009 um 00:44 Uhr).
Chriz ist offline   Mit Zitat antworten
Sponsor Mitteilung
PHP Code Flüsterer

Registriert seit: 21.08.2005
Beiträge: 4682
PHP-Kenntnisse:
Fortgeschritten

Alt 14.09.2009, 11:49  
Erfahrener Benutzer
 
Benutzerbild von Phoscur
 
Registriert seit: 01.12.2008
Beiträge: 450
PHP-Kenntnisse:
Fortgeschritten
Phoscur wird schon bald berühmt werdenPhoscur wird schon bald berühmt werden
Standard

Habe ein wenig überflogen.
PHP-Code:
if ($user->isValidBirthDate($birthDate)) {
  
$user->setBirthDate($birthDate);

Warum nicht
PHP-Code:
try {
    
$user->setBrithData($birthData); // throws Exception if invalid
} catch (UserException /*<-wieauchimmer */ $e) {
    
// behandle Fehler

Ich würde hier aus dem selben Grund try/catch benutzen wieso du die Validierung in die Userklasse verschiebst: damit du die Fehlermeldung beim Werfen der Exception bestimmen kannst und nicht jedes mal im else Block erneut schreiben musst.
Insgesamt würde ich die has* und isValid* Methoden innerhalb des Setters aufrufen (und evtl. private deklarieren) und mit Exceptions arbeiten, das hält das Interface der Klasse kleiner und ist einfacher mit einem @throws zu dokumentieren.
__________________

Geändert von Phoscur (14.09.2009 um 11:54 Uhr).
Phoscur ist offline   Mit Zitat antworten
Alt 14.09.2009, 12:03  
Moderator
 
Benutzerbild von Chriz
 
Registriert seit: 11.05.2008
Beiträge: 6.069
Chriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer Anblick
Standard

Hallo,
danke für deine Antwort!

Nach einer Faustregel sollten im Standardfall alle try-catch-Blöcke entfernt werden können, und die Anwendung sollte immernoch laufen. Exceptions stellen ja wirklich nur Ausnahmen dar und sollten nicht zur Validierung missbraucht werden. Daher die Unterscheidung. Außerdem: Wenn du die isValid*() Prüfung weglässt, müsstest du ja entweder um alle setter ein gemeinsames try-catch machen, dann würde die erste bereits das Setzen der anderen Werte verhindern. Die Alternative wäre um jeden setter ein try-catch zu machen, was sicherlich auch sehr eigen wäre.
__________________
"Nuschel ich?" - "Was?"
Chriz ist offline   Mit Zitat antworten
Alt 14.09.2009, 15:33  
Erfahrener Benutzer
 
Benutzerbild von Phoscur
 
Registriert seit: 01.12.2008
Beiträge: 450
PHP-Kenntnisse:
Fortgeschritten
Phoscur wird schon bald berühmt werdenPhoscur wird schon bald berühmt werden
Standard

Ich weiß nich, ich finde die If/else Blöcke genauso unpraktikabel wie try/catch, wobei hier try/catch vorteilhafter ist. Wir können uns jetzt streiten ob ein falscher Input eine Ausnahme ist oder nicht, auf jeden Fall passt diese Struktur gut zur Validierung. Kannst ja auch mal googeln, bin da nicht der einzige Befürworter.
Dazu kommt, dass du die Kapselung brichst, wenn du isValid*() nicht innerhalb des Setters aufrufst.
__________________

Geändert von Phoscur (14.09.2009 um 15:37 Uhr).
Phoscur ist offline   Mit Zitat antworten
Alt 14.09.2009, 16:15  
Erfahrener Benutzer
 
Registriert seit: 28.08.2009
Beiträge: 233
PHP-Kenntnisse:
Anfänger
Steve befindet sich auf einem aufstrebenden Ast
Standard

Zitat:
Zitat von Chriz Beitrag anzeigen
Bei den Daten-Objekten geht es wieder darum, unabhängig von der Datenquelle (CSV, Datenbank, Formular, ..) Objekte zu erzeugen, mit denen in der Anwendung gearbeitet werden kann.
Das Problem, was ja auch schon angesprochen wurde, sind ja die unterschiedlichen Formatierungen der Datentypen.
Also z.B. "Datum" - für ein Formular oder CSV-Datei möchte ich sicherlich ein deutsches Datums-Format (DD.MM.YYYY) benutzen, wenn ich das Datum in einer MySQL-Tabelle speichern will, muss es aber im Format YYYY-MM-DD vorliegen.

Insofern benötige ich doch für jeden Kontext ein eigenes Datenobjekt mit anderen Validatoren, etc. oder wie würdest du das mit deinem Vorgehen lösen?
Steve ist offline   Mit Zitat antworten
Alt 14.09.2009, 16:33  
Moderator
 
Benutzerbild von Chriz
 
Registriert seit: 11.05.2008
Beiträge: 6.069
Chriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer Anblick
Standard

Zitat:
Zitat von Phoscur Beitrag anzeigen
Ich weiß nich, ich finde die If/else Blöcke genauso unpraktikabel wie try/catch, wobei hier try/catch vorteilhafter ist. Wir können uns jetzt streiten ob ein falscher Input eine Ausnahme ist oder nicht, auf jeden Fall passt diese Struktur gut zur Validierung. Kannst ja auch mal googeln, bin da nicht der einzige Befürworter.
Dazu kommt, dass du die Kapselung brichst, wenn du isValid*() nicht innerhalb des Setters aufrufst.
Inwiefern unpraktikabel? Du kannst auch $user->import($_POST) verwenden, bekommst dann aber Exceptions um die Ohren gehauen, wenn was nicht stimmt, was soll auch sonst passieren? Eine schöne Nachricht aus den Fehlern generieren ist nunmal nicht die Aufgabe der User-Klasse. Oder wie würdest du das konkret lösen? Ich kann dir da noch nicht ganz folgen..


Zitat:
Zitat von Steve Beitrag anzeigen
Das Problem, was ja auch schon angesprochen wurde, sind ja die unterschiedlichen Formatierungen der Datentypen.
Also z.B. "Datum" - für ein Formular oder CSV-Datei möchte ich sicherlich ein deutsches Datums-Format (DD.MM.YYYY) benutzen, wenn ich das Datum in einer MySQL-Tabelle speichern will, muss es aber im Format YYYY-MM-DD vorliegen.

Insofern benötige ich doch für jeden Kontext ein eigenes Datenobjekt mit anderen Validatoren, etc. oder wie würdest du das mit deinem Vorgehen lösen?
Wo sind sie denn noch unterschiedlich? Beim Datum, und hier habe ich ja ein normalisiertes, universelles Format gewählt: die DateTime-Klasse. Mit der format()-Methode kannst du jedes beliebige Format herausziehen. Die Daten-Klasse kann und soll nicht wissen, welche Datenquelle ihr im Nacken sitzt, das ist ja gerade der Clou. Natürlich fängst du dir damit auch mögliche Nachteile ein, CSV-Spalte darf keine Quotes enthalten und nur maximal 100 Zeichen haben, während die DB alles bis 255 Zeichen erlaubt. Aber dann hast du einen Kompatibilitäts-Problem zwischen deinen Datenquellen, das zu lösen wieder nicht die Aufgabe der Daten-Klasse ist. Höchstens durch Einstellen der ValidateFilter auf den kleinsten gemeinsamen Nenner.
__________________
"Nuschel ich?" - "Was?"

Geändert von Chriz (14.09.2009 um 16:38 Uhr).
Chriz ist offline   Mit Zitat antworten
Alt 14.09.2009, 16:36  
moderatives Dielektrikum
 
Benutzerbild von nikosch
 
Registriert seit: 21.05.2008
Beiträge: 34.253
PHP-Kenntnisse:
Fortgeschritten
nikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz sein
Standard

Zitat:
Höchstens durch Einstellen der ValidateFilter auf den kleinsten gemeinsamen Nenner.
Alternativ durch Einstellen zweiter Validatorensets. So, und jetzt werde ich mich mal beimachen und den Thread lesen. Da mußt Du ja ewig dran geschrieben haben!
__________________
--
One pixel is still too big. Please make it smaller. ASAP.

Initiative Mittelstand.
Die wichtigste Gestaltungsregel im Screendesign ist Pi mal Daumen des Arbeitgebers.
--
nikosch ist offline   Mit Zitat antworten
Alt 14.09.2009, 16:54  
Moderator
 
Benutzerbild von Chriz
 
Registriert seit: 11.05.2008
Beiträge: 6.069
Chriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer Anblick
Standard

Auch nicht, weil nur die Datenquelle weiß, wie sie ihre Daten haben möchte. Natürlich ist das ungünstig, um z.B. Duplikate gleich abzulehnen. Möglicherweise sollte ich die setup()-Methoden public machen und erlauben, dass die Datenquelle eigene ValidateFilter injiziert, beispielsweise als Callback, das dann auf die Datenbank losgelassen wird und auf Duplikate prüft. Ich lass mir das mal durch den Kopfgehen.
__________________
"Nuschel ich?" - "Was?"
Chriz ist offline   Mit Zitat antworten
Alt 14.09.2009, 17:26  
Erfahrener Benutzer
 
Benutzerbild von Phoscur
 
Registriert seit: 01.12.2008
Beiträge: 450
PHP-Kenntnisse:
Fortgeschritten
Phoscur wird schon bald berühmt werdenPhoscur wird schon bald berühmt werden
Standard

Zitat:
Zitat von Chriz Beitrag anzeigen
Inwiefern unpraktikabel? Du kannst auch $user->import($_POST) verwenden, bekommst dann aber Exceptions um die Ohren gehauen, wenn was nicht stimmt, was soll auch sonst passieren? Eine schöne Nachricht aus den Fehlern generieren ist nunmal nicht die Aufgabe der User-Klasse. Oder wie würdest du das konkret lösen? Ich kann dir da noch nicht ganz folgen..
Unpraktikabel ist vielleicht das falsche Wort. Du hast es "eigen" genannt. Natürlich ist das komisch um jedes Set ein Try/Catch zu basteln, aber dein If/Else musst du genauso machen.
Du musst nur import() anders implementieren.
Code:
foreach daten
    try 
          set
    catch exception
          übergebe an den errorhandler
Der Errorhandler ist ein eigenes Objekt oder eine Funktion, die du entweder direkt als Parameter von import() oder über ein gesondertes Set an die Userklasse übergibst. In jedem Fall lose gekoppelt, sodass du diesen Handler je nach Bedarf austauschen kannst. Der Handler kann dann zum Beispiel alle Validationsfehler sammeln und du kannst sie am Ende ausgeben. Oder er gibt sie gleich oder gar nicht aus... je nach Bedarf...
Die Exceptions selbst hätten für mich nur eine sehr einfach englische Fehlerbeschreibung und würden entweder UserException oder WrongInputException angehören, der Handler würde evtl. noch mit Sprachunterstützung eine userfreundliche Fehlermeldung generieren oder weiterleiten.
__________________
Phoscur ist offline   Mit Zitat antworten
Alt 14.09.2009, 17:32  
moderatives Dielektrikum
 
Benutzerbild von nikosch
 
Registriert seit: 21.05.2008
Beiträge: 34.253
PHP-Kenntnisse:
Fortgeschritten
nikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz seinnikosch kann auf vieles stolz sein
Standard

Das Array in _validate(array $property) verwendest Du, falls die genannten Abhängigkeiten von Properties bei der Validierung berücksichtigt werden sollen oder wozu?

Gerade dieser Punkt macht mir auch anderweitig Sorgen. Wäre es nicht doch besser, Eingabedaten doch erstmal ins Objekt aufzunehmen, egal ob valide oder nicht? Aus der Sicht einer Formulareingabe kann sowas für den User zur Geduldsprobe werden. Das ist bei Deiner Lösung ja selbst bei einzelnen invaliden Daten der Fall. Zum Formwiederbefüllen muß ich mich damit also an $_POST halten, nicht an das Objekt. Das widerum verkompliziert die Angelegenheit, wenn ich einen Formularcode gleichzeitig für Neuerfassung und Bearbeiten eines Datensatzes nutzen wollte. Ich hoffe Du verstehst, was ich meine.
Das widerspricht natürlich Deinem gesamten Konzept, aber ich finde darüber muß man nachdenken.
__________________
--
One pixel is still too big. Please make it smaller. ASAP.

Initiative Mittelstand.
Die wichtigste Gestaltungsregel im Screendesign ist Pi mal Daumen des Arbeitgebers.
--
nikosch ist offline   Mit Zitat antworten
Antwort


Themen-Optionen
Thema bewerten
Thema bewerten:

Forumregeln
Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are an
Gehe zu

Ähnliche Themen
Thema Autor Forum Antworten Letzter Beitrag
Registrierte User sollen ihre Daten ändern können 54ch4 PHP Tipps 2009 17 14.03.2009 14:29
Mehrere Arrays unterschiedlicher Größe kombinieren querfisch PHP Tipps 2007 9 31.03.2007 21:34
Session Frage - gleiches Formular 2 mal alle Daten behalten NetLook PHP Tipps 2007 1 21.11.2005 18:42
PHP-GTK Tutorial Beitragsarchiv 9 02.11.2005 21:07
[Erledigt] sql daten für einen kunden auslesen/ändern im Formular PHP Tipps 2005-2 3 12.10.2005 08:36
[Erledigt] Daten auslesen und ändern Datenbanken 2 17.09.2005 19:28
Daten eintragen und auslesen Rettungsdackel Datenbanken 0 14.09.2005 16:29
Daten in Datenbank ändern PHP Tipps 2005 3 27.01.2005 14:40
array_push nur in begrenzter Anzahl ausführen ? PHP Tipps 2004 2 07.09.2004 09:05

Besucher kamen über folgende Suchanfragen bei Google auf diese Seite
php mapping, php or mapping, or mapping php, php or mapper, php datenobjekte, or mapper php, php or-mapping, php ormapping, php o/r mapper, php datenobjekt, php ormapper, php or, objektrelationales mapping php, php mapper, ormapper php, php object mapping, php db mapping, php objekt datensätze, php objektrelationales mapping, db mapping php

Alle Zeitangaben in WEZ +1. Es ist jetzt 18:32 Uhr.




Powered by vBulletin® Version 3.7.2 (Deutsch)
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.2.0
Aprilia-Forum, Aquaristik-Forum, Liebeskummer-Forum, Zierfisch-Forum, Geizkragen-Forum