Двухэтапная регистрация Laravel: Дополнительные поля для страны и биографии

Двухэтапная регистрация Laravel: Дополнительные поля для страны и биографии

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

По умолчанию форма регистрации Laravel имеет четыре поля:

Двухэтапная регистрация Laravel: Дополнительные поля для страны и биографии

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

Вот наш план действий:

  1. Добавить новые поля в модель User и миграцию;
  2. Для поля «страна» создать сидер (seeder — наполнитель БД начальными данными) для всех стран мира;
  3. Создать GET URL /register-step2 и route/controller/view с формой для этих двух новых полей;
  4. Обновить данные формы и редиректнуть на /home;
  5. Добавить ссылку для возможности пропустить второй шаг;
  6. Наконец, связать всё это вместе, чтобы перенаправить успешную регистрацию на этот /register-step2.

На самом деле, я объединю всё это в четыре этапа.

Шаг 1. Новые поля: миграции, сиды и модель

Нам нужно добавить два новых поля в таблицу user: country_id и biography. Но перед этим нам нужно создать новую таблицу countries, чтобы иметь возможность использовать внешний ключ.

Итак, запускаем:

php artisan make:migration create_countries_table
php artisan make:migration create_countries_table
php artisan make:migration create_countries_table

И для этого у нас есть «быстрый хак» — внутри генератора QuickAdminPanel есть сидер, который выдаст нам следующее:

class CreateCountriesTable extends Migration
{
public function up()
{
Schema::create('countries', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('short_code');
$table->timestamps();
$table->softDeletes();
});
}
}
class CreateCountriesTable extends Migration { public function up() { Schema::create('countries', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('short_code'); $table->timestamps(); $table->softDeletes(); }); } }
class CreateCountriesTable extends Migration
{
    public function up()
    {
        Schema::create('countries', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('short_code');
            $table->timestamps();
            $table->softDeletes();
        });
    }
}

Кроме того, у нас есть сгенерированный сид со всеми странами мира:

class CountriesTableSeeder extends Seeder
{
public function run()
{
$countries = [
[
'id' => 1,
'name' => 'Afghanistan',
'short_code' => 'af',
],
[
'id' => 2,
'name' => 'Albania',
'short_code' => 'al',
],
[
'id' => 3,
'name' => 'Algeria',
'short_code' => 'dz',
],
// ... Другие страны
[
'id' => 239,
'name' => 'Zambia',
'short_code' => 'zm',
],
[
'id' => 240,
'name' => 'Zimbabwe',
'short_code' => 'zw',
],
];
Country::insert($countries);
}
}
class CountriesTableSeeder extends Seeder { public function run() { $countries = [ [ 'id' => 1, 'name' => 'Afghanistan', 'short_code' => 'af', ], [ 'id' => 2, 'name' => 'Albania', 'short_code' => 'al', ], [ 'id' => 3, 'name' => 'Algeria', 'short_code' => 'dz', ], // ... Другие страны [ 'id' => 239, 'name' => 'Zambia', 'short_code' => 'zm', ], [ 'id' => 240, 'name' => 'Zimbabwe', 'short_code' => 'zw', ], ]; Country::insert($countries); } }
class CountriesTableSeeder extends Seeder
{
    public function run()
    {
        $countries = [
            [
                'id'         => 1,
                'name'       => 'Afghanistan',
                'short_code' => 'af',
            ],
            [
                'id'         => 2,
                'name'       => 'Albania',
                'short_code' => 'al',
            ],
            [
                'id'         => 3,
                'name'       => 'Algeria',
                'short_code' => 'dz',
            ],

            // ... Другие страны

            [
                'id'         => 239,
                'name'       => 'Zambia',
                'short_code' => 'zm',
            ],
            [
                'id'         => 240,
                'name'       => 'Zimbabwe',
                'short_code' => 'zw',
            ],
        ];
        Country::insert($countries);
    }
}

Добавляем этот сидер в основной database/seeds/DatabaseSeeder.php:

class DatabaseSeeder extends Seeder
{
public function run()
{
$this->call(CountriesTableSeeder::class);
}
}
class DatabaseSeeder extends Seeder { public function run() { $this->call(CountriesTableSeeder::class); } }
class DatabaseSeeder extends Seeder
{
    public function run()
    {
        $this->call(CountriesTableSeeder::class);
    }
}

Теперь мы можем создать внешний ключ в таблице пользователей:

php artisan make:migration add_fields_to_users_table
php artisan make:migration add_fields_to_users_table
php artisan make:migration add_fields_to_users_table

Вот и код миграции:

public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->unsignedInteger('country_id')->nullable();
$table->foreign('country_id')->references('id')->on('countries');
$table->text('biography')->nullable();
});
}
public function up() { Schema::table('users', function (Blueprint $table) { $table->unsignedInteger('country_id')->nullable(); $table->foreign('country_id')->references('id')->on('countries'); $table->text('biography')->nullable(); }); }
public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->unsignedInteger('country_id')->nullable();
        $table->foreign('country_id')->references('id')->on('countries');
        $table->text('biography')->nullable();
    });
}

