Регистрация в Laravel с проверкой email. События и слушатели

Регистрация в Laravel с проверкой email. События и слушатели

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

Полагаю, наиболее распространённым вариантом верификации email-а является отправка ссылки активации аккаунта на указанный при регистрации почтовый ящик. Но можно ведь поступить и проще – вместо того, чтобы отправлять ссылку активации, отправить пользователю сгенерированный временный пароль. Естественно, при этом не надо требовать ввод пароля при регистрации, но следует предоставить пользователю возможность его изменения в личном кабинете.

Приступим к реализации этой идеи в Laravel 5.6. Прежде всего, создадим контроллеры и представления аутентификации, выполнив в консоли команду:

php artisan make:auth

 

Поля ввода и подтверждения пароля в форме регистрации нам не понадобятся, поэтому идём в файл resources/views/auth/register.blade.php и удаляем их, после чего форма будет выглядеть так:

<form method="POST" action="{{ route('register') }}">
    @csrf

    <div class="form-group row">
        <label for="name" class="col-md-4 col-form-label text-md-right">
            {{ __('Name') }}
        </label>

        <div class="col-md-6">
            <input 
                id="name" 
                type="text" 
                class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" 
                name="name" 
                value="{{ old('name') }}" 
                required 
                autofocus
            >
            @if ($errors->has('name'))
                <span class="invalid-feedback">
                    <strong>{{ $errors->first('name') }}</strong>
                </span>
            @endif
        </div>
    </div>

    <div class="form-group row">
        <label for="email" class="col-md-4 col-form-label text-md-right">
            {{ __('E-Mail Address') }}
        </label>

        <div class="col-md-6">
            <input 
                id="email" 
                type="email" 
                class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" 
                name="email" 
                value="{{ old('email') }}" 
                required
            >
            @if ($errors->has('email'))
                <span class="invalid-feedback">
                    <strong>{{ $errors->first('email') }}</strong>
                </span>
            @endif
        </div>
    </div>

    <div class="form-group row mb-0">
        <div class="col-md-6 offset-md-4">
            <button type="submit" class="btn btn-primary">
                {{ __('Register') }}
            </button>
        </div>
    </div>
</form>

 

Кроме этого, в контроллере регистрации – app/Http/Controllers/Auth/RegisterController.php - следует удалить из валидации поле пароля и вместо этого сгенерировать случайный пароль, который мы запишем в добавленное нами же свойство класса $generatedPassword. Пароль нужно «запомнить» для того, чтобы впоследствии отправить его пользователю. Ну а хэш пароля сохраним в базе данных:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

class RegisterController extends Controller
{
    use RegistersUsers;

    protected $generatedPassword;

    protected $redirectTo = '/login';

    public function __construct()
    {
        $this->middleware('guest');
    }

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users'
        ]);
    }

    protected function create(array $data)
    {
        $this->password = str_random(8);

        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($this->password),
        ]);
    }
}

 

Есть ещё один важный момент – в Laravel сразу после регистрации пользователь авторизован, что не согласуется с нашей идеей. Поэтому в том же RegisterController перегрузим метод registered() (этот метод определён в трейте RegistersUsers), в котором пропишем лог-аут и редирект на страницу входа с сообщением об отправке письма с паролем:

protected function registered(Request $request, $user)
{
    $this->guard()->logout();

    return redirect($this->redirectPath())
        ->withSuccess('Thanks for registration! The password has been sent to your email.');
}

 

Отправкой почты займёмся позже, а пока создадим событие регистрации, выполнив в консоли команду:

php artisan make:event Auth/UserRegistered

 

В результате будет создан файл app/Events/Auth/UserRegistered.php. Открываем его и редактируем. Код должен выглядеть следующим образом:

<?php

namespace App\Events\Auth;

use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use App\User;

class UserRegistered
{
    use Dispatchable, SerializesModels;

    public $user;

    public $password;

    public function __construct(User $user, $password)
    {
        $this->user = $user;
        $this->password = $password;
    }
}

 

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

