Подтвердить что ты не робот

Лучший подход к проверке модели в PHP?

Я узнал, что существует множество способов решения одной проблемы программирования, причем каждый подход обычно имеет свои преимущества и отрицательные стороны.

То, что я пытаюсь определить сегодня, - лучший способ сделать проверку модели в PHP. Используя пример человека, я изложил четыре разных подхода, которые я использовал в прошлом, каждый из которых включает классы и пример использования, а также то, что мне нравится и не нравится в каждом подходе.

Мой вопрос здесь: Какой подход вы считаете лучшим? Или у вас есть лучший подход?

Подход №1: проверка с использованием методов setter в классе модели

Хороший

  • Простой, только один класс
  • Отбрасывая исключения, класс никогда не может находиться в недопустимом состоянии (кроме бизнес-логики, т.е. смерть до рождения)
  • Не нужно забывать, чтобы вызвать какие-либо методы проверки

Плохой

  • Может только вернуть 1 ошибку (через Exception)
  • Требуется использование исключений и их улов, даже если ошибки не очень исключительны.
  • Может действовать только по одному параметру, поскольку другие параметры не могут быть установлены (нет возможности сравнить birth_date и death_date)
  • Модельный класс может быть длинным из-за большого количества валидации
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
}

Подход № 2: проверка с использованием методов проверки в классе модели

Хороший

  • Простой, только один класс
  • Можно проверить (сравнить) несколько параметров (поскольку проверка выполняется после установки всех параметров модели)
  • Может возвращать несколько ошибок (с помощью метода errors())
  • Свобода от исключений
  • Доступны методы getter и setter для других задач.

Плохой

  • Модель может находиться в недопустимом состоянии
  • Разработчик должен помнить, чтобы вызвать метод проверки is_valid()
  • Модельный класс может быть длинным из-за большого количества валидации
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()
}

Подход №3: Проверка в отдельном классе проверки

Хороший

  • Очень простые модели (все проверки выполняются в отдельном классе)
  • Можно проверить (сравнить) несколько параметров (поскольку проверка выполняется после установки всех параметров модели)
  • Может возвращать несколько ошибок (с помощью метода errors())
  • Свобода от исключений
  • Доступны методы getter и setter для других задач.

Плохой

  • Немного сложнее, поскольку для каждой модели требуются два класса.
  • Модель может находиться в недопустимом состоянии
  • Разработчик должен не забывать использовать класс проверки
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()
}

Подход №4: Проверка в классе модели и классе проверки

Хороший

  • Отбрасывая исключения, класс никогда не может находиться в недопустимом состоянии (кроме бизнес-логики, т.е. смерть до рождения)
  • Можно проверить (сравнить) несколько параметров (поскольку проверка бизнеса происходит после установки всех параметров модели)
  • Может возвращать несколько ошибок (с помощью метода errors())
  • Валидация организована в две группы: тип (класс модели) и бизнес (класс проверки)
  • Доступны методы getter и setter для других задач.

Плохой

  • Обработка ошибок сложнее: есть исключения (класс модели) и массив ошибок (класс проверки)
  • Немного сложнее, поскольку для каждой модели требуются два класса.
  • Разработчик должен не забывать использовать класс проверки
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
}
4b9b3361

Ответ 1

Я не думаю, что есть только один лучший подход, это зависит от того, как вы собираетесь использовать свои классы. В этом случае, когда у вас есть простой объект данных, я бы предпочел использовать Подход № 2: Проверка с использованием методов проверки в классе модели.

Плохие вещи не так уж плохи, на мой взгляд:

Модель может находиться в недопустимом состоянии

Иногда желательно иметь модель в недопустимом состоянии.

Например, если вы заполняете объект Person из веб-формы и хотите его зарегистрировать. Если вы используете первый подход, вам придется расширить класс Person, переопределить все сеттеры, чтобы перехватывать исключения, а затем вы можете иметь этот объект в недопустимом состоянии для ведения журнала.

Разработчик должен помнить о вызове метода проверки is_valid()

Если модель абсолютно не должна находиться в недопустимом состоянии или метод требует, чтобы модель находилась в допустимом состоянии, вы всегда можете вызывать is_valid() из класса, чтобы убедиться, что она находится в допустимом состоянии.

Модельный класс может быть длинным из-за большого количества валидации

Код проверки должен все же идти. Большинство редакторов позволяют вам сворачивать функции, поэтому это не должно быть проблемой при чтении кода. Во всяком случае, я считаю, что хорошо иметь всю проверку в одном месте.