Наконец, мы можем запустить эту волшебную команду на нашей (все еще пустой) базе данных:

php artisan migrate --seed
php artisan migrate --seed
php artisan migrate --seed

Двухэтапная регистрация Laravel: Дополнительные поля для страны и биографии

Шаг регистрации 2: Route/Controller/View

Итак, делаем эту страницу:

Двухэтапная регистрация Laravel: Дополнительные поля для страны и биографии

Начнем с routes/web.php:

Route::get('register-step2', 'Auth\RegisterStep2Controller@showForm');
Route::get('register-step2', 'Auth\RegisterStep2Controller@showForm');
Route::get('register-step2', 'Auth\RegisterStep2Controller@showForm');

Теперь давайте создадим контроллер, он будет в app/Http/Controllers/Auth/RegisterStep2Controller.php:

namespace App\Http\Controllers\Auth;
use App\Country;
use App\Http\Controllers\Controller;
class RegisterStep2Controller extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function showForm()
{
$countries = Country::all();
return view('auth.register_step2', compact('countries'));
}
}
namespace App\Http\Controllers\Auth; use App\Country; use App\Http\Controllers\Controller; class RegisterStep2Controller extends Controller { public function __construct() { $this->middleware('auth'); } public function showForm() { $countries = Country::all(); return view('auth.register_step2', compact('countries')); } }
namespace App\Http\Controllers\Auth;

use App\Country;
use App\Http\Controllers\Controller;

class RegisterStep2Controller extends Controller
{
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function showForm()
    {
        $countries = Country::all();
        return view('auth.register_step2', compact('countries'));
    }
}

Как вы видите, мы добавили мидлвар auth внутрь конструктора контроллера, поэтому только аутентифицированные пользователи смогут получить доступ к шагу 2 — сразу после регистрации.

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

Теперь давайте создадим Blade-файл — для этого мы просто скопипастим register.blade.php и изменим поля ввода. Вот результат resources/views/auth/register_step2.blade.php:

@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Register Step 2 (optional)') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('register.step2') }}">
@csrf
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Country') }}</label>
<div class="col-md-6">
<select name="country_id" class="form-control @error('country_id') is-invalid @enderror">
<option value="">-- {{ __('choose your country') }} --</option>
@foreach ($countries as $country)
<option value="{{ $country->id }}">{{ $country->name }}</option>
@endforeach
</select>
@error('country_id')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Biography') }}</label>
<div class="col-md-6">
<textarea class="form-control @error('biography') is-invalid @enderror" name="biography">{{ old('biography') }}</textarea>
@error('biography')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Finish Registration') }}
</button>
<br /><br />
<a href="{{ route('home') }}">Skip for now</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
@extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Register Step 2 (optional)') }}</div> <div class="card-body"> <form method="POST" action="{{ route('register.step2') }}"> @csrf <div class="form-group row"> <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Country') }}</label> <div class="col-md-6"> <select name="country_id" class="form-control @error('country_id') is-invalid @enderror"> <option value="">-- {{ __('choose your country') }} --</option> @foreach ($countries as $country) <option value="{{ $country->id }}">{{ $country->name }}</option> @endforeach </select> @error('country_id') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row"> <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Biography') }}</label> <div class="col-md-6"> <textarea class="form-control @error('biography') is-invalid @enderror" name="biography">{{ old('biography') }}</textarea> @error('biography') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> <div class="form-group row mb-0"> <div class="col-md-6 offset-md-4"> <button type="submit" class="btn btn-primary"> {{ __('Finish Registration') }} </button> <br /><br /> <a href="{{ route('home') }}">Skip for now</a> </div> </div> </form> </div> </div> </div> </div> </div> @endsection
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Register Step 2 (optional)') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('register.step2') }}">
                        @csrf

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

                            <div class="col-md-6">
                                <select name="country_id" class="form-control @error('country_id') is-invalid @enderror">
                                    <option value="">-- {{ __('choose your country') }} --</option>
                                    @foreach ($countries as $country)
                                        <option value="{{ $country->id }}">{{ $country->name }}</option>
                                    @endforeach
                                </select>

                                @error('country_id')
                                <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

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

                            <div class="col-md-6">
                                <textarea class="form-control @error('biography') is-invalid @enderror" name="biography">{{ old('biography') }}</textarea>

                                @error('biography')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Finish Registration') }}
                                </button>
                                <br /><br />
                                <a href="{{ route('home') }}">Skip for now</a>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Как видите, мы добавили ссылку «Skip for now» (Пропустить сейчас) ведущую на /home. И приготовили post-экшн на адрес register.step2, которого пока не существует. Сделаем это в следующем шаге.

