DARK MODE 

Posted on Wednesday, December 16, 2020 by

Create Instagram Clone with Laravel (Part 5): Adding Username to the Registration Flow

In this post, I covered from minute 42:00 until 58:35 of this video.


Before we start, let's take a deep, deep breath... because in this part, we're about to explore many files that maybe could make you feel overwhelm. 

Add a new field in the form
As you know, the ui:auth command doesn't create a username field for us in the registration form. But Instagram uses username, right? So to add a username field or custom field, let's begin by exploring the file that has the registration form, and that is inside resources > views > auth > register.blade.php

If you take a look at the code in the file, every field begins with <div class="form-group row">. To add a new field, just duplicate the code for any field. In my case, I duplicate the Email. After that, change every email occurences in the new code to username. Save and see the changes.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@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') }}</div>

                <div class="card-body">
                    <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 @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>

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

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

                            <div class="col-md-6">
                                <input id="username" type="username" class="form-control @error('username') is-invalid @enderror" name="username" value="{{ old('username') }}" required autocomplete="username">

                                @error('username')
                                <span class="invalid-feedback" role="alert">
                                    <strong>{{ $message }}</strong>
                                </span>
                                @enderror
                            </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 @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">

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

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

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">

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

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

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
                            </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>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection
resources > views > auth > register.blade.php

In the code, there's some lines of code that starts with @error. What this code do is it will validate the fields and display error if any of it doesn't satisfied the requirement (such as bad format email address, password too short, confirmation password doesn't match). To see it more clearly, try to remove every required occurences. Save and click Register while leaving everything empty.
Left is when there's required in the field. Right is when required is removed and the @error code do its job. Without required, the form actually got send and processed, and the back-end said "nope, this data is wrong" and return an error. Let's keep both required and @error. Using required can reduces the usage of our site resources while @error will handle everything that required can't. We need both of it. But right now, we have bigger problem to solve. As you can see, Laravel doesn't return any error when the username field was left empty. This is because we only just put a markup for it, we haven't done anything to register this username as a data yet.

Registering username as a data
In part 2, I've wrote that views should never hold the actual PHP logic. That would be the job for a controller. But we haven't talked about controller. Controllers are basically where all of the logics is stored. If you need to fetch data, manipulate data, or whatever you want to do with data, this is the job for controller. It is not job for a view. View is to simply put things in the right location, shouldn't do any calculations or queries. 

Controller can be found in app > http > Controllers. The one that we're going to explore first is in auth > RegisterController.php. auth is another directory that were created when we ran the ui:auth command. Now in RegisterController.php, there's a bunch of code. Let's focus on these code in particular:
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return \Illuminate\Contracts\Validation\Validator
     */
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\Models\User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }
app > http > Controllers > auth > RegisterController.php

In the Validator function which is on line 50 to 57, each of the data we entered in the form must pass some sort of validation. On line 54 for example, an email is required, meaning that user musn't leave it empty, string that means it's a type of string, email that means it must be in an email format, max:255 that means the maximum string length is 255, and unique:users than means you cannot re-registered a user with the same email. The data must pass all of these validations in order for the server to create a user. Because we haven't written one for username yet, the server can't validate the data hence, why we don't receive any error message when we left the username field in registration form empty before. So, add a new line of code. Like an email, username also needs to be unique.
50
51
52
53
54
55
56
57
58
    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => ['required', 'string', 'max:255'],
            'username' => ['required', 'string', 'max:255', 'unique:users'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
    }
app > http > Controllers > auth > RegisterController.php

Now, Laravel should be able to display a proper error if you left the username field empty.

When all of these data pass all the validations, when all is good to go, it will then go to create the user in the create function, no more checking. Now, we can add a line of code to register username.
66
67
68
69
70
71
72
73
74
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'username' => $data['username'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }
app > http > Controllers > auth > RegisterController.php

We're not quite done just yet. Remember about migrations that is supposed to describe our database? So if we're adding username to our Users table, we need to make a modification to our migration because our migration needs to know that we're now going to 'know' a username for each of our users. So let's go and take a look at our migrations in database > migrations. Right now, we're only interested with Users table so go ahead and open 2014_10_12_000000_create_users_table.php

In the file, there's Schema that describes the structure of our database. On line 16 below, we're telling Schema, to create a table named users, and then there's the blueprint for it. The blueprint is gonna be big increment for id that's just an auto incremental id, a string for name, a string for email that needs to be unique (2 users can't have the same email), timestamp for email verified at that is nullable (can be empty, not required), string for password, remember token, and timestamps. A lot of stuff here you don't need to understand right away. But we do know this. We need to add a new username that is unique. Now, the unique here is different from RegisterController.php Validator because in controller, it's just checking at the PHP level. But if for some odd reason, someone was to post directly into our server (basically tried to use the backdoor), our database are going to protect this and make sure that no one is going to create a user with a username that's already exist.
14
15
16
17
18
19
20
21
22
23
24
25
26
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('username')->unique();
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }
database > migrations > 2014_10_12_000000_create_users_table.php

Remake the database
After making all of the modifications above, you should be able to register a user with username, right? Not really, actually but let's just try create a user and see what happens. I registered a new user with following info:

You'll be redirected to homepage like there's no error. However, if we look at our database, this new username isn't actually got registered. To view your database or interact with your database, you can do it with Tinker by running the command php artisan tinker. After running the command, you're now in Tinker Shell. Let's write User::all(); and hit enter.

As you can see, username isn't registered to the database. This is because whenever we make a new migration or make any changes to our database, we need to remake the database. To do this, first, exit from the Tinker Shell by writing exit and hit enter. Then run php artisan migrate:fresh. This command will drop all tables/erase the entire database. There's one more step that we need to do. 

Let's go and open app > Models > User.php. This file represents just one row inside our database. As an extra layer of protection from Laravel so that malicious user doesn't add anything to our database that's not supposed to be there, we have this protected $fillable code. So of course, we need to add username to it.
19
20
21
22
23
24
    protected $fillable = [
        'name',
        'username',
        'email',
        'password',
    ];
app > Models > User.php

Now, everything's ready and we are good to go and create a user! If you receive an error SQLSTATE[HY000]: General error: 1 table users has no column named username, migrate your database once again. To see the proof that the username is successfully registered to database, of course run the Tinker.

Displaying username instead of name in header
After you're able to login or register, at the top right side of header, it display your name instead of username. To change this, replace {{ Auth::user()->name }} in app.blade.php to {{ Auth::user()->username }}. As simple as that!
61
62
63
                            <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                {{ Auth::user()->username }}
                            </a>
resources > views > layouts > app.blade.php

That is all for part 5! Right now, we know for a fact that the text beside our profile photo is just hardcoded. It isn't something from the database. On the next part, we're going to create controller for our profile to fetch some data from the database.

Highlights ⚡

  • To add a new data for registration, modify register.blade.php, RegisterController.php, 2014_10_12_000000_create_users_table.php and User.php
  • To fetch data, manipulate data, or whatever you want to do with data, use a controller
  • Interact with your database with Tinker, php artisan tinker
  • To display all users in table using Tinker, write User::all();
  • Exit Tinker Shell with exit
  • Always remake your database after making modifications with migrate
  • Drop all tables in database with php artisan migrate:fresh

No comments:

Post a Comment