Sunday, February 10, 2019

Laravel How to create a basic user creation page

Table of contents
1. how to start
2. create a basic user creation page
3. create unit tests for Laravel 5.7

Create tables

At first, we will create these tables:
  • user table
  • password reset table
Actually these tables are prepared by default in /vagrant/laravel/database/migrations folder.
Open the "2014_10_12_000000_create_users_table.php". You can see the code inside:
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}
The function up() is executed to apply changes to the database when this file is migrated by the "migration" command of artisan. The function down() is executed when you revert the migration. Let's migrate the migration files. But before migrating, as of Laravel Framework 5.7.25, according to this stackoverflow question, we need to modify and add some code to a file.
Open /vagrant/laravel/app/Providers/AppServiceProvider.php. Change it as follows:
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema; //Here. Import Schema!!!!!

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        Schema::defaultStringLength(191); //Increase the StringLength!!!!
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}
Now you can migrate the migration files:
$ cd /vagrant/laravel
$ php artisan migrate
Then the tables are created in the database:

Create a new migration file and a table

To create new migration file, just run this command:
$ php artisan make:migration {table name}
To create a table named "articles", run this command:
$ php artisan make:migration articles
Then the migration file for articles table will be added:
2019_02_10_131250_articles.php is the migration file for the articles table. Open the migration file for articles and change it as follows:
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class Articles extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
            Schema::create('articles', function (Blueprint $table) {
                    $table->increments('id');
                    $table->integer('user_id')->unsigned(); 
                    $table->string('name');
                    $table->string('detail');
                    $table->rememberToken();
                    $table->timestamps();
                    //foreign key
                    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
                    $table->index('user_id');
            });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('articles', function($table) {
            $table->dropForeign('articles_user_id_foreign');
            $table->dropIndex('articles_user_id_index');
            $table->dropColumn('user_id');
        });
        Schema::dropIfExists('articles');
    }
}
Now migrate it:
$ php artisan migrate
Then the articles table is added in the database.

Create models and controllers

We will make corresponding models:
$ php artisan make:model User
$ php artisan make:model Article
And controllers:
$ php artisan make:controller UserController
$ php artisan make:controller ArticleController
For your information, to make models and controllers at the same time:
$ php artisan make:model User -c
$ php artisan make:model Article -c
Create factories for testing:
$ php artisan make:factory UserFactory
$ php artisan make:factory ArticleFactory
Now seeders:
$ php artisan make:seeder UsersTableSeeder
$ php artisan make:seeder ArticlesTableSeeder
You can see the
models in /vagrant/laravel/app/
controllers in /vagrant/laravel/app/Http/Controller/
Factories in /vagrant/laravel/database/factories/
Seeders in /vagrant/laravel/database/seeds/.

Create 10 users for testing

Let's make 10 users for testing. Open the UsersTableSeeder and change the run() function as follows:
public function run()
{
    factory(App\User::class, 10)->create();
}
Open the DatabaseSeeder.php and change the run function as follows:
public function run()
{
    $this->call(UsersTableSeeder::class);
}
Then migrate it with seed option:
$ php artisan migrate --seed
10 users are created in the database:
You can initialize all the tables and insert the 10 users at the same time with migrate:fresh:
$ php artisan migrate:fresh --seed

Access DB and pass the data to View in Web.php

Open /vagrant/laravel/routes/web.php. If you followed instructions written here, the code inside should be like this:
<?php
Route::get('/', function () {
    return view('welcome');
});

Route::get('/test', function () {
    return view('test');
});
Change it as follows:
<?php
Route::get('/', function () {
    $users = \App\User::all();
    return view('welcome', ['users' => $users]);
});

Route::get('/test', function () {
    return view('test');
});
Now you can use the $users variable in the welcome page's view. Add the following somewhere in the body of /vagrant/laravel/resources/views/welcome.blade.php.
<ul>
    @foreach ($users as $user)
      <li>{{ $user->name }}</li>
    @endforeach
<ul>
Like this:
If you open the welcome page http://192.168.33.10, you can see all of the 10 users are iterated:

