Next.jsとLaravelを連携して管理者登録する方法

# サンプル
このような管理者画面で登録する際のコードを備忘録として残します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/01ee5508-480a-42e4-b04a-20128fb309ba.png)

# フロント側(Next.js)のコード

```frontend/app/adminInformation/adminRegistration/page.tsx
'use client'
import { useRouter } from "next/navigation"
import { useState } from "react"
import { useForm } from "react-hook-form"
import axios from "axios"

export default function RegisterationAdmin(){
    //Set default valu
    const defaultValues = {
        userName:'',
        emailAddress:'',
        password:'',
        phoneNumber:'',
        country:'',
        streetAddress:'',
        city:'',
        stateProvince:'',
        zip:''
    }

    //Initialise formdata
    const {register,handleSubmit,formState:{errors}} = useForm({defaultValues});

    //Set route
    const router = useRouter();

    //Click registeration button
    const clickRegistration = ()=>{
        router.push('');
    }

    const backToTopPage = ()=>{
        router.push('/');
    }

    const [userName,setUserName] = useState('');
    const [emailAddress,setEmailAddress] = useState('');
    const [phoneNumber,setPhoneNumber] = useState('');
    const [password,setPassword] = useState('');
    const [country,setCountry] = useState('');
    const [streetAddress,setStreetAddress] = useState('');
    const [city,setCity] = useState('');
    const [stateProvince,setStateProvince] = useState('');
    const [zipCode,setZipCode] = useState('');

    const onSubmit = async(data:any)=>{
        //e.preventDefault();
        const formData = new FormData();
        formData.append('userName',data.userName);
        formData.append('emailAddress',data.emailAddress);
        formData.append('phoneNumber',data.phoneNumber);
        formData.append('password',data.password);
        formData.append('country',data.country);
        formData.append('streetAddress',data.streetAddress);
        formData.append('city',data.city);
        formData.append('stateProvince',data.stateProvince);
        formData.append('zipCode',data.zip);

        formData.forEach((value, key) => {
            console.log(key, value);
        });

        try{
            const response = await axios.post('http://localhost:8000/api/adminRegistration',formData,{               
            });
            console.log(response.data); // レスポンスの確認
        }catch(error){
            alert('Error happend.');
        }

    }

    return (
        <div className="shadow-md flex items-center justify-center rounded-md border border-gray-700">
            <form onSubmit={handleSubmit(onSubmit)} className="w-xl shadow-md min-h-screen rounded-md border border-gray-700 px-10 py-10">
                <div>
                    <h2 className="text-base/7 text-gray-900">
                        Admin account profile
                    </h2>
                </div>
                <div className="w-full grid grid-cols-1 gap-x-6">
                    <label className="block text-sm/6 font-medium text-gray-900">UserName</label>
                    <input
                        type="text"
                        className="text-gray-700 border border-gray-300 rounded-md"
                        defaultValue={defaultValues.userName}
                        {...register('userName',{
                            required:'UserName must be input.',                           
                        })}
                        onChange={(e)=>setUserName(e.target.value)}
                    />
                    <div className="text-red-500">{errors.userName?.message}</div>
                </div>
                <div className="w-full grid grid-cols-1 gap-x-6">
                    <label className="block text-sm/6 font-medium text-gray-900">Email address</label>
                    <input
                        type="email"
                        className="text-gray-700 border border-gray-300 rounded-md"
                        defaultValue={defaultValues.emailAddress}
                        {...register('emailAddress',{
                            required:'EmailAddress must be input.',
                            pattern:{
                                value:/([a-z\d+\-.]@[a-z\d-]+(?:\.[a-z]+)*)/i,
                                message:'Your Email Format is invalid.'
                            }
                        })}
                        onChange={(e)=>setEmailAddress(e.target.value)}
                    />
                    <div className="text-red-500">{errors.emailAddress?.message}</div>
                </div>
                <div className="w-full grid grid-cols-1 gap-x-6">
                    <label className="block text-sm/6 font-medium text-gray-900">Phone number</label>
                    <input
                        type="text"
                        className="text-gray-700 border border-gray-300 rounded-md"
                        defaultValue={defaultValues.phoneNumber}
                        {...register('phoneNumber',{
                            required:'Phone number must be input.'
                        })}
                        onChange={(e)=>setPhoneNumber(e.target.value)}
                    />
                    <div className="text-red-500">{errors.phoneNumber?.message}</div>
                </div>
                <div className="w-full grid grid-cols-1 gap-x-6">
                    <label className="block text-sm/6 font-medium text-gray-900">Password</label>
                    <input
                        type="password"
                        className="text-gray-700 border border-gray-300 rounded-md"
                        {...register('password',{
                            required:'Password must be input.',
                        })}
                        onChange={(e)=>{setPassword(e.target.value)}}
                    />
                    <div className="text-red-500">{errors.password?.message}</div>
                </div>
                <div className="w-full grid grid-cols-1 gap-x-6">
                    <label className="block text-sm/6 font-medium text-gray-900">Country</label>
                    <select className="border border-gray-300 rounded-md" 
                        {...register('country',{required:'Country must be required.'})}
                        onChange={(e)=>{setCountry(e.target.value)}}
                        >
                        <option></option>
                        <option>United States</option>
                        <option>Canada</option>
                        <option>Mexico</option>
                        <option>Australia</option>
                        <option>England</option>
                    </select>
                    <div className="text-red-500">{errors.country?.message}</div>
                </div>
                <div className="w-full grid grid-cols-1 gap-x-6">
                    <label>Street address</label>
                    <input
                        type="text"
                        className="text-gray-700 border border-gray-300 rounded-md"
                        defaultValue={defaultValues.streetAddress}
                        {...register('streetAddress',{
                            required:'StreetAddress must be input.',
                        })}
                        onChange={(e)=>setStreetAddress(e.target.value)}
                    />
                    <div className="text-red-500">{errors.streetAddress?.message}</div>
                </div>
                <div className="w-full grid grid-cols-1 gap-x-6">
                    <label className="block text-sm/6 font-medium text-gray-900">City</label>
                    <input
                        type="text"
                        className="text-gray-700 border border-gray-300 rounded-md"
                        defaultValue={defaultValues.city}
                        {...register('city',{
                            required:'City must be input.',
                        })}
                        onChange={(e)=>setCity(e.target.value)}
                    />
                    <div className="text-red-500">{errors.city?.message}</div>
                </div>
                <div className="w-full grid grid-cols-1 gap-x-6">
                    <label className="block text-sm/6 font-medium text-gray-900">State / Province</label>
                    <input
                        type="text"
                        className="text-gray-700 border border-gray-300 rounded-md"
                        defaultValue={defaultValues.stateProvince}
                        {...register('stateProvince',{
                            required:'StateProvince must be input.'
                        })}
                        onChange={(e)=>setStateProvince(e.target.value)}
                    />
                    <div className="text-red-500">{errors.stateProvince?.message}</div>
                </div>
                <div className="w-full grid grid-cols-1 gap-x-6">
                    <label className="block text-sm/6 font-medium text-gray-900">ZIP / Postal code</label>
                    <input
                        type="text"
                        className="text-gray-700 border border-gray-300 rounded-md"
                        defaultValue={defaultValues.zip}
                        {...register('zip',{
                            required:'ZipCode must be required.'
                        })}
                        onChange={(e)=>setZipCode(e.target.value)}
                    />
                    <div className="text-red-500">{errors.zip?.message}</div>
                </div>
                <div className="flex items-center">
                    <button 
                        type="submit" 
                        className="bg bg-blue-500 text-white px-4 py-4 rounded-md cursor:text-pointer hover:bg-blue-700"
                        onClick={clickRegistration}
                        >
                        Register
                    </button>
                    <button
                        type="button"
                        className="bg bg-rose-500 text-white px-4 py-4 rounded-md cursor:text-pointer hover:bg-rose-700"
                        onClick={backToTopPage}
                        >
                        Home
                    </button>
                </div>
            </form>
        </div>
    )
}
```

