Ich habe gelernt, dass es oft viele Möglichkeiten gibt, ein Programmierproblem zu lösen, wobei jeder Ansatz typischerweise seine eigenen Vorteile und negativen Nebeneffekte hat.
Was ich heute zu bestimmen versuche, ist der beste Weg zur Modellvalidierung in PHP. Am Beispiel einer Person habe ich vier verschiedene Ansätze skizziert, die ich in der Vergangenheit verwendet habe, jeweils einschließlich der Klassen und eines Anwendungsbeispiels, sowie was ich an jedem Ansatz mag und was nicht.
Meine Frage hier ist folgende: Welche Vorgehensweise halten Sie für die beste? Oder hast du einen besseren Ansatz?
Ansatz Nr. 1: Validierung mit Setter-Methoden in der Modellklasse
Der gute
- Einfach, nur eine Klasse
- Durch das Auslösen von Ausnahmen kann sich die Klasse niemals in einem ungültigen Zustand befinden (außer für die Geschäftslogik, dh der Tod kommt vor der Geburt).
- Sie müssen nicht daran denken, irgendwelche Validierungsmethoden aufzurufen
Das Schlechte
- Kann nur 1 Fehler zurückgeben (via
Exception
) - Erfordert die Verwendung von Ausnahmen und deren Abfangen, auch wenn die Fehler nicht sehr außergewöhnlich sind
- Kann nur auf einen Parameter wirken, da andere Parameter möglicherweise noch nicht eingestellt sind (keine Vergleichsmöglichkeit
birth_date
unddeath_date
) - Die Modellklasse kann aufgrund vieler Validierungen lang sein
class Person
{
public $name;
public $birth_date;
public $death_date;
public function set_name($name)
{
if (!is_string($name))
{
throw new Exception('Not a string.');
}
$this->name = $name;
}
public function set_birth_date($birth_date)
{
if (!is_string($birth_date))
{
throw new Exception('Not a string.');
}
if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $birth_date))
{
throw new Exception('Not a valid date.');
}
$this->birth_date = $birth_date;
}
public function set_death_date($death_date)
{
if (!is_string($death_date))
{
throw new Exception('Not a string.');
}
if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $death_date))
{
throw new Exception('Not a valid date.');
}
$this->death_date = $death_date;
}
}
// Usage:
try
{
$person = new Person();
$person->set_name('John');
$person->set_birth_date('1930-01-01');
$person->set_death_date('2010-06-06');
}
catch (Exception $exception)
{
// Handle error with $exception
}
Ansatz Nr. 2: Validierung mit Validierungsmethoden in der Modellklasse
Der gute
- Einfach, nur eine Klasse
- Es ist möglich, mehrere Parameter zu validieren (zu vergleichen), da die Validierung erfolgt, nachdem alle Modellparameter festgelegt wurden.
- Kann mehrere Fehler zurückgeben (via
errors()
Methode) - Freiheit von Ausnahmen
- Lässt Getter- und Setter-Methoden für andere Aufgaben verfügbar
Das Schlechte
- Das Modell kann sich in einem ungültigen Zustand befinden
- Der Entwickler muss daran denken, die Validierung aufzurufen
is_valid()
Methode - Die Modellklasse kann aufgrund vieler Validierungen lang sein
class Person
{
public $name;
public $birth_date;
public $death_date;
private $errors;
public function errors()
{
return $this->errors;
}
public function is_valid()
{
$this->validate_name();
$this->validate_birth_date();
$this->validate_death_date();
return count($this->errors) === 0;
}
private function validate_name()
{
if (!is_string($this->name))
{
$this->errors['name'] = 'Not a string.';
}
}
private function validate_birth_date()
{
if (!is_string($this->birth_date))
{
$this->errors['birth_date'] = 'Not a string.';
break;
}
if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->birth_date))
{
$this->errors['birth_date'] = 'Not a valid date.';
}
}
private function validate_death_date()
{
if (!is_string($this->death_date))
{
$this->errors['death_date'] = 'Not a string.';
break;
}
if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->death_date))
{
$this->errors['death_date'] = 'Not a valid date.';
break;
}
if ($this->death_date < $this->birth_date)
{
$this->errors['death_date'] = 'Death cannot occur before birth';
}
}
}
// Usage:
$person = new Person();
$person->name="John";
$person->birth_date="1930-01-01";
$person->death_date="2010-06-06";
if (!$person->is_valid())
{
// Handle errors with $person->errors()
}
Ansatz #3: Validierung in separater Validierungsklasse
Der gute
- Sehr einfache Modelle (die gesamte Validierung erfolgt in einer separaten Klasse)
- Es ist möglich, mehrere Parameter zu validieren (zu vergleichen), da die Validierung erfolgt, nachdem alle Modellparameter festgelegt wurden.
- Kann mehrere Fehler zurückgeben (via
errors()
Methode) - Freiheit von Ausnahmen
- Lässt Getter- und Setter-Methoden für andere Aufgaben verfügbar
Das Schlechte
- Etwas komplizierter, da für jedes Modell zwei Klassen erforderlich sind
- Das Modell kann sich in einem ungültigen Zustand befinden
- Der Entwickler muss daran denken, die Validierungsklasse zu verwenden
class Person
{
public $name;
public $birth_date;
public $death_date;
}
class Person_Validator
{
private $person;
private $errors = array();
public function __construct(Person $person)
{
$this->person = $person;
}
public function errors()
{
return $this->errors;
}
public function is_valid()
{
$this->validate_name();
$this->validate_birth_date();
$this->validate_death_date();
return count($this->errors) === 0;
}
private function validate_name()
{
if (!is_string($this->person->name))
{
$this->errors['name'] = 'Not a string.';
}
}
private function validate_birth_date()
{
if (!is_string($this->person->birth_date))
{
$this->errors['birth_date'] = 'Not a string.';
break;
}
if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->person->birth_date))
{
$this->errors['birth_date'] = 'Not a valid date.';
}
}
private function validate_death_date()
{
if (!is_string($this->person->death_date))
{
$this->errors['death_date'] = 'Not a string.';
break;
}
if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->person->death_date))
{
$this->errors['death_date'] = 'Not a valid date.';
break;
}
if ($this->person->death_date < $this->person->birth_date)
{
$this->errors['death_date'] = 'Death cannot occur before birth';
}
}
}
// Usage:
$person = new Person();
$person->name="John";
$person->birth_date="1930-01-01";
$person->death_date="2010-06-06";
$validator = new Person_Validator($person);
if (!$validator->is_valid())
{
// Handle errors with $validator->errors()
}
Ansatz #4: Validierung in Modellklasse und Validierungsklasse
Der gute
- Durch das Auslösen von Ausnahmen kann sich die Klasse niemals in einem ungültigen Zustand befinden (außer für die Geschäftslogik, dh der Tod kommt vor der Geburt).
- Validierung (Vergleich) mehrerer Parameter möglich (da die Geschäftsvalidierung erfolgt, nachdem alle Modellparameter festgelegt wurden)
- Kann mehrere Fehler zurückgeben (via
errors()
Methode) - Die Validierung ist in zwei Gruppen unterteilt: Typ (Modellklasse) und Geschäft (Validierungsklasse)
- Lässt Getter- und Setter-Methoden für andere Aufgaben verfügbar
Das Schlechte
- Die Fehlerbehandlung ist komplizierter, wenn Ausnahmen (Modellklasse) und ein Fehlerarray (Validierungsklasse) ausgelöst werden.
- Etwas komplizierter, da für jedes Modell zwei Klassen erforderlich sind
- Der Entwickler muss daran denken, die Validierungsklasse zu verwenden
class Person
{
public $name;
public $birth_date;
public $death_date;
private function validate_name()
{
if (!is_string($this->person->name))
{
$this->errors['name'] = 'Not a string.';
}
}
private function validate_birth_date()
{
if (!is_string($this->person->birth_date))
{
$this->errors['birth_date'] = 'Not a string.';
break;
}
if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->person->birth_date))
{
$this->errors['birth_date'] = 'Not a valid date.';
}
}
private function validate_death_date()
{
if (!is_string($this->person->death_date))
{
$this->errors['death_date'] = 'Not a string.';
break;
}
if (!preg_match('/(\d{4})-([01]\d)-([0-3]\d)/', $this->person->death_date))
{
$this->errors['death_date'] = 'Not a valid date.';
}
}
}
class Person_Validator
{
private $person;
private $errors = array();
public function __construct(Person $person)
{
$this->person = $person;
}
public function errors()
{
return $this->errors;
}
public function is_valid()
{
$this->validate_death_date();
return count($this->errors) === 0;
}
private function validate_death_date()
{
if ($this->person->death_date < $this->person->birth_date)
{
$this->errors['death_date'] = 'Death cannot occur before birth';
}
}
}
// Usage:
try
{
$person = new Person();
$person->set_name('John');
$person->set_birth_date('1930-01-01');
$person->set_death_date('2010-06-06');
$validator = new Person_Validator($person);
if (!$validator->is_valid())
{
// Handle errors with $validator->errors()
}
}
catch (Exception $exception)
{
// Handle error with $exception
}
Wow, du hättest es tatsächlich einfach machen und zehnmal so lange weitermachen können, wie es dafür gedauert hat. Ich schätze, das ist nicht für eine echte Arbeitssituation.
– Benutzer557846
4. März 2013 um 19:33 Uhr
@ChrisCooney, es ist ein guter Beitrag mit einer sehr guten Frage. +1
– Vucko
4. März 2013 um 19:34 Uhr
@AmazingDreams Zeichenkettentyphint in PHP?!
– Markus D
4. März 2013 um 19:39 Uhr
@AmazingDreams – Typhinweise können nicht mit skalaren Typen wie int oder string verwendet werden.
– Josef Silber
4. März 2013 um 19:40 Uhr
Persönlich mag ich Methode 2, vielleicht weil ich das Yii-Framework wirklich mag und sie es im Grunde so machen. Einziger Vorbehalt ist, dass Sie selten anrufen müssen
is_valid()
odervalidate()
oder wie auch immer man es nennt, da es in die eingebaut istbeforeSave()
Teil der Klasse.– Pitchinnate
4. März 2013 um 20:57 Uhr