Page to create users

At first, we will create a view for the page. Save the following as layout.blade.phpin /vagrant/laravel/resources/views/.
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>@yield('title')</title>
</head>
<body>
    <div class="container">
        @yield('content')
    </div>
</body>
Then save the following as submit.blade.php in /vagrant/laravel/resources/views/.
@extends('layout')

@section('title', 'submit')

@section('content')
    <div class="container">
        <div class="row">
            <h1>Submit a user</h1>
            <form action="/submit" method="post">
                {!! csrf_field() !!}

                <div class="form-group">
                    <label for="name">Name</label>
                    <input type="text" class="form-control" id="name" name="name" placeholder="name">
                </div>

  <div class="form-group">
                    <label for="email">Email</label>
                    <input type="text" class="form-control" id="email" name="email" placeholder="email">
                </div>

                <div class="form-group">
                    <label for="password">Password</label>
                    <input type="text" class="form-control" id="password" name="password" placeholder="password">
                </div>

                <button id="submitBtn" type="submit" class="btn btn-default">Submit</button>
            </form>
        </div>
    </div>
@endsection
We will add the following in /vagrant/laravel/routes/web.php.
use Illuminate\Http\Request;

Route::get('/submit', function () {
    return view('submit');
});

Route::post('/submit', function (Request $request) {
    $data = $request->validate([
        'name' => 'required | max:255',
        'email'  => 'required | max:255',
        'password' => 'required | max:255',
    ]);

    $user = new App\User($data);
    $user->save();

    return redirect('/');
});
Like this:
<?php
use Illuminate\Http\Request;

Route::get('/', function () {
    $users = \App\User::all();
    return view('welcome', ['users' => $users]);
});

Route::get('/submit', function () {
    return view('submit');
});

Route::post('/submit', function (Request $request) {
    $data = $request->validate([
        'name' => 'required | max:255',
        'email'  => 'required | max:255',
        'password' => 'required | max:255',
    ]);

    $user = new App\User($data);
    $user->save();

    return redirect('/');
});

Route::get('/test', function () {
    return view('test');
});

Route:get is the function to be executed when user access the url with the GET method. Route:get is the function to be executed when user access the url with POST method.
See http://192.168.33.10/submit from your browser and you can see users can be now added.

Refactoring the code

We have a working code now, but all of the process is written in the web.php, which is not the correct way to implement. To write clean code, we must separate operation in Model, Controller (and in Request).

Request

At first, we will move the validation process to Request. To make Request for the users table, run the following command:
$ php artisan make:request UserRequest
You can find the UserRequest in /vagrant/laravel/app/Http/Requests/. Open and change it as follows:
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        // The validation here
        return [
            'name' => 'required | max:255',
            'email'  => 'required | max:255',
            'password' => 'required | max:255',
        ];
    }
}

You can see that $validator->fails() is not implemented in the file because Laravel automatically prevents users to go to the page where users are supposed to go only if the validation is success. Also you can use $errors variable in the view without defining it here.

Controller

Open /vagrant/laravel/app/Http/Controller/UserController.php and change it as follows:
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\User;
use App\Http\Requests\UserRequest;

class UserController extends Controller
{
    public function submitGet(Request $request){
        return view('/submit');
    }

    public function submitPost(UserRequest $request){
        $user = new User();
        $user->name = $request->name;
        $user->email = $request->email;
        $user->password = $request->password;
        $user->save();
        return redirect('/');
    }
}
By using UserRequest for the type hinting of submitPost() function, only if the validation process has no error, the process inside the function is executed.

Routing

Now open the /vagrant/laravel/routes/web.php and change it as follows:
<?php
use Illuminate\Http\Request;

Route::get('/', function () {
    $users = \App\User::all();
    return view('welcome', ['users' => $users]);
});

Route::get('/submit','UserController@submitGet');
Route::post('/submit','UserController@submitPost');

Route::get('/test', function () {
    return view('test');
});

Check if these work

Go to http://192.168.33.10/submit and create a user:
You can see the created user is added in the database: