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

Про регистрацию через социальные сети можно посмотреть в этой статье:
Авторизация в Laravel, через социальные сети (Ulogin). Просто, гибко и эффективноТеперь посмотрим в нашу таблицу, что там было создано:

Из-за водяного знака плохо видно, но дата создания и дата редактирования совпадают. Отлично, давайте заметим этот момент. Дальше, токен для запоминания пользователя пустой. Тоже это для себя отметим. Но и само собой понятно, что статус пользователя равен нолю.
Что дает нам эта информация? На самом деле очень многое. Применяя системный анализ мы можем легко построить и зависимости, и алгоритм наших действий.
Раньше я хотел применить в алгоритме факт одинаковых дат в таблице, но потом еще проще сделал. И так, будем использовать зависимость пустого токена и нулевого статуса.
Для себя я решил, что буду всегда залогинивать пользователя с токеном, чтобы пользователь как можно дольше находился в аккаунте, если он заходит через социальную сеть или залогинивается в первый раз.
В методе регистрации нового пользователя я создаю новую запись в базе и передаю ее в новый объект, который я создаю для отправки сообщения:
// Send user message for activation account.
Mail::to($newUser)->send(new ActivateAccount($newUser));
В почтовом классе я формирую ссылку для активации аккаунта:
// Create activation link.
$activationLink = route('activation', ['id' => $this->user->id, 'token' => md5($this->user->email)]);
Что я делаю и зачем. А логика действий такова. Я предполагаю, что если статус нулевой и токен также нулевой, то это новый пользователь, который еще не подтверждал свой email. А если токен не нулевой, а статус нулевой, то это означает, что адрес был ранее подтвержден, но пользователь в последствии был забаннен. А если и статус равен единице и есть токен, то пользователь уже успешно активирован ранее.
Полностью класс для отправки email выглядит так:
<?php
namespace App\Mail;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class ActivateAccount extends Mailable
{
use Queueable, SerializesModels;
// User data.
protected $user;
/**
* Create a new message instance.
*
* @param \App\User $user
*/
public function __construct(User $user)
{
$this->user = $user;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
// Create activation link.
$activationLink = route('activation', [
'id' => $this->user->id,
'token' => md5($this->user->email)
]);
return $this->subject(trans('interface.ActivationAccount'))
->view('emails.activate')->with([
'link' => $activationLink
]);
}
}
Как видите, ничего сложного. Передаем линк в шаблон и формируем текст для пользователя.
Итак, что значит этот линк. Все просто, мы указываем хост для ссылки, потом указываем действие для роута:
// Activation user.
Route::get('activate/{id}/{token}', 'RegistrationController@activation')->name('activation');
Ид пользователя и токен - это переменные. Я специально передаю еще и идентификатор пользователя, чтобы не прогонять всю базу в поисках совпадения email адреса. Адрес пользователя я хэширую через функцию md5.
Дальше, когда пользователь кликает на ссылку, алгоритм действий простой. Пробуем получить пользователя из базы. Если его нет, то ошибка, если есть, то проверяем пустой ли токен для запоминания пользователя. Если пустой, то хэшируем пользовательский email и сравниваем с токеном из адресной строки. Если они совпадают, то активируем пользователя и тут же залогиниваем используя токен запоминания пользователя. Вот сам метод активации:
/**
* Make user activation.
*/
public function activation($userId, $token)
{
$user = User::findOrFail($userId);
// Check token in user DB. if null then check data (user make first activation).
if (is_null($user->remember_token)) {
// Check token from url.
if (md5($user->email) == $token) {
// Change status and login user.
$user->status = 1;
$user->save();
\Session::flash('flash_message', trans('interface.ActivatedSuccess'));
// Make login user.
Auth::login($user, true);
} else {
// Wrong token.
\Session::flash('flash_message_error', trans('interface.ActivatedWrong'));
}
} else {
// User was activated early.
\Session::flash('flash_message_error', trans('interface.ActivatedAlready'));
}
return redirect('/');
}
Это все! И не нужно никаких дополнительных таблиц, полей, методов, моделей и связей! Всем желаю удачи, придумывайте свои простые и эффективные способы по построению функционала. До скорой встречи.
П.С. Спасибо за дельные комментарии, которые помогли сделать код качественнее.
Комментарии (33):
Хотя, в принципе можно сделать проще:
в LoginController переопределяем функции sendFailedLoginResponse и credentials, и по сути можно и дальше использовать коробочное решение.
Mail::to($newUser)->send(new ActivateAccount($newUser));
# Production Mail environment
MAIL_DRIVER=mail
{
$message->to($data['email'])->subject('Подтверждение email');
});
У меня роуты именуются, например, "user::info", "admin::dashboard" и так далее.
$activationLink = 'https://' . $_SERVER['HTTP_HOST'] . '/activate/' . $this->user->id . '/' . md5($this->user->email);
выглядит оооочень страшно.
Route::get('activate/{user_id}/{md5}', 'EmailController@confirm')->name('email:confirm');
$activationLink = route('email:confirm', ['user_id' => $this->user->id, 'md5' => md5($this->user->email)]);