Шаг 3. Обновление полей

Всё довольно просто, добавляем новый метод в наш контроллер и указываем на него в маршрутах. Помните, выше, в файле Blade мы уже ссылались на него:

<form method="POST" action="{{ route('register.step2') }}">
<form method="POST" action="{{ route('register.step2') }}">
<form method="POST" action="{{ route('register.step2') }}">

Итак, нам нужно добавить новую строку в routes/web.php

Route::post('register-step2', 'Auth\RegisterStep2Controller@postForm')
->name('register.step2');
Route::post('register-step2', 'Auth\RegisterStep2Controller@postForm') ->name('register.step2');
Route::post('register-step2', 'Auth\RegisterStep2Controller@postForm')
->name('register.step2');

Наш метод postForm() будет простым:

use Illuminate\Http\Request;
class RegisterStep2Controller extends Controller
{
// ... другие методы
public function postForm(Request $request)
{
auth()->user()->update($request->only(['biography', 'country_id']));
return redirect()->route('home');
}
}
use Illuminate\Http\Request; class RegisterStep2Controller extends Controller { // ... другие методы public function postForm(Request $request) { auth()->user()->update($request->only(['biography', 'country_id'])); return redirect()->route('home'); } }
use Illuminate\Http\Request;

class RegisterStep2Controller extends Controller
{

    // ... другие методы

    public function postForm(Request $request)
    {
        auth()->user()->update($request->only(['biography', 'country_id']));
        return redirect()->route('home');
    }

}

Чтобы это заработало, нам нужно сделать два этих новых поля — fillable (заполняемыми) в app/User.php. Просто добавьте их в уже существующий массив:

class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password', 'country_id', 'biography'
];
class User extends Authenticatable { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', 'country_id', 'biography' ];
class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password', 'country_id', 'biography'
    ];

И, после того как мы залогинимся/зарегистрируемся, перейдем по адресу /register-step2, заполним форму, то получим в базе данных:

Двухэтапная регистрация Laravel: Дополнительные поля для страны и биографии

Шаг 4. Редирект регистрации на Шаг 2

Последний шаг, пожалуй, самый простой. По умолчанию, успешная регистрация в Laravel перенаправляет пользователя по адрему /home, это настраивается в app/Http/Controllers/Auth/RegisterController.php:

class RegisterController extends Controller
{
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = '/home';
// ...
}
class RegisterController extends Controller { /** * Where to redirect users after registration. * * @var string */ protected $redirectTo = '/home'; // ... }
class RegisterController extends Controller
{
    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    // ...

}

И всё, что нам нужно сделать, это изменить значение:

protected $redirectTo = '/register-step2';
protected $redirectTo = '/register-step2';
protected $redirectTo = '/register-step2';

Всё, наш урок готов!

Полный проект урока лежит на Github: https://github.com/LaravelDaily/Laravel-Registration-Step2

 

Автор: Povilas Korop
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.