# サーバ側(Laravel)のコード
`Model`クラスとテーブルを紐づけるために`$table`プロパティにテーブル名を指定します。
```
protected $table = 'adminusers';
```
さらに、カラム`adminId`を主キーと認識させるために次の処理を追加します。

```
protected $primaryKey = 'adminId';
```

さいごにオートインクリメントさせないように下記の処理を追加します。
```
public $incrementing = false;
```
以上を踏まえた`AdminUser.php`の全体ソースは下記です。

```backend/app/Models/Adminuser.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class AdminUser extends Model
{
    use HasFactory;

    protected $table = 'adminusers';//Add
    protected $primaryKey = 'adminId';// Assign Column'adminId' primarykey
    public $incrementing = false;//Not AutoIncrement
    protected $keyType = 'string';//UUID type is String

    public function getAdminUserId()
    {
        return $this->attributes['adminId'];
    }

    public function setAdminUserId($adminId)
    {
        $this->attributes['adminId'] = $adminId;
    }

    public function getAdminUserName()
    {
        return $this->attributes['adminUserName'];
    }

    public function setAdminUserName($adminUserName)
    {
        $this->attributes['adminUserName'] = $adminUserName;
    }

    public function getAdminUserEmail()
    {
        return $this->attributes['adminUserEmail'];
    }

    public function setAdminUserEmail($adminUserEmail)
    {
        $this->attributes['adminUserEmail'] = $adminUserEmail;
    }

    public function getAdminUserPassword()
    {
        return $this->attributes['adminUserPassword'];
    }

    public function setAdminUserPassword($adminUserPassword)
    {
        $this->attributes['adminUserPassword'] = $adminUserPassword;
    }

    public function getAdminUserPhoneNumber()
    {
        return $this->attributes['adminUserPhoneNumber'];
    }

    public function setAdminUserPhoneNumber($adminUserPhoneNumber)
    {
        $this->attributes['adminUserPhoneNumber'] = $adminUserPhoneNumber;
    }

    public function getAdminUserCountry()
    {
        return $this->attributes['adminUserCountry'];
    }

    public function setAdminUserCountry($adminUserCountry)
    {
        $this->attributes['adminUserCountry'] = $adminUserCountry;
    }

    public function getAdminUserStreetAddress()
    {
        return $this->attributes['adminUserStreetAddress'];
    }

    public function setAdminUserStreetAddress($adminUserStreetAddress)
    {
        $this->attributes['adminUserStreetAddress'] = $adminUserStreetAddress;
    }

    public function getAdminUserCity()
    {
        return $this->attributes['adminUserCity'];
    }

    public function setAdminUserCity($adminUserCity)
    {
        $this->attributes['adminUserCity'] = $adminUserCity;
    }

    public function getAdminUserStateProvince()
    {
        $this->attributes['adminUserStateProvince'];
    }

    public function setAdminUserStateProvince($adminUserStateProvince)
    {
        $this->attributes['adminUserStateProvince'] = $adminUserStateProvince;
    }

    public function getAdminUserZipCode()
    {
        return $this->attributes['adminUserZipCode'];
    }

    public function setAdminUserZipCode($adminUserZipCode)
    {
        $this->attributes['adminUserZipCode'] = $adminUserZipCode;
    }

    public function getCreatedAt()
    {
        return $this->attributes['created_at'];
    }

    public function setCreatedAt($createdAt)
    {
        $this->attributes['created_at'] = $createdAt;
    }

    public function getUpdatedAt()
    {
        return $this->attributes['updated_at'];
    }

    public function setUpdatedAt($updatedAt)
    {
        $this->attributes['updated_at'] = $updatedAt;
    }
}

```
マイグレーションクラスは貝のとおりです。
まず、管理者のIDは、セキュリティの観点から`UUID`としました。
つぎに、これから作るログイン画面でも使用する`E-mail address`をユニーク製薬としました。

