Validación en PHP. ¿Belleza o fideos?

Al elegir el mejor validador de PHP entre diez populares, me enfrenté a un dilema. ¿Qué es más importante para mí? ¿Adherencia a todos los cánones SOLID / OOP o usabilidad y claridad del código? ¿Qué preferirán los usuarios del framework Comet ? Si cree que la pregunta está lejos de ser simple, bienvenido bajo el corte en un largo viaje a través de fragmentos de código :)





Además de las preocupaciones sobre el rendimiento de la API REST y los microservicios, me preocupa mucho la legibilidad del código que escribimos todos los días para resolver problemas de trabajo, incluida la validación de datos.



Quiero mostrar fragmentos de código de mis propios puntos de referencia para que pueda apreciar la variedad de enfoques para resolver el mismo problema. Imaginemos que nos ha llegado el siguiente conjunto de datos:



$form = [
    'name'           => 'Elon Mask', 
    'name_wrong'     => 'Mask',
    'login'          => 'mask', 
    'login_wrong'    => 'm@sk', 
    'email'          => 'elon@tesla.com', 
    'email_wrong'    => 'elon@tesla_com', 
    'password'       => '1q!~|w2o<z', 
    'password_wrong' => '123456',
    'date'           => '2020-06-05 15:52:00',
    'date_wrong'     => '2020:06:05 15-52-00',
    'ipv4'           => '192.168.1.1',
    'ipv4_wrong'     => '402.28.6.12',
    'uuid'           => '70fcf623-6c4e-453b-826d-072c4862d133',
    'uuid_wrong'     => 'abcd-xyz-6c4e-453b-826d-072c4862d133',
    'extra'          => 'that field out of scope of validation',
    'empty'          => ''
];


Nuestro objetivo es ejecutar esta matriz a través de un conjunto de reglas de validación, lo que da como resultado una lista de todos los campos con errores y mensajes estándar para mostrar al usuario.



Estándar de la industria e icono de la programación orientada a objetos pura - Symfony, por supuesto





use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Translation\MessageSelector;

$validator = Validation::createValidator();

$constraint = new Assert\Collection([    
    'name' => new Assert\Regex('/^[A-Za-z]+\s[A-Za-z]+$/u'),   	
    'login' => new Assert\Regex('/^[a-zA-Z0-9]-_+$/'),
    'email' => new Assert\Email(),
    'password' => [
        new Assert\NotBlank(),
        new Assert\Length(['max' => 64]),
        new Assert\Type(['type' => 'string'])
    ],
    'agreed' => new Assert\Type(['type' => 'boolean'])
]);

$violations = $validator->validate($form, $constraint);

$errors = [];
if (0 !== count($violations)) {
    foreach ($violations as $violation) {
        $errors[] = $violation->getPropertyPath() . ' : ' . $violation->getMessage();
    }
} 

return $errors;


Código ingenioso en PHP puro



$errors = [];

if (!preg_match('/^[A-Za-z]+\s[A-Za-z]+$/u', $form['name']))
    $errors['name'] = 'should consist of two words!';
if (!preg_match('/^[A-Za-z]+\s[A-Za-z]+$/u', $form['name_wrong']))
    $errors['name_wrong'] = 'should consist of two words!';
if (!preg_match('/^[a-zA-Z0-9-_]+$/', $form['login']))
    $errors['login'] = 'should contain only alphanumeric!';
if (!preg_match('/^[a-zA-Z0-9]-_+$/', $form['login_wrong']))
    $errors['login_wrong'] = 'should contain only alphanumeric!';

if (filter_var($form['email'], FILTER_VALIDATE_EMAIL) != $form['email'])
    $errors['email'] = 'provide correct email!';
if (filter_var($form['email_wrong'], FILTER_VALIDATE_EMAIL) != $form['email_wrong'])
    $errors['email_wrong'] = 'provide correct email!';

