Al leer esta pregunta SO , parece que lanzar excepciones para validar la entrada del usuario está mal visto.
Pero, ¿quién debería validar estos datos? En mis aplicaciones, todas las validaciones se realizan en la capa empresarial, porque solo la clase en sí misma sabe qué valores son válidos para cada una de sus propiedades. Si tuviera que copiar las reglas para validar una propiedad al controlador, es posible que las reglas de validación cambien y ahora hay dos lugares donde se debe realizar la modificación.
¿Mi premisa de que la validación debe hacerse en la capa empresarial es incorrecta?
Lo que hago
Entonces mi código generalmente termina así:
<?php
class Person
{
private $name;
private $age;
public function setName($n) {
$n = trim($n);
if (mb_strlen($n) == 0) {
throw new ValidationException("Name cannot be empty");
}
$this->name = $n;
}
public function setAge($a) {
if (!is_int($a)) {
if (!ctype_digit(trim($a))) {
throw new ValidationException("Age $a is not valid");
}
$a = (int)$a;
}
if ($a < 0 || $a > 150) {
throw new ValidationException("Age $a is out of bounds");
}
$this->age = $a;
}
// other getters, setters and methods
}
En el controlador, simplemente paso los datos de entrada al modelo y capturo las excepciones lanzadas para mostrar los errores al usuario:
<?php
$person = new Person();
$errors = array();
// global try for all exceptions other than ValidationException
try {
// validation and process (if everything ok)
try {
$person->setAge($_POST['age']);
} catch (ValidationException $e) {
$errors['age'] = $e->getMessage();
}
try {
$person->setName($_POST['name']);
} catch (ValidationException $e) {
$errors['name'] = $e->getMessage();
}
...
} catch (Exception $e) {
// log the error, send 500 internal server error to the client
// and finish the request
}
if (count($errors) == 0) {
// process
} else {
showErrorsToUser($errors);
}
¿Es esta una mala metodología?
Metodo alternativo
¿Debería crear métodos para isValidAge($a)
ese retorno verdadero / falso y luego llamarlos desde el controlador?
<?php
class Person
{
private $name;
private $age;
public function setName($n) {
$n = trim($n);
if ($this->isValidName($n)) {
$this->name = $n;
} else {
throw new Exception("Invalid name");
}
}
public function setAge($a) {
if ($this->isValidAge($a)) {
$this->age = $a;
} else {
throw new Exception("Invalid age");
}
}
public function isValidName($n) {
$n = trim($n);
if (mb_strlen($n) == 0) {
return false;
}
return true;
}
public function isValidAge($a) {
if (!is_int($a)) {
if (!ctype_digit(trim($a))) {
return false;
}
$a = (int)$a;
}
if ($a < 0 || $a > 150) {
return false;
}
return true;
}
// other getters, setters and methods
}
Y el controlador será básicamente el mismo, solo que en lugar de try / catch hay ahora if / else:
<?php
$person = new Person();
$errors = array();
if ($person->isValidAge($age)) {
$person->setAge($age);
} catch (Exception $e) {
$errors['age'] = "Invalid age";
}
if ($person->isValidName($name)) {
$person->setName($name);
} catch (Exception $e) {
$errors['name'] = "Invalid name";
}
...
if (count($errors) == 0) {
// process
} else {
showErrorsToUser($errors);
}
¿Entonces qué debo hacer?
Estoy bastante contento con mi método original, y a mis colegas a quienes se lo mostré en general les ha gustado. A pesar de esto, ¿debería cambiar al método alternativo? ¿O estoy haciendo esto terriblemente mal y debería buscar otra forma?
IValidateResults
.
ValidationException
y otras excepciones