```create_AdminUser_table.php
<?php

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

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('adminusers', function (Blueprint $table) {
            $table->uuid('adminId')->primary(); //Primary key
            $table->string('adminUserName');
            $table->string('adminUserEmail')->unique();
            $table->string('adminUserPassword');
            $table->integer('adminUserPhoneNumber');
            $table->string('adminUserCountry');
            $table->string('adminUserStreetAddress');
            $table->string('adminUserCity');
            $table->string('adminUserStateProvince');
            $table->integer('adminUserZipCode');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('adminusers');
    }
};

```

さいごにコントローラです。

#### 手動でのトランザクション処理を施すため下記のライブラリを追加しました。

```sample.php
use Illuminate\Support\Facades\DB;//Add
use Illuminate\Support\Facades\Log;//Add
```

#### `UUID`を生成するため下記のライブラリを追加しました。

```sample.php
use Illuminate\Support\Str; // Add to use uuid
```

```backend/app/Http/Controllers/AdminRegisterController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash; // Add hashd library
use App\Models\AdminUser; //Add AdminUser
use Illuminate\Support\Facades\DB;//Add
use Illuminate\Support\Facades\Log;//Add
use Illuminate\Support\Str; // Add to use uuid

class AdminRegisterController extends Controller
{
    public function createAdminUser(Request $request)
    {
        //\Log::info('リクエストデータ:', $request->all());
        // Get Requested Data
        $userName = $request->input('userName');
        $emailAddress = $request->input('emailAddress');
        $phoneNumber = $request->input('phoneNumber');
        $password = $request->input('password');
        //Make hashedPassword
        $hashedPassword = Hash::make($password);
        $country = $request->input('country');
        $streetAddress = $request->input('streetAddress');
        $city = $request->input('city');
        $stateProvince = $request->input('stateProvince');
        $zipCode = $request->input('zipCode');

        DB::beginTransaction();
        try{
            $adminUser = new AdminUser();
            $adminUser->adminId = Str::uuid();
            $adminUser->adminUserName = $userName;
            $adminUser->adminUserEmail = $emailAddress;
            $adminUser->adminUserPassword = $hashedPassword;
            $adminUser->adminUserPhoneNumber = $phoneNumber;
            $adminUser->adminUserCountry = $country;
            $adminUser->adminUserStreetAddress = $streetAddress;
            $adminUser->adminUserCity = $city;
            $adminUser->adminUserStateProvince = $stateProvince;
            $adminUser->adminUserZipCode = $zipCode;
    
            //Save the AdminUser
            $adminUser->save();
            // Commit
            DB::commit();

        }catch(\Throwable $e){
            DB::rollBack();
            Log::error('Admin user creation failed: ' . $e->getMessage());
            return response()->json([
                'error' => 'Admin user creation failed.',
                'message' => $e->getMessage(),
            ], 500);
            throw $e;
        }

        //$adminUser = new AdminUser();
        $createdAdminuserId = AdminUser::where('adminUserEmail', $emailAddress)->first();

        return response()->json([
            'adminUserId' => $createdAdminuserId->adminId,
            'username'=>$userName,
            'emailAddress'=>$emailAddress,
            'password'=>$password,
            'country'=>$country
        ]);
    }
}

```

