Динамические структуры данных. Очередь. На примере восстановления пароля пользователя

Динамические структуры данных. Очередь. На примере восстановления пароля пользователя

От автора: в данном уроке мы с Вами поговорим о динамической структуре данных под названием очередь, на примере реализации необычного функционала по сбросу пароля пользователя. Вообще, поводом к написанию текущего урока послужила довольно нестандартная задача, которая была поставлена в рамках разработки сложного медицинского портала для зарубежного заказчика. Но обо всем по порядку.

скачать исходники

Собственно как было сказано Выше, не так давно я был увлечен в разработку веб-проекта, который по своему функционалу должен обрабатывать определенные медицинские документы и отображать информацию в специально отформатированном виде для каждого из них. При этом, конечно же, ресурс, для всеобщего посещения должен быть закрытым, а для доступа необходимо пройти регистрацию.

Но как Вы знаете, все, что связано с медициной – это строго конфиденциальная информация, в связи с чем заказчиком были поставлены различные задачи по повышению безопасности портала, одна из которых была достаточно нестандартной. Суть ее сводилась к следующему: все зарегистрированные пользователи должны через определенный промежуток времени сбрасывать свои пароли.

Казалось бы, ничего сложного, сохраняем в базе данных, в отдельном столбце, дату смены пароля и после истечения времени, при входе пользователя на сайт, показываем соответствующий интерфейс по сбросу пароля. Но в техническом задании, был прописан еще один любопытный нюанс. Дело в том, что пользователю, при смене пароля, нельзя использовать предыдущий пароль, который использовался до этого. А также нельзя использовать “пре предыдущий” пароль.

Другими словами, чтобы повторно использовать конкретный пароль, нужно пять раз сменить пароли, то есть пять раз придумать абсолютно разные пароли. Причем число пять, я указал для примера, это значение, должно задаваться в конфиге, но не в этом суть. А значит, потребовалось определенное красивое и элегантное решение, которое позволяло бы удобно хранить пароли и контролировать их количество.

Соответственно для реализации поставленной задачи, я решил задействовать как раз динамические структуры данных, а именно Очередь. Вообще сами по себе динамические структуры данных применительно к языку PHP, используются крайне редко, они более востребованы в таких языках как C, C++, C#, потому как есть существенные отличия по работе с данными.

Как следует из названия динамическая структура данных – это определенная совокупность информации, которая изменяется при выполнении скрипта. К примеру, можно провести параллель, с всеми известными массивами или объектами. При этом различают несколько видов динамических структур: очередь, стек, бинарное дерево и т.д. Конечно, все мы их рассматривать не будем, так как для этого нужны отдельные уроки, а остановимся подробно на первом представителе.

Итак, очередь — это структура данных, в которой для добавления элементов доступен только один конец, называемый хвостом, а для удаления — другой, называемый головой. В англоязычной литературе для обозначения очередей довольно часто используется аббревиатура FIFO (first-in-first-out — первый вошёл — первым вышел). Графически очередь можно отобразить следующим образом.

Конечно, возникает вопрос, как же это поможет для реализации поставленной задачи. Смотрите, каждый элемент очереди это придуманный пароль пользователя. То есть как только пользователь, сменит пароль, он сразу же будет добавлен в очередь. Перед каждой сменой пароля мы будем проверять, есть ли указанный пароль среди элементов очереди, если да, то необходимо придумать другой, если же нет, то пароль будет сохранен и добавлен в очередь. При этом, количество элементов очереди, будет ограничено, к примеру значением 5. Если перед добавлением нового элемента, количество элементов равно указанному значению 5, то перед добавлением, один элемент будет удален. А так как элементы удаляются совсем с другой стороны очереди, через пять циклов смены пароля, пользователь сможет использовать заново придуманный ранее пароль, так как он будет удален из цепочки элементов очереди.

Собственно в стандартных библиотеках PHP есть реализация класса, для работы с очередями, вот как раз данный класс мы и используем, а именно, мы создадим собственный класс, который будет наследовать функционал стандартного, но с одним допущением. По умолчанию, в очереди, нет ограничения по количеству элементов, а мы его добавим. Поэтому создадим новый файл и добавим в него следующий код:

