Выбирая лучший PHP-валидатор из десятка популярных, я столкнулся с дилеммой. Что для меня важнее? Следование всем SOLID / ООП-канонам или удобство работы и наглядность кода? Что предпочтут пользователи фреймворка Comet? Если вы считаете, что вопрос далеко не прост — добро пожаловать под кат в длинное путешествие по фрагментам кода 🙂

Помимо озабоченности вопросами быстродействия для REST API и микросервисов, я очень переживаю за читаемость кода, который мы ежедневно набиваем для решения рабочих задач, в том числе — валидации данных.

Помимо озабоченности вопросами быстродействия для REST API и микросервисов, я очень переживаю за читаемость кода, который мы ежедневно набиваем для решения рабочих задач, в том числе — валидации данных.
Хочу показать куски кода из собственных бенчмарков, чтобы вы смогли оценить широту подходов к решению одной и той же проблемы. Представим, что к нам прилетел следующий набор данных:
$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' => '' ];
Наша цель — прогнать этот массив через набор правил валидации, получив на выходе список всех полей с ошибками и стандартные сообщения для демонстрации пользователю.
Отраслевой стандарт и икона чистого ООП — конечно же Symfony
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;
Вырвиглазный код на чистом PHP
$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;
Решение на базе одной из самых популярных библитек 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;
Еще одно известное имя: 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
$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;
А вот так валидируют в 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();
Неожиданный бриллиант Rakit Validation
$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();
Ну так что? Какой из примеров кода наиболее наглядный, идиоматичный, корректный и вообще «правильный»? Мой личный выбор — в доках на Comet: github.com/gotzmann/comet
В заключение — небольшой опрос для потомков.
ссылка на оригинал статьи https://habr.com/ru/post/521292/
Добавить комментарий