Следующий шаг – создание слушателя. Сделаем это с помощью команды:

php artisan make:listener Auth/SendRegisterNotification --event=Auth/UserRegistered

 

Поскольку в слушателе нам понадобится класс для отправки почты, сразу же создадим и его. При этом сгенерируем markdown-шаблон для письма:

php artisan make:mail Auth/RegistrationEmail --markdown=emails.auth.registration

 

Открываем созданный на предыдущем шаге слушатель  и прописываем отправку почты:

<?php

namespace App\Listeners\Auth;

use App\Events\Auth\UserRegistered;
use App\Mail\Auth\RegistrationEmail;
use Mail;

class SendRegisterNotification
{
    public function handle(UserRegistered $event)
    {
        Mail::to($event->user->email)->send(new RegistrationEmail($event->user, $event->password));
    }
}

 

Теперь надо зарегистрировать слушатель события, и, как говорит документация, лучшим местом для этого является провайдер сервиса событий (app/Providers/EventServiceProvider.php):

<?php

namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        'App\Events\Auth\UserRegistered' => [
            'App\Listeners\Auth\SendRegisterNotification',
        ],
    ];

    public function boot()
    {
        parent::boot();
    }
}

 

Пришёл черёд поработать с почтой. Заходим в файл app/Mail/Auth/RegistrationEmail.php и вносим необходимые изменения. Если точнее – также передадим в конструктор модель пользователя и пароль и снова запишем их в публичные свойства для того, чтобы к ним можно было обращаться в шаблоне письма. Помимо этого, реализуем метод build(), в котором сконструируем письмо:

<?php

namespace App\Mail\Auth;

use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class RegistrationEmail extends Mailable
{
    use Queueable, SerializesModels;

    public $user;

    public $password;

    public function __construct(User $user, $password)
    {
        $this->user = $user;
        $this->password = $password;
    }

    public function build()
    {
        return $this->markdown('emails.auth.registration')
            ->subject(config('app.name') . ": Registration Notification")
            ->from(config('mail.from.address'));
    }
}

 

Что касается шаблона письма, как мы и просили, Laravel создал файл registration.blade.php в директории resources/emails/auth. Разумеется, в шаблоне можно разместить что угодно, я просто добавлю пару слов благодарности за регистрацию, укажу сгенерированный пароль и добавлю кнопку для входа на сайт:

@component('mail::message')
# Welcome!

Thanks for signing up. This is the password we generated for you: **{{ $password }}**

You can change it in your profile.

@component('mail::button', ['url' => route('login')])
Login
@endcomponent

Thanks,<br>
{{ config('app.name') }}
@endcomponent

 

И последний шаг – вернуться в контроллер регистрации и в методе registered() добавить событие. Теперь код данного метода будет выглядеть так:

protected function registered(Request $request, $user)
{
    event(new UserRegistered($user, $this->password));

    $this->guard()->logout();

    return redirect($this->redirectPath())
        ->withSuccess('Thanks for registration! The password has been sent to your email.');
}

 

Что ж, дело сделано. Теперь каждый раз при регистрации нового пользователя ему на почту будет отправляться письмо с паролем. И если кто-либо решит указать «левый» email, он попросту не сможет войти на сайт для каких либо действий. Мы же в свою очередь знаем, что почтовые ящики, скажем так, - активных пользователей - реальны.

Надо понимать, что представленный здесь код, можно доработать на своё усмотрение. Например, мы не хотим, чтобы в базе оставались данные о всех тех, кто указал несуществующий email. В этом случае в таблицу пользователей можно добавить поле activated, которое по умолчанию будет равно false и изменять его на true, только в случае, если пользователь в течение суток изменит пароль. Если же это не произойдёт - попросту удалять запись. Разумеется, помимо пароля в письмо также надо будет добавить информацию о том, что пароль будет действителен в течение суток. Можно также в форму регистрации добавить капчу. В общем, что касается доработки, всё зависит от потребностей – кому-то нужно одно, а кому-то другое.