# LaravelコマンドのTips

マイグレーションのリセットは下記のコマンドで実行します。
テーブルのカラム情報の更新に使用します。
```
php artisan migrate:refresh

```

# LaravelのマイグレーションのTips

#### `timestamps`の自動管理
`timestamps()`メソッドにより、`created_at`と`updated_at`は自動的に管理されるので、手動で設定する必要はありません。




# 参考サイト

#### フォーム inputのpatternを使って、電話番号・郵便番号・フリガナ・空白などを判別する方法

https://arts-factory.net/pattern/

#### Laravelでパスワードに強くなろう！ハッシュ化・パスワード照会

https://www.kamome-susume.com/laravel-password/

#### Laravelの困る機能を解決！テーブル名とモデル名が一致しない・IDで自動採番したくない時の対処法

https://www.yui-web-beginner.net/model_property/

#### Laravelでハッシュ化したいんですけどっ！！？

https://qiita.com/Sunasuna24/items/bc8117650e0639ce949c

#### Laravelでuuidをidとするモデルを作りたいとき

https://qiita.com/fagai/items/61a315a3a89e04bf6945

#### テーブルののユニーク制約について

https://qiita.com/yosub-shim/items/b9a590f9e6064000445f

#### Laravel公式サイトののUUIDライブラリ

https://readouble.com/laravel/10.x/ja/strings.html#method-str-uuid