Além das preocupações com o desempenho da API REST e microsserviços, estou muito preocupado com a legibilidade do código que digitamos todos os dias para resolver problemas de trabalho, incluindo validação de dados.
Quero mostrar trechos de código de meus próprios benchmarks para que você possa apreciar a amplitude de abordagens para resolver o mesmo problema. Vamos imaginar que o seguinte conjunto de dados chegou até nós:
$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' => ''
];
Nosso objetivo é executar este array por meio de um conjunto de regras de validação, resultando em uma lista de todos os campos com erros e mensagens padrão para demonstração ao usuário.
Padrão da indústria e ícone de pura OOP - Symfony, é claro
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 inteligente em 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;
Solução baseada em uma das mais populares bibliotecas Respect Validation
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;
Também nome conhecido: 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 adorável
$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;
E é assim que eles validam no 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();
Validação inesperada de diamante Rakit
$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();
E daí? Qual exemplo de código é mais descritivo, idiomático, correto e geralmente “correto”? Minha escolha pessoal está nas docas do Comet: github.com/gotzmann/comet Finalmente
, uma pequena votação para a posteridade.