Laravel Two-Step Registration: Optional Fields for Country and Bio

Nowadays, we have a lot of Laravel tutorial about some syntax or package, but I think there’s not enough written with real-life mini-projects, close to real demands of the clients. So will try to write more of these, and this is one of them: imagine a project where you need to have two-step registration process, with some optional fields in the second step. Let’s build it in Laravel.

By default, Laravel registration form has four fields:

Let’s say we have a task to add two more fields: country (dropdown) and Biography (textarea). After successful default registration, user would be redirected to that second step with those two fields, and ability to fill them in, or skip that second step.

Here’s our plan of actions:

  1. Add new fields to User model and migration;
  2. For “country” field we would create a seed for all world’s countries;
  3. Create a GET URL /register-step2 and route/controller/view with form for those two new fields;
  4. Update that form values and redirect to /home;
  5. Add a link to Skip the second step;
  6. Finally, tie it all together to redirect successful registration to that /register-step2.

In reality, I will merge it all into four steps, let’s go.


Step 1. New fields: migrations, seeds and model

We need to add two new fields to the database users table: country_id and biography. But before that, we need to create a new table countries to have a foreign key to.

So we launch:

php artisan make:migration create_countries_table

And for this we have a “quick hack” – there’s a seeder inside of our QuickAdminPanel generator, that will give us this:

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();
        });
    }
}

Also, we have the seed generated with all world’s 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',
            ],

            // ... Other countries

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

And we add this seeder file into main database/seeds/DatabaseSeeder.php:

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

Now we can create a foreign key in users table:

php artisan make:migration add_fields_to_users_table

And here’s the migration code:

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();
    });
}

Finally, we can launch this magic command on our (still empty) database:

php artisan migrate --seed

Registration Step 2: Route/Controller/View

So, we’re building this page now:

Let’s start with routes/web.php:

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

Now, let’s the create the Controller we want, it will be in 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'));
    }
}

As you can see, we add middleware auth inside of Controller’s constructor, so only authenticated users will be able to access that step 2 – immediately after registration.

Also, if you remember, one of the fields will be Countries list, so we need to pass it from Controller.

Now, let’s create a Blade file – for this, we will just copy-paste register.blade.php and change the input fields. Here’s the result 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

As you can see, we’re adding “Skip for now” link to /home route. Also, we’re referencing the POST action to the route name register.step2 that doesn’t exist yet. This is our next step.


Step 3. Update the Fields

This is pretty simple, we just add a new method to our new Controller, and point to it in Routes. Remember, in the Blade file above, we already referenced it:

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

So, we need to add a new line in routes/web.php:

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

Our postForm() method will be as simple as that:

use Illuminate\Http\Request;

class RegisterStep2Controller extends Controller
{

    // ... other methods

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

}

To make this work, we also need to make those two new fields fillable, in app/User.php – just add them into already existing array:

class User extends Authenticatable
{
    use Notifiable;

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

And, after we login/register, go to /register-step2 URL, fill in the form – we get success in the database:


Step 4. Redirect Registration to Step 2

Final step is probably the most simple one. By default, successful Laravel registration redirects user to /home URL, it is set in app/Http/Controllers/Auth/RegisterController.php:

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

    // ...

}

So, all we need to do is change the value to this:

protected $redirectTo = '/register-step2';

And, that’s it, our tutorial is done!

Here’s a link to Github repository for full project:
https://github.com/LaravelDaily/Laravel-Registration-Step2

Like our articles?
Check out our Laravel online courses!

1 COMMENT

  1. Just a detail/question:

    Is not better to add the auth middleware to the route instead in the constructor in the controller?
    If the first step is unsuccessful the user won’t go that far.

LEAVE A REPLY

Please enter your comment!
Please enter your name here