if (!is_string($form['password']) ||
    $form['password'] == '' ||
    strlen($form['password']) < 8 ||
    strlen($form['password']) > 64 
)
    $errors['password'] = 'provide correct password!';

if (!is_string($form['password_wrong']) ||
    $form['password_wrong'] == '' ||
    strlen($form['password_wrong']) < 8 ||
    strlen($form['password_wrong']) > 64 
)
    $errors['password_wrong'] = 'provide correct password!';

if (!preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $form['date']))
    $errors['date'] = 'provide correct date!';
if (!preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/', $form['date_wrong']))
    $errors['date_wrong'] = 'provide correct date!';

if (filter_var($form['ipv4'], FILTER_VALIDATE_IP) != $form['ipv4'])
    $errors['ipv4'] = 'provide correct ip4!';
if (filter_var($form['ipv4_wrong'], FILTER_VALIDATE_IP) != $form['ipv4_wrong'])
    $errors['ipv4_wrong'] = 'provide correct ip4!';

if (!preg_match('/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i', $form['uuid']))
    $errors['uuid'] = 'provide correct uuid!';
if (!preg_match('/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i', $form['uuid_wrong']))
    $errors['uuid_wrong'] = 'provide correct uuid!';

if (!isset($form['agreed']) || !is_bool($form['agreed']) || $form['agreed'] != true)
    $errors['agreed'] = 'you should agree with terms!';

return $errors;


Solución basada en una de las bibliotecas de validación de respeto más populares



use Respect\Validation\Validator as v;
use Respect\Validation\Factory;

Factory::setDefaultInstance(
    (new Factory())
        ->withRuleNamespace('Validation')
        ->withExceptionNamespace('Validation')
);

$messages = [];

try {
    v::attribute('name', v::RespectRule())
        ->attribute('name_wrong', v::RespectRule())
        ->attribute('login', v::alnum('-_'))
        ->attribute('login_wrong', v::alnum('-_'))
        ->attribute('email', v::email())
        ->attribute('email_wrong', v::email())
        ->attribute('password', v::notEmpty()->stringType()->length(null, 64))
        ->attribute('password_wrong', v::notEmpty()->stringType()->length(null, 64))
        ->attribute('date', v::date())
        ->attribute('date_wrong', v::date())
        ->attribute('ipv4', v::ipv4())
        ->attribute('ipv4_wrong', v::ipv4())
        ->attribute('uuid', v::uuid())
        ->attribute('uuid_wrong', v::uuid())
        ->attribute('agreed', v::trueVal())
        ->assert((object) $form);
} catch (\Exception $ex) {
    $messages = $ex->getMessages();
}

return $messages;


Nombre también conocido: Valitron



use Valitron\Validator;

Validator::addRule('uuid', function($field, $value) {
    return (bool) preg_match('/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i', $value);
}, 'UUID should confirm RFC style!');

$rules = [
    'required'  => [ 'login', 'agreed' ],
    'regex'     => [ ['name', '/^[A-Za-z]+\s[A-Za-z]+$/'] ],
    'lengthMin' => [ [ 'password', '8'], [ 'password_wrong', '8'] ],
    'lengthMax' => [ [ 'password', '64'], [ 'password_wrong', '64'] ],
    'slug'      => [ 'login', 'login_wrong' ],
    'email'     => [ 'email', 'email_wrong' ],
    'date'      => [ 'date', 'date_wrong' ],
    'ipv4'      => [ 'ipv4', 'ipv4_wrong' ],
    'uuid'      => [ 'uuid', 'uuid_wrong' ],
    'accepted'  => 'agreed'
];

$validator = new Validator($form);
$validator->rules($rules);
$validator->rule('accepted', 'agreed')->message('You should set {field} value!');
$validator->validate();

return $validator->errors());


Sirius encantador




$validator = new \Sirius\Validation\Validator;