<?php
class Passwordlist extends SplQueue
{ private $mySize = 0; public function __construct($size = 0) { if(isset($size)) { $this->mySize = $size; } } public function enqueue($value) { if($this->mySize && $this->count() == $this->mySize) { $this->dequeue(); } parent::enqueue($value); } public function toArray() { $array = []; foreach ($this as $item) { $array[] = $item; } return $array; }
}

Обратите внимание – в конструкторе, используя передаваемый аргумент, мы инициализируем значение свойства $mySize. Данное свойство как раз и содержит максимально допустимое количество элементов очереди. Далее метод enqueue(), который мы описали – переопределяет метод родительского класса и необходим для добавления элементов в очередь. При этом, по сути, мы вызываем на исполнение, родительский метод, но перед этим проверяем, если количество элементов очереди, равно значению, свойства $mySize, то один элемент из очереди, необходимо удалить. Для удаления элементов, используется метод dequeue().

Ну и третий метод, который мы описали – это преобразование элементов очереди в простой массив.

Теперь, как же использовать, созданный класс. Хотел бы отметить, что элементы очереди, мы будем хранить в базе данных в отдельной таблице, в которой будет всего три столбца: идентификатор, идентификатор пользователя и собственно содержимое – пароль пользователя.

При этом, для данного урока, я создал простенький класс для работы с базой данных, код которого приведен ниже.

<?php class Db { private $mysqli = NULL; public function __construct($host,$user,$pass,$db) { $this->mysqli = new mysqli($host,$user,$pass,$db); if ($this->mysqli->connect_error) { die('Ошибка подключения (' . $this->mysqli->connect_errno . ') ' . $this->mysqli->connect_error); } } public function __destruct() { $this->mysqli->close(); } public function changePassword($post,$id) { ///!!!md5(); $query = "UPDATE `users` SET `password` = '".md5($this->mysqli->real_escape_string($post['password']))."' WHERE `id` = ".$id; $this->mysqli->query($query); return TRUE; } public function saveList($arr,$id) { $queryDelete = "DELETE FROM `passwords` WHERE `user_id` = ".$id; $this->mysqli->query($queryDelete); $queryInsert = "INSERT INTO `passwords` (`user_id`,`password`) VALUES "; foreach($arr as $key=>$item) { if($key > 0) { $queryInsert .= ","; } $queryInsert .= "("; $queryInsert .= "'".$id."','".$item."'"; $queryInsert .= ")"; } $this->mysqli->query($queryInsert); return TRUE; } public function getPasswordsList($id) { ///!!!md5(); $query = "SELECT `password` FROM `passwords` WHERE `user_id` = ".$id; $result = $this->mysqli->query($query); if($result) { return array_map(function($item) { return $item[0]; },$result->fetch_all()); } return FALSE; }
}

Собственно когда пользователь отправляет придуманный пароль на сервер, мы первым делом, создаем объект написанного класса:

$list = new Passwordlist(5);

Далее выбираем из базы данных, все сохраненные пароли, для конкретного пользователя:

$passwordsList = $db->getPasswordsList(1);

Таким образом, в переменной $passwordsList, содержится массив ранее используемых паролей. Далее проверим есть ли совпадения с текущим отправленным паролем:

if($passwordsList && array_search(md5($_POST['password']),$passwordsList) !== FALSE) { echo 'This password cannot be used.'; }

Если есть совпадения, значит, пароль использовать нельзя. Если же нет, то формируем очередь из выбранных элементов, добавляем в нее новый пароль и сохраняем изменения в базу данных.

else { $pwd = md5($_POST['password']); if($passwordsList) { foreach($passwordsList as $item) { $list->enqueue($item); } } $list->enqueue($pwd); if($db->changePassword($_POST,1)) { $db->saveList($list->toArray(),1); header("Location:index.php"); exit(); } }

Вот, собственно, и все – задача решена, согласитесь, довольно краткое и интересное решение. А теперь давайте прощаться. Всего Вам доброго и удачного кодирования.

Да, и напишите в комментариях, хотели бы Вы увидеть уроки по другим динамическим структурам, а также как бы Вы решили поставленную задачу.