Система активации пользователей по email на PHP

23.09.2023 в 14:48
10788
+632

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

Прежде чем начать, вам нужно настроить OpenServer для отправки писем.

После того, как вы это сделали, можно приступать к написанию кода. Первое, что нам нужно – создать новую табличку, в которой мы будем хранить коды для активации пользователей.
Называем её «users_activation_codes», и указываем, что нам требуются три столбца:

  • id – это просто id записи в таблице;
  • user_id – id пользователя;
  • code – код для активации этого пользователя.
CREATE TABLE `users_activation_codes` (
  `id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `code` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `users_activation_codes`
  ADD PRIMARY KEY (`id`);

ALTER TABLE `users_activation_codes`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

Таблица с кодами активации

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

src/MyProject/Models/Users/UserActivationService.php

<?php

namespace MyProject\Models\Users;

use MyProject\Services\Db;

class UserActivationService
{
    private const TABLE_NAME = 'users_activation_codes';

    public static function createActivationCode(User $user): string
    {
        // Генерируем случайную последовательность символов, о функциях почитайте в документации
        $code = bin2hex(random_bytes(16));

        $db = Db::getInstance();
        $db->query(
            'INSERT INTO ' . self::TABLE_NAME . ' (user_id, code) VALUES (:user_id, :code)',
            [
                'user_id' => $user->getId(),
                'code' => $code
            ]
        );

        return $code;
    }

    public static function checkActivationCode(User $user, string $code): bool
    {
        $db = Db::getInstance();
        $result = $db->query(
            'SELECT * FROM ' . self::TABLE_NAME . ' WHERE user_id = :user_id AND code = :code',
            [
                'user_id' => $user->getId(),
                'code' => $code
            ]
        );
        return !empty($result);
    }
}

И теперь нам нужно сделать ещё один сервис, который будет предназначен для отправки email-сообщений.

src/MyProject/Services/EmailSender.php

<?php

namespace MyProject\Services;

use MyProject\Models\Users\User;

class EmailSender
{
    public static function send(
        User $receiver,
        string $subject,
        string $templateName,
        array $templateVars = []
    ): void {
        extract($templateVars);

        ob_start();
        require __DIR__ . '/../../../templates/mail/' . $templateName;
        $body = ob_get_contents();
        ob_end_clean();

        mail($receiver->getEmail(), $subject, $body, 'Content-Type: text/html; charset=UTF-8');
    }
}

Здесь вам все функции уже знакомы – мы использовали похожий функционал для рендеринга шаблонов. Теперь у нас появится еще один тип шаблонов – специально для email-ов.

Давайте создадим наш первый шаблон, который будет предназначен для писем активации.

templates/mail/userActivation.php

Добро пожаловать на наш портал!<br>
Для активации вашего аккаунта нажмите <a href="http://myproject.loc/users/<?=$userId?>/activate/<?=$code?>">сюда</a>.

Ну и, собственно, остаётся только взять эти компоненты и собрать воедино. Делаем это в контроллере пользователя в экшене с регистрацией.

src/MyProject/Controllers/UsersController.php

public function signUp()
{
    if (!empty($_POST)) {
        try {
            $user = User::signUp($_POST);
        } catch (InvalidArgumentException $e) {
            $this->view->renderHtml('users/signUp.php', ['error' => $e->getMessage()]);
            return;
        }

        if ($user instanceof User) {
            $code = UserActivationService::createActivationCode($user);

            EmailSender::send($user, 'Активация', 'userActivation.php', [
                'userId' => $user->getId(),
                'code' => $code
            ]);

            $this->view->renderHtml('users/signUpSuccessful.php');
            return;
        }
    }

    $this->view->renderHtml('users/signUp.php');
}

Теперь пробуем зарегистрироваться на свою почту в нашей системе.
Регистрация в системе на реальную почту

И после этого смотрим в базу данных.
Пользователи:
Зарегистрированный пользователь

Коды активации:
Код активации

Как видим, все успешно отработало, и кроме того, нам пришло письмо на почту!
Письмо с активацией

После перехода по ссылке мы видим, что такой страницы не существует.
Отсутствие страницы с активацией

Еще бы, ведь мы не добавляли для нее соответствующий роутинг. Добавляем его.

src/routes.php

...
'~^users/(\d+)/activate/(.+)$~' => [\MyProject\Controllers\UsersController::class, 'activate'],
...

И добавляем соответствующий экшен в контроллере:

src/MyProject/Controllers/UsersController.php

public function activate(int $userId, string $activationCode)
{
    $user = User::getById($userId);
    $isCodeValid = UserActivationService::checkActivationCode($user, $activationCode);
    if ($isCodeValid) {
        $user->activate();
        echo 'OK!';
    }
}

И добавляем у модели пользователя метод activate().

src/MyProject/Models/Users/User.php

public function activate(): void
{
    $this->isConfirmed = true;
    $this->save();
}

Снова пробуем обновить страничку для активации.

Успешная активация пользователя по email

Видим заветное “OK!”.

Проверяем, что в базе наш пользователь теперь подтвержден.
Активированный пользователь

Успех! Теперь осталось довести систему до ума – создать нормальные шаблоны для странички активации и обрабатывать возможные ошибки. Это вам предоставляется сделать самостоятельно в домашнем задании.

Текущая версия проекта на гитхабе.

loader
23.09.2023 в 14:48
10788
+632
Домашнее задание

Реализуйте следующий функционал:

  • после того как код был использован, он должен удаляться из базы;
  • обработайте все возможные ошибки в экшене activate() - подумайте, что будет, если в ссылку активации подставить несуществующего пользователя, что будет, если код не найден, и т.д. Чем больше сделаете - тем лучше;
  • добавьте шаблоны для всех этих случаев.
Комментарии
Этот урок набрал набрал достаточно большое количество комментариев и дальнейшее его комментирование отключено. Если вы хотели убедиться в правильности выполнения ДЗ или у вас возник вопрос по уроку, посмотрите ранее добавленные комментарии, кликнув по кнопке ниже. Скорее всего вы найдете там то, что искали. Если это не помогло - задайте вопрос в чате в телеграме - https://t.me/php_zone
Логические задачи с собеседований