$validator
    ->add('name', 'required | \Validation\SiriusRule')
    ->add('login', 'required | alphanumhyphen', null, 'Only latin chars, underscores and dashes please.')
    ->add('email', 'required | email', null, 'Give correct email please.')
    ->add('password', 'required | maxlength(64)', null, 'Wrong password.')
    ->add('agreed', 'required | equal(true)', null, 'Where is your agreement?');

$validator->validate($form);

$errors = [];
foreach ($validator->getMessages() as $attribute => $messages) {
    foreach ($messages as $message) {
        $errors[] = $attribute . ' : '. $message->getTemplate();
    }
}

return $errors;


Y así se validan en Laravel



use Illuminate\Validation\Factory as ValidatorFactory;
use Illuminate\Translation\Translator;
use Illuminate\Translation\ArrayLoader;
use Symfony\Component\Translation\MessageSelector;
use Illuminate\Support\Facades\Validator as FacadeValidator;

$rules = array(
    'name' => ['regex:/^[A-Za-z]+\s[A-Za-z]+$/u'],
    'name_wrong' => ['regex:/^[A-Za-z]+\s[A-Za-z]+$/u'],
    'login' => ['required', 'alpha_num'],
    'login_wrong' => ['required', 'alpha_num'],
    'email' => ['email'],
    'email_wrong' => ['email'],
    'password' => ['required', 'min:8', 'max:64'],
    'password_wrong' => ['required', 'min:8', 'max:64'],
    'date' => ['date'],
    'date_wrong' => ['date'],
    'ipv4' => ['ipv4'],
    'ipv4_wrong' => ['ipv4'],
    'uuid' => ['uuid'],
    'uuid_wrong' => ['uuid'],
    'agreed' => ['required', 'boolean']
);

$messages = [
    'name_wrong.regex' => 'Username is required.',
    'password_wrong.required' => 'Password is required.',
    'password_wrong.max' => 'Password must be no more than :max characters.',
    'email_wrong.email' => 'Email is required.',
    'login_wrong.required' => 'Login is required.',
    'login_wrong.alpha_num' => 'Login must consist of alfa numeric chars.',
    'agreed.required' => 'Confirm radio box required.',
);

$loader = new ArrayLoader();
$translator = new Translator($loader, 'en');
$validatorFactory = new ValidatorFactory($translator);

$validator = $validatorFactory->make($form, $rules, $messages);

return $validator->messages();


Validación inesperada de Rakit de diamantes



$validator = new \Rakit\Validation\Validator;
$validator->addValidator('uuid', new \Validation\RakitRule);

$validation = $validator->make($form, [
    'name'           => 'regex:/^[A-Za-z]+\s[A-Za-z]+$/u',
    'name_wrong'     => 'regex:/^[A-Za-z]+\s[A-Za-z]+$/u',
    'email'          => 'email',
    'email_wrong'    => 'email',
    'password'       => 'required|min:8|max:64',
    'password_wrong' => 'required|min:8|max:64',
    'login'          => 'alpha_dash',
    'login_wrong'    => 'alpha_dash',
    'date'           => 'date:Y-m-d H:i:s',
    'date_wrong'     => 'date:Y-m-d H:i:s',
    'ipv4'           => 'ipv4',
    'ipv4_wrong'     => 'ipv4',
    'uuid'           => 'uuid',
    'uuid_wrong'     => 'uuid',
    'agreed'         => 'required|accepted'
]); 	

$validation->setMessages([
    'uuid'     => 'UUID should confirm RFC rules!',
    'required' => ':attribute is required!',
    // etc
]);

$validation->validate();

return $validation->errors()->toArray();


¿Y qué? ¿Qué ejemplo de código es el más descriptivo, idiomático, correcto y, en general, "correcto"? Mi elección personal está en los muelles de Comet: github.com/gotzmann/comet Finalmente



, una pequeña encuesta para la posteridad.



All Articles