[PHP, Symfony, Yii, Laravel] Валидация в PHP. Красота или лапша?
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Выбирая лучший PHP-валидатор из десятка популярных, я столкнулся с дилеммой. Что для меня важнее? Следование всем SOLID / ООП-канонам или удобство работы и наглядность кода? Что предпочтут пользователи фреймворка Comet? Если вы считаете, что вопрос далеко не прост — добро пожаловать под кат в длинное путешествие по фрагментам кода :)
Помимо озабоченности вопросами быстродействия для 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
В заключение — небольшой опрос для потомков.
===========
Источник:
habr.com
===========
Похожие новости:
- [PHP, Nginx] Использование Nginx FastCGI Cache
- [Laravel, PHP, Разработка веб-сайтов] Laravel–Дайджест (21–27 сентября 2020)
- [Разработка веб-сайтов, PHP, Совершенный код, Проектирование и рефакторинг] Рефакторинг в стиле ниндзя и другие приемчики
- [Laravel] Laravel Jetstream — новый скаффолдинг для фреймворка
- [Laravel, Программирование] Новинки Laravel 8
- [PHP, Программирование] Собеседование php-developer в 2020
- [PHP, Laravel] Система управления иерархическими древовидными комментариями для Laravel
- [JavaScript, Разработка веб-сайтов] Проверяем формы по стандартам с Validation API
- [PHP, Проектирование и рефакторинг, ООП] PHP класс для работы с INI файлами
- [Разработка веб-сайтов, PHP, Laravel] Laravel–Дайджест (14–20 сентября 2020)
Теги для поиска: #_php, #_symfony, #_yii, #_laravel, #_php, #_comet, #_frejmvork (фреймворк), #_validatsija (валидация), #_validator (валидатор), #_symfony, #_laravel, #_rakit, #_validation, #_valitron, #_php, #_symfony, #_yii, #_laravel
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:24
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Выбирая лучший PHP-валидатор из десятка популярных, я столкнулся с дилеммой. Что для меня важнее? Следование всем SOLID / ООП-канонам или удобство работы и наглядность кода? Что предпочтут пользователи фреймворка Comet? Если вы считаете, что вопрос далеко не прост — добро пожаловать под кат в длинное путешествие по фрагментам кода :) Помимо озабоченности вопросами быстродействия для 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 В заключение — небольшой опрос для потомков. =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:24
Часовой пояс: UTC + 5