Регистрация и авторизация пользователей на PHP. Часть четвертая
Класс для работы с массивами
В процессе обработки пользовательских данных нам всегда, так или иначе, приходится обращаться к массивам $_GET, $_POST или $_REQUEST. Что-то иногда приходится брать из массива $GLOBALS.
Просто так брать и что-то делать с пользовательскими данными достаточно... наивно, пожалуй. Когда мы берем некое значение для его дальнейшей обработки, нужно убрать из него, например, лишние пробелы, html-теги, преобразовать «опасные» символы в html-сущности и т.п. Чтобы не делать это каждый раз, когда мы обращаемся к данным, полученных из суперглобальных массивов $_GET, $_POST или $GLOBALS, мы создадим статический класс Core_Array для работы с массивами.
<?php // Файл modules/core/array.php defined("LEZH") || exit("Доступ к файлу запрещен"); /** * Класс для работы с массивами */ class Core_Array { static protected function _filter($data) { if (empty($data)) { return NULL; } return htmlspecialchars(trim($data)); } static public function getPost($key = NULL) { if (is_null($key)) { return NULL; } return static::get($_POST, $key); } static public function getGet($key = NULL) { if (is_null($key)) { return NULL; } return static::get($_GET, $key); } static public function getRequest($key = NULL) { if (is_null($key)) { return NULL; } return static::get($_REQUEST, $key); } static public function getSession($key = NULL) { if (is_null($key)) { return NULL; } return static::get($_SESSION, $key); } static public function getCookie($key = NULL) { if (is_null($key)) { return NULL; } return static::get($_COOKIE, $key); } static public function get(array $array, string $key) { if (is_null($key) || empty($array)) { return NULL; } return (is_array($array) && array_key_exists($key, $array)) ? static::_filter($array[$key]) : NULL; } static public function set(array & $array, string $key, $value) { $array[$key] = $value; } }
Код реализации этого класса, пожалуй, вряд ли требует какого-то особого описания. Читатель должен всё хорошо понять.
Порядок работы с формой регистрации пользователя
Возвращаемся к работе над формой регистрации пользователя. Что произойдет, если её отправить? Каждое из полей формы имеет атрибут required. Это не позволит отправить форму до тех пор, пока все поля с этим атрибутом не будут заполнены. Но, допустим, мы их заполнили. Что дальше?
Дальше требуется проверить, доказал ли пользователь, что он не робот. Все данные формы, в том числе и поле капчи, проверять и обрабатывать мы будем при помощи JavaScript, а точнее — AJAX.
Начнем с обработки поля капчи. Вероятно, нашей форме не хватает поля для вывода сообщений пользователю. Добавим его над самой формой.
<?php // Файл registration.php require_once('bootstrap.php'); // Получаем параметры конфигурации для генерации нашей капчи $oCaptcha_Config = Core_Config::instance()->get("captcha"); ?> <!doctype html> <html> <head> <title>Регистрация нового пользователя</title> <?php // Подключаем необходимые CSS-файлы require_once(INCLUDE_BLOCKS_PATH . "css.php"); // Подключаем необходимые JS-файлы сценариев require_once(INCLUDE_BLOCKS_PATH . "scripts.php"); ?> </head> <body> <?php // Подключаем блок header для страницы require_once(INCLUDE_BLOCKS_PATH . "header.php"); ?> <main> <div class="container-fluid container-lg"> <h1>Регистрация пользователя сайта</h1> <div id="user-message"></div> <div id="user-registration-form"> <!-- Все поля формы обязательны для заполнения --> <form id="user-registration" name="user-registration" action="/service.php" autocomplete="off" method="post" onsubmit="return false;"> <!-- Поле формы --> <div class="form-group row"> <label for="user_login" class="col-md-3 col-form-label">Ваш логин:</label> <div class="col-md-9"> <!-- Непосредственно само поле --> <input type="email" class="form-control" id="user_login" name="user_login" placeholder="Например: evgeni@lezhenkin.ru" required /> <!-- Описание поля формы и требования к его заполнению --> <div class="alert alert-info instr" id="user_login-instr">Укажите ваш действительный адрес электропочты, к которому вы имеете доступ, и на котором вы можете принимать и отправлять письма.</div> <!-- Поле для вывода сообщения об ошибке, относящегося именно к этому полю формы --> <div class="alert alert-danger error" id="user_login-error"></div> </div> </div> <!-- Поле формы --> <div class="form-group row"> <label for="user_password" class="col-md-3 col-form-label">Ваш пароль:</label> <div class="col-md-9"> <!-- Непосредственно само поле --> <input type="password" min="5" max="32" class="form-control" id="user_password" name="user_password" required /> <!-- Описание поля формы и требования к его заполнению --> <div class="alert alert-info instr" id="user_password-instr">Пароль может состоять только из символов латинского алфавита, цифр, и символов: <strong><samp>* - + ! @ # $ [ ] { } ' " ; : > < , .</samp></strong>. Пароль дожен содержать, как минимум, одну заглавную букву, и, как минимум, одну цифру. Длина пароля не должна быть менее шести символов, и не должна превышать 32 символа.</div> <!-- Поле для вывода сообщения об ошибке, относящегося именно к этому полю формы --> <div class="alert alert-danger error" id="user_password-error"></div> </div> </div> <!-- Поле формы --> <div class="form-group row"> <label for="user_password_approve" class="col-md-3 col-form-label">Подтвердите введенный пароль:</label> <div class="col-md-9"> <!-- Непосредственно само поле --> <input type="password" class="form-control" id="user_password_approve" name="user_password_approve" required /> <!-- Описание поля формы и требования к его заполнению --> <div class="alert alert-info instr" id="user_password_approve-instr">Вы должны ввести одинаковые пароли</div> <!-- Поле для вывода сообщения об ошибке, относящегося именно к этому полю формы --> <div class="alert alert-danger error" id="user_password_approve-error"></div> </div> </div> <!-- Капча Google recaptcha --> <div class="form-group row"> <label for="user_captcha" class="col-md-3 col-form-label">Вы не робот?</label> <div class="col-md-9"> <!-- Чтобы заработала капча Google, нужно лишь добавить одно поле --> <div class="g-recaptcha" data-sitekey="<?=$oCaptcha_Config["site_key"]?>"></div> </div> </div> <!-- Скрытое поле формы, в котором хранится строка запроса к сценарию обработчику --> <input type="hidden" id="user_registration_request" name="request" value="user_registration" /> <!-- Кнопки управления формой --> <div class="btn-group offset-md-3"> <input type="submit" name="submit" value="Зарегистрироваться" class="ml-md-2 btn btn-success" /> <input type="reset" name="reset" value="Очистить" class="btn btn-warning" /> <a href="/" class="btn btn-danger">На главную</a> </div> </form> </div> </div> </main> </body> </html>
В контейнер с ID user-message мы будем помещать все сообщения для пользователя. Общая схема взаимодействия пользователя и формы будет такая:
- пользователь что-то сделал с полями формы, или не сделал вообще ничего;
- пользователь нажал кнопку отправки формы или кнопки сброса, возврат;
- если пользователь отправил форму, JavaScript сначала проверяет корректность капчи;
- если капча корректная, JavaScript берет все имеющиеся поля формы, проверяет данные на соответствие требованиям;
- если все данные формы верны, JavaScript передает их серверу на обработку, ожидая от него ответ.
Пользовательские функции JavaScript для формы регистрации пользователя
Нам потребуется добавить множество пользовательских функций в сценарий JavaScript. Вынесем их все в отдельный файл. Дополним код нашего файла /blocks/scripts.php.
<!-- Файл blocks/scripts.php --> <!-- jQuery --> <script src="https://yastatic.net/jquery/3.3.1/jquery.min.js"></script> <!-- JS Bootstrap 4 --> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script> <!-- reCAPTCHA Google --> <script src="https://www.google.com/recaptcha/api.js" async defer></script> <!-- Пользовательские JS-сценарии --> <script src="/js/func.js"></script> <script src="/js/scripts.js"></script>
Обработчик формы регистрации пользователя на сайте
Добавляем обработчик формы регистрации пользователя на сайте, который будет срабатывать при нажатии кнопки отправки формы.
// Файл /js/scripts.js // Используем строгий режим ECMAScript 5 "use strict"; (function($) { jQuery(function() { // Обработчик события отправки формы регистрации пользователя на сайте $("#user-registration").on("submit", $.userRegistrationHandler) // Добавляем обработчик на поля формы // Если поле формы в фокусе, очищаем его от ошибки .find("input:text, input:password, input[type='email']") .on("focus", $.cleanError); }); })(jQuery);
Сам же метод $.userRegistrationHandler() мы объявим в нашем новом файле /js/func.js.
// Файл /js/func.js // Используем строгий режим ECMAScript 5 "use strict"; $.extend({ userRegistrationHandler: function() { alert("Обработка формы регистрации пользователя на сайте"); } }); (function($) { jQuery(function() { }); })(jQuery);
Проверка капчи
Как уже было сказано выше, первым делом следует проверить корректность капчи. Этим и займёмся. В качестве капчи мы используем Google reCAPTCHA.
Метод $.userRegistrationHandler() должен проверить корректность капчи, и только в случае положительного результата проверки отправлять данные форму на обработку.
/** * Обработывает форму регистрации пользователя на сайте по событию отправки формы */ userRegistrationHandler: function() { var form = $( this ), // Сама форма регистрации пользователя на сайте login = $("#user_login").val(), // Логин пользователя password = $("#user_password").val(), // Пароль пользователя password_approve = $("#user_password_approve").val(), // Подтвержденный пароль пользователя sCaptcha = form.find(".g-recaptcha-response").val(), // Значение капчи Google reCAPTCHA checkPassword, checkLogin; // Сначала проверяем капчу // Продолжаем обработку формы только в случае корректной капчи if ($.checkCapctha(sCaptcha, form.attr("action"))) { // Очищаем все поля ошибок $.cleanAllErrors(this); // Если введенные пароли не идентичны, прекращаем обработку if (password !== password_approve) { $.showError("user_password_approve", "Введенные вами пароли не совпадают"); // Помечаем поле как проверенное $.validField("user_password_approve", false); return false; } else { // Помечаем поле как проверенное $.validField("user_password_approve", true); } // Проверяем пароль на соответствие требованиям // Если он не подходит, прекращаем работу checkPassword = $.checkPassword(password); if (!checkPassword.success) { $.showError("user_password", checkPassword.message); // Помечаем поле как проверенное $.validField("user_password", false); return false; } else { // Помечаем поле как проверенное $.validField("user_password", true); } // Проверяем логин пользователя, являющийся одновременно и адресом электропочты // Если он не прошел проверку, прекращаем проверку checkLogin = $.checkLogin(login, form.attr("action")); if (!checkLogin.success) { $.showError("user_login", checkLogin.message); // Помечаем поле как проверенное $.validField("user_login", false); return false; } else { // Помечаем поле как проверенное $.validField("user_login", true); } // Если нет ошибок, регистрируем пользователя if ($("#user-registration .error:visible").length == 0) { var request = {}; // Формируем объект данных, который будет передан AJAX-запросом request.data = { request: "userRegistration", login: login, password: password, password_approve: password_approve }; // URL для AJAX-запроса request.url = $("#user-registration").attr("action"); // Запрос будет синхронным request.async = false; // Действия после выполнения запроса request.success = function(data, textStatus, jqXHR ) { // Сохраняем результат выполнения запроса $.response.data = data; $.response.textStatus = textStatus; $.response.jqXHR = jqXHR; // Если запрос выполнен успешно if (typeof (data) != "undefined") { // Если при регистрации что-то пошло не так if (data.success === false) { showError("user_message", data.message); } else { // Очищаем поля формы $("#user-registration").trigger("reset"); alert(data.message + "\nСейчас вы будете перенаправлены на главную страницу сайта"); window.location.href = "/"; } } }; request = $.extend({}, $.request, request); // Выполняем запрос $.ajax(request); } else { alert("При заполнении полей формы были найдены ошибки. Пожалуйста, исправьте."); } } // Если капча была заполнена некорректно, показываем ошибку else { alert("Вы не подтвердили, что вы не робот. Ответьте на вопрос, пожалуйста."); // Больше ничего не делаем return false; } }
Вероятно, читатель не ожидал увидеть так много всего. А что же это за всякие: $.checkCaptcha, $.cleanAllErrors, $.showError, $.validFields, $.checkPassword, $.checkLogin? Это методы, которые занимаются проверкой введенных пользователем данных.
Этапы проверки данных и последующая регистрация пользователя
Настало время описать словами всё, что делает метод $.userRegistrationHandler.
- Инициализируются все необходимые переменные.
- Проверяется корректность капчи Google reCAPTCHA:
- если капча корректная, обработка данных продолжается;
- если капча некорректная, дальше ничего не делаем, сообщаем об этом пользователю.
- Итак. Капча корректная. Убираем все ошибки полей формы, сбрасываем их состояние до непроверенных (метод $.cleanAllErrors):.
- если методу не была передана форма, ничего не делаем, возвращаем false;
- скрываем все поля ошибок внутри формы, убираем тексты ошибок;
- ищем текстовые поля формы, снимаем с них пометки проверки;
- если есть поле для сообщений пользователю, очищаем и его;
- сбрасываем капчу.
- Если введенные пользователем пароли не идентичны, показываем ошибку. В ином случае помечаем поле корректным.
- Если пароль не соответствует требованиям, показываем ошибку. В ином случае помечаем поле корректным.
- Если логин пользователя не признан корректным, а он у нас является адресом электропочты, показываем ошибку. В ином случае помечаем поле корректным. На этом же шаге проверяется уникальность логина.
- Если нет отображенных ошибок, можем регистрировать пользователя.
- Если регистрация была успешной, отправляем пользователя на главную страницу сайта, а на сайте он уже будет авторизован.
Вот и всё, в общем-то. Ну, это мы увидели JavaScript-сторону процесса. И она, к тому же, неполная. Весь файл func.js выглядит следующим образом.
// Файл /js/func.js // Используем строгий режим ECMAScript 5 "use strict"; $.extend({ /** * Параметры по умолчанию для AJAX-запросов */ request: { // Путь для запросов url: "", // Метод запросов type: "POST", // Тип данных, которые ожидаются в ответ от сервера dataType: "json", // Действия, выполняемые при неуспешных запросах error: function(jqXHR, textStatus, errorThrown) { // Пишем текст ошибки в консоль console.log("textStatus: " + textStatus + ". Error: " + errorThrown); // Выводим ошибку на экран alert("textStatus: " + textStatus + ". Error: " + errorThrown); } }, /** * Объект для хранения результатов AJAX-запросов */ response: { data: null, textStatus: null, jqXHR: null }, /** * Очищает все поля формы от сообщений об ошибках */ cleanAllErrors: function(form) { // Если не передана форма, ничего не делаем if (!form) { return false; } // Внутри формы ищем все поля для сообщений об ошибке, очищаем их и убираем с экрана $( form ).find(".error").hide().text("") // Для всех полей формы убираем классы валидации .end() .find("input:text, input:password, input[type='email']") .removeClass("is-valid").removeClass("is-invalid"); // Если есть поле для сообщения пользователю if ($("#user-message").length) { // Очищаем и его $("#user-message").html(""); } // Сбрасываем капчу if (typeof (grecaptcha) != "undefined" && typeof (grecaptcha.reset) == "function") { grecaptcha.reset(); } }, /** * Очищает поле формы от ошибки */ cleanError: function(event) { // Если не передано поле, ничего не делаем if (!event) { return false; } // Сохраняем идентификатор поля формы var field = event.currentTarget, id = $( field ).attr("id"); // Очищаем у поля классы валидации $( field ).removeClass("is-valid").removeClass("is-invalid"); // Очищаем поле ошибки, если оно есть $("#" + id + "-error").hide().text(""); }, /** * Показывает ошибку для поля формы */ showError: function(sFieldId, sMessage) { // Если не передан идентификатор поля, ничего не делаем if (!sFieldId) { console.log("Не передан идентификатор поля формы"); return false; } var field = $("#" + sFieldId), // Поле формы // Поле для сообщения об ошибке errorField = $("#" + sFieldId + "-error"), // Текст сообщения об ошибке по умолчанию sMessage = sMessage || "Поле заполнено неверно. Проверьте"; // Показываем сообщение об ошибке field.addClass("is-invalid"); errorField.text(sMessage).show(); }, /** * Помечает поле указанным способом */ validField: function(fieldId, validType) { // Результат проверки по-умолчанию validType = validType || true; if (!fieldId) { console.log("Не указано поле, которое необходимо отметить как проверенное"); alert("Не указано поле, которое необходимо отметить как проверенное"); return false; } var className = (validType === true) ? "is-valid" : "is-invalid"; $("#" + fieldId).addClass(className); }, /** * Проверяет корректность капчи */ checkCapctha: function(sCaptcha, url) { // Если нет капчи, проверка не может быть успешной if (!sCaptcha) { return false; } // URL для запроса url = url || ""; // Подготавливаем данные для AJAX-запроса var request = {}; // Строка AJAX-запроса request.data = "request=checkCaptcha&captchaValue=" + sCaptcha; // URL для AJAX-запроса request.url = url; // Запрос будет синхронный request.async = false; // Действия после выполнения запроса request.success = function(data, textStatus, jqXHR ) { // Сохраняем результат выполнения запроса $.response.data = data; $.response.textStatus = textStatus; $.response.jqXHR = jqXHR; }; request = $.extend({}, $.request, request); // Выполняем запрос $.ajax(request); // Возвращаем результат проверки капчи return $.response.data.success; }, /** * Проверяет пароль на соответствие требованиям */ checkPassword: function(sPassword) { var response = { success: false, message: "Пароль для проверки не передан" }, pattern1 = {}, pattern2 = {}, pattern3 = {}; // Если не передан пароль if (!sPassword) { return response; } /** * Нужно проверить пароль на соответствие нескольким требованиям * * 1. Длина пароля не менее 6 и не более 32 символов * 2. Пароль не может содержать символы кроме символов латинского алфавита и *-+!@#$[]{}'";:><,. * 3. Пароль доджен содержать как минимум одну цифру, и как минимум одну заглавную букву * * Для каждого из условий добавляем проверку */ // Для указанного щаблона регулярного выражения... pattern1.pattern = /^.{6,32}$/; // ...нужен положительный результат pattern1.result = true; // Для указанного щаблона регулярного выражения... pattern2.pattern = /[^-\w\d*+!@#$\[\]\{\}'";:><,.]/; // ...нужен отрицательный результат pattern2.result = false; // Для указанного щаблона регулярного выражения... pattern3.pattern = /([A-Z].+?\d|\d.+?[A-Z])/; // ...нужен положительный результат pattern3.result = true; if (pattern1.pattern.test(sPassword) !== pattern1.result) { response.message = "Пароль не соответствует требованиям по длине"; return response; } else if (pattern2.pattern.test(sPassword) !== pattern2.result) { response.message = "Пароль содержит недопустимые символы"; return response; } else if (pattern3.pattern.test(sPassword) !== pattern3.result) { response.message = "Пароль не содержит в себе обязательные симовлы"; return response; } // Если все проверки прошли else { response.success = true; response.message = "Пароль соответствует требованиям"; } return response; }, /** * Проверяет логин пользователя * * Проверка логина состоит из двух этапов. * * 1. Проверяем корректность указанного адреса электропочты * 2. Проверяем уникальность указанного адреса электропочты */ checkLogin: function(email, url) { var response = { success: false, message: "Не удалось проверить логин пользователя" }, request = {}; // Путь для выполнения AJAX-запросов по умолчанию url = url || ""; if (!email) { console.log("Не передано значение логина пользователя для проверки"); response.message = "Не передано значение логина пользователя для проверки"; return response; } var pattern = /^[-_.\da-z]+@[-.a-z\d]{1,}\.(ru|com|su|[a-z]{2,4})$/i; // Убеждаемся в корректности адреса электропочты if (!pattern.test(email)) { response.message = "Некорректный формат адреса электропочты"; return response; } // Проверяем уникальность логина пользователя request.data = "request=checkLoginExists&email=" + email; // URL для AJAX-запроса request.url = url; // Запрос будет синхронным request.async = false; // Действия после выполнения запроса request.success = function(data, textStatus, jqXHR ) { // Сохраняем результат выполнения запроса $.response.data = data; $.response.textStatus = textStatus; $.response.jqXHR = jqXHR; // Если запрос выполнен успешно if (typeof (data) != "undefined") { // Если email не уникален if (data.success === false) { response.message = data.message; } else { response.success = true; response.message = "Указанный логин доступен для регистрации"; } } }; request = $.extend({}, $.request, request); // Выполняем запрос $.ajax(request); return response; }, /** * Обработывает форму регистрации пользователя на сайте по событию отправки формы */ userRegistrationHandler: function() { var form = $( this ), // Сама форма регистрации пользователя на сайте login = $("#user_login").val(), // Логин пользователя password = $("#user_password").val(), // Пароль пользователя password_approve = $("#user_password_approve").val(), // Подтвержденный пароль пользователя sCaptcha = form.find(".g-recaptcha-response").val(), // Значение капчи Google reCAPTCHA checkPassword, checkLogin; // Сначала проверяем капчу // Продолжаем обработку формы только в случае корректной капчи if ($.checkCapctha(sCaptcha, form.attr("action"))) { // Очищаем все поля ошибок $.cleanAllErrors(this); // Если введенные пароли не идентичны, прекращаем обработку if (password !== password_approve) { $.showError("user_password_approve", "Введенные вами пароли не совпадают"); // Помечаем поле как проверенное $.validField("user_password_approve", false); return false; } else { // Помечаем поле как проверенное $.validField("user_password_approve", true); } // Проверяем пароль на соответствие требованиям // Если он не подходит, прекращаем работу checkPassword = $.checkPassword(password); if (!checkPassword.success) { $.showError("user_password", checkPassword.message); // Помечаем поле как проверенное $.validField("user_password", false); return false; } else { // Помечаем поле как проверенное $.validField("user_password", true); } // Проверяем логин пользователя, являющийся одновременно и адресом электропочты // Если он не прошел проверку, прекращаем проверку checkLogin = $.checkLogin(login, form.attr("action")); if (!checkLogin.success) { $.showError("user_login", checkLogin.message); // Помечаем поле как проверенное $.validField("user_login", false); return false; } else { // Помечаем поле как проверенное $.validField("user_login", true); } // Если нет ошибок, регистрируем пользователя if ($("#user-registration .error:visible").length == 0) { var request = {}; // Формируем объект данных, который будет передан AJAX-запросом request.data = { request: "userRegistration", login: login, password: password, password_approve: password_approve }; // URL для AJAX-запроса request.url = $("#user-registration").attr("action"); // Запрос будет синхронным request.async = false; // Действия после выполнения запроса request.success = function(data, textStatus, jqXHR ) { // Сохраняем результат выполнения запроса $.response.data = data; $.response.textStatus = textStatus; $.response.jqXHR = jqXHR; // Если запрос выполнен успешно if (typeof (data) != "undefined") { // Если при регистрации что-то пошло не так if (data.success === false) { showError("user_message", data.message); } else { // Очищаем поля формы $("#user-registration").trigger("reset"); alert(data.message + "\nСейчас вы будете перенаправлены на главную страницу сайта"); window.location.href = "/"; } } }; request = $.extend({}, $.request, request); // Выполняем запрос $.ajax(request); } else { alert("При заполнении полей формы были найдены ошибки. Пожалуйста, исправьте."); } } // Если капча была заполнена некорректно, показываем ошибку else { alert("Вы не подтвердили, что вы не робот. Ответьте на вопрос, пожалуйста."); // Больше ничего не делаем return false; } } }); (function($) { jQuery(function() { }); })(jQuery);
Что же, проверка капчи, проверка уникальности логина и сама регистрация происходят через AJAX-запросы. Они отправляются в сценарий по адресу /service.php. Давайте посмотрим, что есть в файле /service.php.
Сценарий PHP для обработки AJAX-запросов
<?php // Файл service.php // Инициализируем ядро сайта require_once("bootstrap.php"); // Делаем что-либо только если выполняется запрос if (!empty($_REQUEST) && !empty(Core_Array::getRequest("request"))) { // По умолчанию данные передаём в формате JSON $bJson = TRUE; // По умолчанию прерываем работу сценария $bExit = TRUE; // Данные, отправляемые в ответ на запрос по умолчанию $response = array( "success" => FALSE, "message" => "При выполнении запроса что-то пошло не так" ); // Сохраняем строку значения типа запроса $sRequest = strval(Core_Array::getRequest("request")); // Для разных типов запроса разные действия switch ($sRequest) { // Проверка корректности капчи Google reCAPTCHA case "checkCaptcha": // Сохраняем полученное значение капчи $sCaptcha = strval(Core_Array::getRequest("captchaValue")); // Получаем параметры конфигурации для Google reCAPTCHA $aCaptcha_Config = Core_Config::instance()->get("captcha"); // Формируем данные для передачи в запросе $aData = array( "secret" => $aCaptcha_Config["secret_key"], "response" => $sCaptcha ); // Запрос к серверу Google выполним через cURL $curl = curl_init(); // Указываем параметры запроса curl_setopt($curl, CURLOPT_URL, $aCaptcha_Config["url_verify"]); // Нам нужен возврат результата, а не вывод в браузер curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // Будем ли передавать заголовки curl_setopt($curl, CURLOPT_HEADER, FALSE); // Подставляем передаваемые данные curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($aData)); // Выполняем запрос и сохраняем его результат $sResponse = curl_exec($curl); // Преобразуем ответ в массив $response = json_decode($sResponse, TRUE); break; // Проверка уникальности логина пользователя case "checkLoginExists": // Сохраняем переданное значение логина $sEmail = strval(Core_Array::getRequest("email")); // Пробуем найти пользователя по указанному логину $oUser = Core::factory("User") ->getByLogin($sEmail); // Если такого пользователя нет if (is_null($oUser)) { $response["success"] = TRUE; $response["message"] = "Указанный вами логин доступен для регистрации"; } // Если такой логин в системе уже зарегистрирован else { $response["message"] = "Пользователь с указанным логином уже зарегистрирован"; } break; // Регистрация нового пользователя case "userRegistration": // Сохраняем переданные данные $login = strval(Core_Array::getRequest("login")); $password = strval(Core_Array::getRequest("password")); $password_approve = strval(Core_Array::getRequest("password_approve")); // Создаем объект модели пользователя $oUser = Core::factory("User"); // Создаем объект контроллера пользователя $User_Controller = new User_Controller($oUser); // Добавляем в контроллер полученные данные $User_Controller->setLogin($login) ->setPassword($password) ->setPasswordApprove($password_approve); // Сначала проверяем все переданные данные // Если данные не прошли проверку, больше ничего не делаем if (!$User_Controller->checkRegistrationData()) { $response["message"] = "Переданные на регистрацию данные о пользователе не прошли проверку."; break; } // Регистрируем пользователя, сохраняя его в БД $iUserId = $User_Controller->registration(); // Если пользователь успешно сохранен if (!is_null($iUserId)) { // Авторизуем на сайте нового пользователя сразу же $oUser->authUser(); /** * Это может показаться странным, но методу authUser() действительно * не передаётся никакой идентификатор. В объект модели уже были * загружены необходимые данные пользователя в процессе его регистрации */ $response["success"] = TRUE; $response["message"] = "Пользователь с логином " . $oUser->login . " успешно зарегистрирован"; } else { $response["message"] = "Не удалось зарегистрировать пользователя с логином " . $login; } break; } // Если требуется представить данные в формате JSON $bJson && print json_encode($response); // Если требуется прервать выполнение скрипта $bExit && exit(); } else { exit("Доступ к разделу запрещен"); } ?>
В целом всё должны быть понятно и очевидно. При проверке капчи с помощью cURL выполняется запрос к серверу Google, который присылает нам ответ. Мы ничего не делаем с телом ответа, просто передаём его, в свою очередь, в ответ на AJAX-запрос проверки капчи. И там уже JavaScript-сценарий определяет дальнейшие действия на основании содержимого ответа от Google.
Проверка уникальности логина тоже не самая сложная. Но для читателя здесь новым окажется метод: ->getByLogin() объекта модели пользователя сайта. Этот метод объект модели пользователя наследует от класса Core_ORM через реализацию в нём магического метода __call(). То есть может быть множество иных методов с именами типа: getByPassword(), getByBirthday() и еще любые иные.
Код реализации управления регистрацией пользователя не очень обширный, но для читателя здесь тоже появляется нечто новое: контроллер объекта модели пользователя. С помощью модели мы лишь получаем или редактируем данные, а вот манипулировать ими будем с помощью контроллера.
Несмотря на то, что данные для регистрации были проверены еще JavaScript-сценарием, мы не поленимся проверить их вновь. Если будут ошибки, работа сценария прервется. А в итоге успешно зарегистрированный пользователь будет авторизован на сайте.
Итак, приводим пример реализации вышеописанного кода. Первое, что нужно показать, это реализация работы метода getByLogin(). За это отвечает класс Core_ORM.
Реализация работы метода getByLogin()
Читателя здесь заинтересует то, что написано для метода __call().
<?php // Файл modules/core/orm.php defined("LEZH") || exit("Доступ к файлу запрещен"); class Core_ORM { // Здесь будет храниться экземпляр объекта класса работы с СУБД protected $_oDatabase = NULL; // Имя таблицы для запросов protected $_tableName = NULL; // Имена полей таблицы protected $_columnNames = array(); // Объект запроса к СУБД protected $_oQuery = NULL; // Первичный ключ таблицы, его значение protected $_primaryKey = NULL; // Статус загрузки данных модели из БД protected $_loaded = FALSE; // Данные модели, загруженные из БД protected $_modelColumns = array(); // Перечень полей модели, запрещенных к загрузке protected $_forbiddenFields = array(); // Поля, требующие сохранения значений protected $_modelFieldsValues = array(); public function __construct($iPrimaryKey) { // Подключаемся к СУБД $this->_oDatabase = Core_Database::instance(); // Получаем имя таблицы, к которой будет выполняться запрос // Таблица будет совпадать либо с именем класса, либо будет // указана в свойстве _modelName дочернего класса // Если имя таблицы не указано в свойстве класса if (empty($this->_modelName)) { /** * Модели могут называться по-разному, но в базе данных таблицы * будут иметь названия во множественном числе * * Например, модель User — таблица users * модель Wireless — таблица wirelesses */ $this->_tableName = $this->_getModelName(); } // Если оно указано в свойстве класса else { $this->_tableName = $this->_modelName; } // Подключаемся к таблице, получаем перечень её полей $this->_getModelColumnNames(); // Если был передан первичный ключ модели, ввыбираем данные по нему if (!empty($iPrimaryKey)) { $this->setPrimaryKey($iPrimaryKey); $this->_load(); } // Объект готов к работе } /** * Получает имя модели из наименования класса */ protected function _getModelName() { // Получаем имя класса $sClassName = get_class($this); // Разбираем имя класса $aClassName = explode("_", $sClassName); // Если массив пустой, значит в имени было только одно слово // Если имя класса состояло из нескольких слов, нам нужно последнее if (!empty($aClassName)) { $sClassName = array_pop($aClassName); // Если последним элементом было слово Model, отбрасываем его $sClassName = array_pop($aClassName); } // Определяем окончание имени класса // Если окончание является одним из: —s, —ss, —sh, —ch, —tch, —z, —x if (preg_match("/^(?<word>.+[^s])(?<ending>ss?|sh|t?ch|z|x)$/Dsui", $sClassName, $matches)) { // Если определено окончание if (!empty($matches["ending"])) { // К имени класса просто добавляем -es $sClassName .= "es"; } } // Если окончанием является —y elseif (preg_match("/^(?<word>.+)(?<ending>y)$/Dsui", $sClassName, $matches)) { // Если определено окончание if (!empty($matches["ending"])) { // Отбрасывем окончание, к слову добавляем -ies $sClassName = $matches["word"] . "ies"; } } // В иных случаях добавляем к слову имени класса -s else { $sClassName .= "s"; } return strtolower($sClassName); } /** * Получает имена полей таблицы в БД */ protected function _getModelColumnNames() { $this->_columnNames = $this->getColumnNames($this->_tableName); return $this; } public function getColumnNames($tableName) { return $this->query() ->getColumnNames($tableName); } /** * Загружает данные в модель по конкретному значению первичного ключа */ public function find(int $iPrimaryKey) { $this->_load($iPrimaryKey); return $this; } /** * Загружает данные модели из БД */ protected function _load($iPrimaryKey = NULL) { // Переключаем статус загрузки данных модели из БД $this->_loaded = FALSE; // Загружаем данные $oQuery = $this->query() ->select() ->from($this->_tableName) ->asAssoc(); // Получаем имя поля, являющимся первичным ключом $sFieldName = $this->_columnNames["primary"]; // Если в модели уже есть заданное значение первичного ключа if (!is_null($this->_primaryKey)) { $oQuery->where($sFieldName, "=", $this->_primaryKey); } // Если был передан конкретный идентификатор elseif (!is_null($iPrimaryKey)) { // Добавляем условие отбора данных $oQuery->where($sFieldName, "=", $iPrimaryKey); } // Иначе загружать нечего else { return NULL; } $oQuery->execute(); $aLoadedValues = $oQuery->fetchAll()[0]; // Перебираем значения, ищем запрещенные к загрузке if (!empty($this->_forbiddenFields)) { // Для каждого из таких запрещенных полей foreach ($this->_forbiddenFields as $sFieldName) { // Если в загруженных результатах есть такое поле if (array_key_exists($sFieldName, $aLoadedValues)) { // Убираем его unset($aLoadedValues[$sFieldName]); } } } // Сохраняем загруженные данные $this->_modelColumns = $aLoadedValues; // Переключаем статус загрузки данных модели из БД $this->_loaded = TRUE; return $this; } /** * Создает запрос к СУБД */ public function query() { $this->_oQuery = $this->_oDatabase->query(); // По умолчанию добавляем в объект запроса имя таблицы, если она есть, // оператором по умолчанию устанавливаем SELECT if (!empty($this->_columnNames)) { $this->_oQuery->table($this->_tableName) ->select(); } return $this->_oQuery; } /** * Сохраняет данные, которые были переданы для этого в модель */ public function save() { // Если ничего для сохранения нет, ничего не делаем if (empty($this->_modelFieldsValues)) { return NULL; } // Создаем объект запроса $oQuery_Save = $this->query(); $oQuery_Save->table($this->_tableName); // Поля для вставки в таблицу $aFields = array(); // Значения для вставки в таблицу $aValues = array(); foreach ($this->_modelFieldsValues as $aFieldValue) { // Если поле не требовало внесения изменений, ничего не делаем if ($aFieldValue["changed"] === FALSE) { continue; } $aFields[] = $aFieldValue["field"]; $aValues[] = $aFieldValue["value"]; } // Добавляем имена полей и значения для сохранения $oQuery_Save->fields($aFields) ->values($aValues); // Если у модели нет значения поля первичного ключа, данные нужно добавить в таблицу if (is_null($this->getPrimaryKey())) { $oQuery_Save->insert(); } // Иначе нужно обновить данные в уже имеющейся строке else { $oQuery_Save->update(); } // Выполняем запрос $oQuery_Save->execute(); // Если у модели не было значения первичного ключа, и если теперь такой первичный ключ есть if (is_null($this->getPrimaryKey()) && $oQuery_Save->lastInsertId()) { // Устанавливаем значение первичного ключа для модели $this->setPrimaryKey($oQuery_Save->lastInsertId()); } // Возвращаем вызову результирующий набор запроса return $oQuery_Save; } /** * Устанавливает значение первичного ключа */ public function setPrimaryKey($iPrimaryKey = 0) { if ($iPrimaryKey) { $this->_primaryKey = $iPrimaryKey; } return $this; } /** * Возвращает значение первичного ключа для объекта модели, если оно есть */ public function getPrimaryKey($primaryKeyFieldName = "id") { // Если значение было установлено ранее if (!is_null($this->_primaryKey)) { // Вернем его return $this->_primaryKey; } // Если есть такое свойство if (!empty($this->$primaryKeyFieldName)) { return $this->$primaryKeyFieldName; } // Если такого свойства нет, возможно, первичный ключ установлен не для поля ID или указанного вызовом поля $primaryKeyFieldName = $this->_columnNames["primary"]; // Если есть такое свойство if (!empty($this->$primaryKeyFieldName)) { return $this->$primaryKeyFieldName; } // Если нет и его else { return NULL; } } /** * Устанавливает значения, которые необходимо сохранить или обновить в БД для модели */ protected function _setFieldValue($field, $value, $changed = TRUE) { $array = array( "field" => $field, "value" => $value, "changed" => $changed ); $this->_modelFieldsValues[] = $array; } /** * Чтобы обеспечить работу с моделями данных по типу * $Model->field($value) или $Model->field = $value * * нам нужно определить так называемые магические методы __get(), __set() и __call() */ /** * Получает значение указанного поля таблицы БД * @param string $property */ public function __get($property) { $property = strtolower($property); $return = NULL; // Если запрошено поле id, попробуем вернуть первичный ключ if ($property === "id") { return $this->getPrimaryKey(); } // Если указанное свойство есть в перечне полей таблицы, и оно не запрещено к чтению if (in_array($property, $this->_columnNames) && !in_array($property, $this->_forbiddenFields)) { // Если данные не были загружены ранее if ($this->_loaded === FALSE) { // Загружаем данные $this->_load(); } return ($this->_loaded === TRUE) ? $this->_modelColumns[$property] : NULL; } // Если такого свойства нет, возвращаем пустую строку else { return ""; } return $return; } /** * Вызов недоступных методов модели */ public function __call($func, $args) { // Если вызывается метод, название которого начинается с ->getByXXX if (preg_match("/^getBy(.+)$/Dsui", $func, $matches)) { // Сохраняем значение для поиска $sValue = strval($args[0]); // Сохраняем имя поля таблицы, по которому происходит отбор $sFieldName = strtolower($matches[1]); // Выполняем запрос к БД $object = $this->query() // Отбираем поле по запрошенному значению ->where($sFieldName, "=", $sValue) // Выполняем запрос ->execute(); // Получаем результат запроса $result = $object->fetchAll(); // Если результат запроса не пустой, возвращаем его return (!empty($result)) ? $result[0] : NULL; } // Если не было передано никаких аргументов if (empty($args)) { return NULL; } // Если устанавливается значение полей для сохранения или обновления $this->_setFieldValue($func, $args[0]); return $this; } } ?>
Реализация метода query() представлена здесь же. Теперь давайте посмотрим, что происходит после того, как за методом query() вызываются еще методы ->where()->execute(). Для этого приведем код реализации класса Core_Database_Pdo.
<?php // Файл modules/core/database/pdo.php defined("LEZH") || exit("Доступ к файлу запрещен"); /** * Класс для создания и выполнения запросов к СУБД */ class Core_Database_Pdo { // Подключение к СУБД protected $_pdo = NULL; // Формируемая строка запроса protected $_sql = ""; // Значения оператора SELECT protected $_select = ""; // Имя таблицы для запросов protected $_table = NULL; // Значения оператора FROM protected $_from = ""; // Условия оператора WHERE protected $_where = array(); // Число затронутых запросом строк protected $_affectedRows = 0; // Идентификатор последней вставленной строки protected $_lastInsertId = NULL; // Значения оператора ORDER BY // По умолчанию сортировка делается по полю id по возрастанию protected $_orderBy = array( "id" => "ASC" ); // Именя полей для операторов UPDATE или INSERT protected $_fields = array(); // Вставляемые значения для операторов UPDATE или INSERT protected $_values = array(); // Значения оператора INSERT protected $_insert = ""; // Значения оператора UPDATE protected $_update = ""; // Значения оператора DELETE protected $_delete = ""; // Объект подготовленного запроса к БД protected $_pdoStatement = NULL; // Результирующий запрос представить в виде анонимного объекта protected const FETCH_OBJ = 1; // Результирующий запрос представить в виде ассоциативного массива protected const FETCH_ASSOC = 2; // Тип представления результирующего запроса protected $_fetchType = NULL; public function __construct(PDO $object) { $this->_pdo = $object; // Устанавливаем тип представления результирующего запроса по умолчанию $this->_fetchType = static::FETCH_OBJ; } /** * Получает перечень имен полен таблицы */ public function getColumnNames($tableName) { if (empty($tableName)) { throw new Exception("Не передано имя таблицы"); } // Формируем строку запроса $this->_sql .= "SHOW COLUMNS"; $this->from($tableName); // Выполняем запрос // Сортировать нам будет нечего $this->clearOrderBy() ->execute(); $aFields = array(); // Сохраняем поля таблицы в массив while ($row = $this->_pdoStatement->fetchObject()) { // Если поле имеет первичный ключ, отмечаем это особым образом if (!empty($row->Key) && $row->Key == "PRI") { $aFields["primary"] = $row->Field; } else { $aFields[] = $row->Field; } } // Возвращаем вызову массив имен полей таблицы return $aFields; } /** * Устанавливает имя таблицы, к которой выполняется запрос */ public function table($table) { if (empty($table)) { throw new Exception("Не передано имя таблицы для запросов"); } $this->_table = "`" . strval($table) . "`"; return $this; } /** * Получает все результаты запроса */ public function fetchAll() { // Для установленных типов представления результата запроса switch ($this->_fetchType) { // Для представления в виде ассоциативного массива case static::FETCH_ASSOC: $option = PDO::FETCH_ASSOC; break; // Для представления в виде анонимного объекта case static::FETCH_OBJ: $option = PDO::FETCH_OBJ; break; } // Получаем все результаты запроса $result = $this->_pdoStatement->fetchAll($option); $return = (!empty($result)) ? $result : NULL; // Возвращаем результаты вызову метода return $return; } /** * Устанавливает тип представления результирующего запроса в виде ассоциативного массива */ public function asAssoc() { $this->_fetchType = static::FETCH_ASSOC; return $this; } /** * Устанавливает тип представления результирующего запроса в виде анонимного объекта */ public function asObject() { $this->_fetchType = static::FETCH_OBJ; return $this; } public function select($selection = "*") { $this->_select = $selection; return $this; } public function from($tableName) { if (empty($tableName)) { throw new Exception("Не передано имя таблицы для оператора FROM"); } $tableName = htmlspecialchars(trim($tableName)); $this->_from = "`$tableName`"; return $this; } public function insert() { $this->_insert = "INSERT INTO "; return $this; } public function update() { $this->_update = "UPDATE "; return $this; } public function where($field, $operand, $condition) { $this->_where[] = array( "field" => strval($field), "operand" => strval($operand), "condition" => strval($condition), "or" => FALSE ); return $this; } /** * Выполняет запрос к СУБД */ public function execute() { // Выполняем запрос $sql = $this->build(); $this->_pdoStatement = $this->_pdo->query($sql); // Сохраняем число затронутых строк if (!empty($this->_insert) || !empty($this->_update) || !empty($this->_delete)) { $this->_affectedRows = $this->_pdoStatement->rowCount(); // Если был вызван оператор INSERT if (!empty($this->_insert)) { // Сохраним идентификатор последней вставленной строки $this->_lastInsertId = $this->_pdo->lastInsertId(); } } // Строку запроса сохраняем в массив $this->_lastQueryString($sql); // Очищаем саму строку запроса $this->clearSql(); return $this; } /** * Возвращает число затронутых запросом строк */ public function affectedRows() { return $this->_affectedRows; } /** * Возвращает идентификатор последней вставленной строки */ public function lastInsertId() { return $this->_lastInsertId; } /** * Сохраняет строку запроса в памяти */ protected function _lastQueryString(string $sql) { Core_Database::addSql($sql); } /** * Очищает строку запроса */ public function clearSql() { $this->_sql = ""; return $this; } /** * Очищает параметры сортировки результатов запроса */ public function clearOrderBy() { $this->_orderBy = array(); return $this; } /** * Устанавливает перечень полей для операторов INSERT или UDPATE */ public function fields($data) { // Если данные переданы в виде массива if (is_array($data)) { $this->_fields = $data; } // Если в виде строки elseif(is_string($data)) { // Убираем пробелы и скобки из строки $data = preg_replace("/(\)|\(|\s)/", "", $data); $this->_fields = explode(",", $data); } else { throw new Exception("Не передано корректное значение массива полей таблицы для операторов INSERT или UPDATE"); } return $this; } /** * Устанавливает перечень значений для вставки операторами INSERT или UDPATE */ public function values($data) { // Если данные переданы в виде массива if (is_array($data)) { $this->_values[] = $data; } // Если в виде строки elseif(is_string($data)) { // Убираем пробелы и скобки из строки $data = preg_replace("/(\)|\(|\s)/", "", $data); $this->_values[] = explode(",", $data); } else { throw new Exception("Не передано корректное значение массива полей таблицы для операторов INSERT или UPDATE"); } return $this; } /** * Формирует строку SQL-запроса */ public function build() { // Получаем то значение строки, которое уже есть $sql = $this->_sql; // Если был объявлен оператор SELECT if (!empty($this->_select)) { $sql = "SELECT " . $this->_select . " "; } // Если был объявлен оператор INSERT if (!empty($this->_insert)) { $sql = $this->_insert; } // Если был объявлен оператор UPDATE if (!empty($this->_update)) { $sql = $this->_update; } // Если был объявлен оператор FROM if (!empty($this->_from)) { $from = $this->_from; } elseif(!is_null($this->_table)) { $from = $this->_table; } // Если не были объявлены INSERT или UPDATE $sql .= (empty($this->_insert) && empty($this->_update)) ? " FROM " . $from . " " : $from . " "; // Добавляем условия отбора результатов запроса if (!empty($this->_where) && empty($this->_insert)) { // В любом случае выбираем только неудаленные элементы $sql .= " WHERE `deleted` = 0"; // Перебираем все созданные условия отбора foreach ($this->_where as $aWhere) { // Если вместо AND требовалось использовать оператор OR, используем его $sql .= ($aWhere["or"] !== TRUE) ? " AND " : " OR "; // Формируем строку условия отбора $sql .= "`" . $aWhere["field"] . "` " . $aWhere["operand"] . " " . $this->_pdo->quote($aWhere["condition"]); } } // Если есть параметры сортировки результата запроса if (!empty($this->_orderBy) && empty($this->_insert) && empty($this->_update)) { $sql .= " ORDER BY "; $i = 0; // Добавляем параметры сортировки foreach ($this->_orderBy as $orderByField => $orderByDirection) { // Если это не первый элемент сортировки, добавляем запятую $sql .= ($i > 0) ? ", " : ""; // Формируем строку условия сортировки $sql .= "`" . $orderByField . "` " . $orderByDirection; // Увеличиваем счетчик условий сортировки ++$i; } } // Если объявлен оператор INSERT if (!empty($this->_insert) && !empty($this->_fields) && !empty($this->_values)) { $sql .= "(" . implode(",", $this->_fields) . ")"; $sql .= " VALUES "; $i = 0; foreach ($this->_values as $aValues) { // Если это не первая группа значений, добавляем запятую $sql .= ($i > 0) ? ", " : ""; $sql .= "("; $array = array(); // Обрабатываем каждое из значений foreach ($aValues as $value) { $array[] = $this->_pdo->quote($value); } $sql .= implode(",", $array); $sql .= ")"; } } // Сохраняем строку запроса $this->_sql = $sql; // Возвращаем строку запроса вызову метода return $this->_sql; } }
Итак, если пользователя с таким логином нет, регистрация возможна. Переходим к описанию её реализации.
Сначала сохраняются переданные значения логина, пароля и подтвержденного пароля. А затем объект модели пользователя передается контроллеру. Давайте посмотрим на этот контроллер.
Контроллер объекта модели пользователя
<?php // Файл modules/core/user/controller.php defined("LEZH") || exit("Доступ к файлу запрещен"); class User_Controller extends Core_Controller { // Логин и пароль пользователя protected $_userData = array(); public function __construct(User_Model $oUser) { // Вызываем конструктор родительского класса, // который сохранит объект модели пользователя parent::__construct($oUser); } /** * Проверяет пользовательские данные перед регистрацией */ public function checkRegistrationData() { if (empty($this->_userData["login"])) { exit("Не установлено значение логина пользователя для проверки"); } if (empty($this->_userData["password"])) { exit("Не установлено значение пароля пользователя для проверки"); } if (empty($this->_userData["password_approve"])) { exit("Не установлено значение подтверждения пароля пользователя для проверки"); } // Введенные пароли должны быть идентичны if ($this->_userData["password"] !== $this->_userData["password_approve"]) { return FALSE; } // В качестве логина пользователя должен быть указан корректный адрес электропочты if (!filter_var($this->_userData["login"], FILTER_VALIDATE_EMAIL)) { return FALSE; } // Логин пользователя дожен быть уникальным $oUser = Core::factory("User") ->getByLogin($this->_userData["login"]); // Если такой пользователь есть, прекращаем выполнение сценария if (!is_null($oUser)) { return FALSE; } /** * Нужно проверить пароль на соответствие нескольким требованиям * * 1. Длина пароля не менее 6 и не более 32 символов * 2. Пароль не может содержать символы кроме символов латинского алфавита и *-+!@#$[]{}'";:><,. * 3. Пароль доджен содержать как минимум одну цифру, и как минимум одну заглавную букву * * Для каждого из условий добавляем проверку */ if (!preg_match("/^.{6,32}$/", $this->_userData["password"]) || preg_match("/[^-\w\d*+!@#$\[\]\{\}'\"