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