Реєстрація в 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, тільки в разі, якщо користувач протягом доби змінить пароль. Якщо ж це не відбудеться - просто видаляти запис. Зрозуміло, крім пароля в лист також треба буде додати інформацію про те, що пароль буде дійсний протягом доби. Можна також в форму реєстрації додати капчу. Загалом, що стосується доопрацювання, все залежить від потреб - комусь потрібно одне, а кому-то інше.