Bester Ansatz zur Modellvalidierung in PHP? [closed]

Lesezeit: 10 Minuten

Benutzer-Avatar
Jonathan

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 und death_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() oder validate() oder wie auch immer man es nennt, da es in die eingebaut ist beforeSave() Teil der Klasse.

    – Pitchinnate

    4. März 2013 um 20:57 Uhr

Ich glaube nicht, dass es nur einen besten Ansatz gibt, es hängt davon ab, wie Sie Ihre Klassen verwenden werden. Wenn Sie in diesem Fall nur ein einfaches Datenobjekt haben, würde ich es vorziehen, zu verwenden Ansatz Nr. 2: Validierung mit Validierungsmethoden in der Modellklasse.

Die schlechten Dinge sind meiner Meinung nach nicht so schlimm:

Das Modell kann sich in einem ungültigen Zustand befinden

Manchmal ist es wünschenswert, ein Modell in einem ungültigen Zustand zu haben.

Wenn Sie beispielsweise das Person-Objekt aus einem Webformular ausfüllen und es protokollieren möchten. Wenn Sie den ersten Ansatz verwenden, müssten Sie die Person-Klasse erweitern, alle Setter überschreiben, um Ausnahmen abzufangen, und dann könnten Sie dieses Objekt in einem ungültigen Zustand für die Protokollierung haben.

Der Entwickler muss daran denken, die Validierungsmethode is_valid() aufzurufen

Wenn sich das Modell absolut nicht in einem ungültigen Zustand befinden darf oder eine Methode erfordert, dass sich das Modell in einem gültigen Zustand befindet, können Sie jederzeit aufrufen is_valid() innerhalb der Klasse, um sicherzustellen, dass sie sich in einem gültigen Zustand befindet.

Die Modellklasse kann aufgrund vieler Validierungen lang sein

Der Validierungscode muss noch irgendwo hin. Die meisten Editoren lassen Sie Funktionen falten, sodass dies beim Lesen des Codes kein Problem darstellen sollte. Wenn überhaupt, finde ich es schön, alle Validierungen an einem Ort zu haben.

  • Gute Punkte, und ich stimme zu, die Negative sind kaum schlecht. Option Nr. 2 und Nr. 3 sind wirklich gleich, man verschiebt die Validierung einfach in eine separate Klasse … und ob das irgendwelche Vorteile hat oder nicht, wäre eine persönliche Entscheidung. Option #2 hat gegenüber #1 auch den zusätzlichen Vorteil, dass die Getter/Setter für andere Verwendungen frei sind. Danke für die Antwort!

    – Jonathan

    4. März 2013 um 21:25 Uhr

  • Ich denke nicht, dass dies in den meisten Fällen eine gute Lösung ist, da Sie den Validierungsteil an das ansonsten “dumme” Modell selbst binden. Modelle können in einem anderen Kontext eine andere Definition von “gültig sein” haben. Wenn Sie beispielsweise Postleitzahlen für ein Land prüfen, gelten in anderen Ländern möglicherweise andere Regeln. Mit dieser Lösung sind Sie völlig unflexibel. Ein separater Mechanismus zur Validierung des Modells entkoppelt diese Verantwortung. Die Klasse selbst sollte meiner Meinung nach keine Validierungslogik enthalten.

    – Jim Panse

    1. Juli 2019 um 11:21 Uhr

1216580cookie-checkBester Ansatz zur Modellvalidierung in PHP? [closed]

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy