【Laravel】生年月日や誕生日から年齢計算してそのままユーザー新規登録



<h1>はじめに</h1>
はじめまして！
未経験webエンジニア転職を目指して4ヶ月が経ちました。
スクールでは共同開発が終わり、ポートフォリオ作成に取り掛かかっている最中です！
そこで、今回執筆する内容は生年月日から年齢を算出できる方法です！
初めは「年齢って数字で入力して登録するのかな？」「生年月日から算出できないかな」という疑問がきっかけで実装に取り掛かろうと思い、無事クリアしましたので、アウトプットがてらまとめて共有していきます！（ほぼ参考サイトのままですが😅）
至らぬ点が多々あるとは思いますが、何かの実装の参考になれば幸いです。😆
では、私の経験も交えて解説していきます！

<h1>目次</h1>
<b>
0,　はじめに
1,　完成品
2,　前提
3,　説明 ①〜⑨
　　① <a>マイグレーション</a>
　　② <a>モデル</a>
　　③ <a>Controller</a>
　　④ <a>livewireのインストール</a>
　　⑤ <a>ファイルの作成</a>
　　⑥ <a>PHPクラス</a>
　　⑦ <a>View(1) 年齢計算表示画面</a>
　　⑧ <a>View(2) ユーザー新規登録画面</a>
　　⑨ <a>View(3) 共通画面</a>
4, 　さいごに
5,   　参考文献
</b>

<h1>1,　完成品</h1>
① ユーザー新規登録画面
![Image from Gyazo](https://i.gyazo.com/a2f792976825bfdfd842ac3347fb36d4.gif)
・生年月日を入力
・年齢 と書かれている隣に年齢を自動算出

② DB確認（Tinker）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1178769/67ede724-3071-b825-6446-dcce56b10af3.png)
・ageに65の値が登録されていれば成功


<h1>2,　前提</h1>
・ Laravel、PHPを勉強中の初学者、駆け出しエンジニア
・ 簡単なユーザー新規登録を実装したことがある方
・ ユーザー新規登録時に [年齢] を登録したい人 ← <b>生年月日を登録するわけではないヨ</b>
・ バリデーションの解説は省略
・ CRUD処理,MVCモデルを大体理解できている方
・ DBに登録する流れを大体理解できている方
・ 暖かい目で応援してくれる方
以上のどれかに当てはまる方と同じ目線で執筆してるかと思います。
少し長ったらしく感じるかもしれませんが、少々お付き合いください。🙇‍♂️

<section id="intro"> <h1>3,　説明 ①〜⑧</h1> </section>

<h3>① マイグレーション</h3>

早速ですが、テーブルを作りましょう！
addカラムで追加してもいいですし作り直してもいいと思います。
既に作成してしまっているものですが、例として私の各ファイルを載せてます。


```php:xxxx_xx_xx_xxxxxx_create_users_table.php
<?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('screen_name')->unique()->null();
            $table->string('name')->null();
            $table->string('email', 128)->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password', 64);
            $table->integer('age');//年齢
            $table->integer('genders_id')->unsigned()->nullable();
            $table->foreign('genders_id')->references('id')->on('genders')->onDelete('cascade');
            $table->string('profile_image')->nullable();
            $table->char('delete_flag', 1)->default(0);
            $table->rememberToken();
            $table->timestamps();
        });
    }

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

```

いろいろと汚くてすみません...🙇‍♂️
ここはサクッとできたかなと思います。

```php
$ php artisan migrate
```

マイグレーションして次の工程へ急ぎましょう！！

<h3>② Model</h3>

```php:User.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;
    
    use SoftDeletes;
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'screen_name',
        'name',
        'email',
        'password',
        'user_sexes_id',
        'age',
        'single_comment',
        'profile_image'
    ];

    protected $dates = ['deleted_at'];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */

    protected $hidden = [
        'password', 'remember_token',
    ];

    /**
     * The attributes that should be cast to native types.
     *
     * @var array
     */
     
    protected $casts = [
        'email_verified_at' => 'datetime',
    ];
```

fillableで age（年齢）を書き換えれるようにします。
こんなに書き換えが多いとguardedでも良かったのかなと思いますね...笑
ここにはSofutDelesなどもuseしてきていますが、退会機能など後々使えるので加えて損わないかもです！

<h3>③ Controller</h3>

```php:RegisterController
<?php

namespace App\Http\Controllers\Auth;

use App\Models\User; ←モデルの追記
use App\Gender;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;

class RegisterController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Register Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles the registration of new users as well as their
    | validation and creation. By default this controller uses a trait to
    | provide this functionality without requiring any additional code.
    |
    */

    use RegistersUsers;

    /**
     * Where to redirect users after registration.
     *
     * @var string
     */
    protected $redirectTo = '/';　　← '/home'から変更

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest');
    }

    /**
     * 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, [
            'screen_name' => ['required', 'string', 'min:6', 'max:50'],
            'name' => ['required', 'string', 'min:1', 'max:15'],
            'age' => ['required', 'string', 'max:200'],  ← 年齢のバリデーション
            'genders_id' => ['required'],
            'single_comment' => ['required', 'string', 'min:1', 'max:15'],
            'email' => ['required', 'string', 'unique:users','email'],
            'password' => ['required', 'string', 'min:6', 'max:15', 'confirmed'],
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        return User::create([
                'screen_name' => $data['screen_name'],
                'name' => $data['name'],
                'age' => $data['age'],  ← 年齢の登録
                'genders_id' => $data['genders_id'],
                'single_comment' => $data['single_comment'],
                'email' => $data['email'],
                'password' => Hash::make($data['password']),
        ]);
    }
}
```

コントローラーはLaravelの既存のものを使います。
Userモデルの記載とリダイレクト先に注意です。
バリデーションやcreate処理は周りと同じ感じでOKです。

<h3>④ livewireをインストールする</h3>

さて、下準備は整いました。
これから<b>conposerのパッケージのひとつ</b>をインストールします。
それが<b>「livewire」</b>という<b>Laravelのコンポーネントシステム</b>です。
どうやら近年に公開された？らしい...
ちょっとまだ勉強不足なとこもあってよく分かってないです！笑
とりあえずこれを入れれば年齢計算に必要な要素が揃うということで。
では下のコマンドでインストールしていきます。

```php
$ composer require calebporzio/livewire
//うまくいかない場合は
$ composer require calebporzio/livewire:0.*
```

<h3>⑤ ファイルの作成</h3>
livewireに必要なファイルを作成します。
以下のようにartisanコマンドで作成することができます。

```php
$ php artisan make:livewire birthday
```

実行完了すると、下のように<b>各ファイル（2つ）</b>が自動的に作成されましたでしょうか？
> App > Http > Livewire > <b>Birthday.php</b>
> resources > views > livewire > <b>birthday.blade.php</b>

確認できれば次は中身の記述です。

<h3>⑥ PHPクラス</h3>

```php:Birthday.php
<?php

namespace App\Http\Livewire;

use Carbon\Carbon;
use Livewire\Component;

class Birthday extends Component
{
    // 変数をビューで使えるようにここで定義
    public $year = 0;
    public $month = 0;
    public $day = 0;
    public $age = -1;
    public $last_day_of_month = 0;

    // 定義したら実行
    public function mount($year = 0, $month = 0, $day = 0) {

        $this->year = $year;
        $this->month = $month;
        $this->day = $day;
        $this->onChange();

    }

    // 入力ボックスに変更のある度に呼ばれる
    public function onChange()
    {
        $year = intval($this->year);
        $month = intval($this->month);
        $day = intval($this->day);

        // 該当月の日を計算（28〜31日）
        if($year > 0 && $month > 0) {

            $this->last_day_of_month = Carbon::create($this->year, $this->month)->endOfMonth()->day;

        }

        // 年齢を計算
        if(checkdate($month, $day, $year)) {

            $this->age = Carbon::createFromDate($this->year, $this->month, $this->day)->age;

        } else {

            $this->age = -1;

        }
    }

    public function render()
    {
        return view('livewire.birthday');
    }
```

こちらは元々空のファイルなので、丸々コピペしても構いません。
計算の処理ですね、定義して実行して格納して、、
あともう少し。。

<h3>⑦ View(1) 年齢計算表示画面</h3>

```php:birthday.blade.php
<div>

    <!-- 年 -->
    <select name="birth-year" wire:model="year" wire:change="onChange">
        <option></option>
        @for($i = 1900 ; $i <= date('Y') ; $i++)
        <option value="{{ $i }}">{{ $i }}年</option>
        @endfor
    </select>

    <!-- 月 -->
    <select name="birth-month" wire:model="month" wire:change="onChange">
        <option></option>
        @for($i = 1 ; $i <= 12 ; $i++)
        <option value="{{ $i }}">{{ $i }}月</option>
        @endfor
    </select>

    <!-- 日 -->
    <select name="birth-day" wire:model="day" wire:change="onChange">
        <option></option>
        @for($i = 1 ; $i <= $last_day_of_month ; $i++)
        <option value="{{ $i }}">{{ $i }}日</option>
        @endfor
    </select>

    <!-- 年齢 -->
    @if($age > -1)
        &nbsp;/&nbsp;{{ $age }} 才
    @endif

</div>
```

年齢を算出して表示するための記述がされています。
年、月、日はわざわざテーブルにbirthdayカラムを入れなくても便利なコンポーネントが入っているのでこのように算出することが可能となります。

```php
    <!-- 年齢 -->
    @if($age > -1)
        &nbsp;/&nbsp;{{ $age }} 才
    @endif
```
↓  ↓  ↓

```php
    <!-- 年齢 -->
    @if($age > -1)
        <input name="age" type="hidden" value="{{ $age }}">
        &nbsp;/&nbsp;{{ $age }} 才
    @endif
```

私の場合は<b>算出した年齢をそのままDBにユーザー情報として登録</b>したかったので、<b>inputタグ</b>に変数（$age）を入れてやりました</b>
ちょっと無理矢理感があるけど咄嗟に思いついたのがこれしかなかったです。。笑
もっと綺麗な方法があれば遠慮せず教えてくれると勉強になります！

最後は
@livewireStyles　→　<b>app.blade.php　（headの中）</b>
@livewire('birthday')　→　<b>register.blade.php  （年齢のとこ）</b>
@livewireScripts　→　<b>app.blade.php　（bodyの中）</b>
をそれぞれのファイルに書き入れてあげて完了です。
この三要素を加えてあげないと、せっかくのコンポーネントのバランスがうまく取れない状態です。

<h3>⑧ View(2) ユーザー新規登録画面</h3>

```php:register.blade.php
@extends('layouts.app')

@section('content')

    <div class="page-header mt-5 text-center">
            <h2>新規登録画面</h2>
    </div>
        
    <div class="row mt-5 mb-5">
        <div class="form-control-sm col-sm-5 mx-auto float-right">
            <form method="POST" action="{{ route('signup.post') }}">
            @csrf
                
                <div class="mt-1 clearfix">
                    <p class="d-inline ml-2">アカウントID（6桁以上）</a>
                    @if ($errors->has('screen_name'))
                        <div class="row justify-content-center">
                            <div class="cal-xs-4">
                                <span style="color:red">{{ $errors->first('screen_name') }}</span>
                            </div>
                        </div>
                    @endif
                    <input type="text" name="screen_name" class="form-control-sm col-sm-8 ml-5 d-inline float-right">
                </div>
                
                <div class="mt-1 clearfix">
                    <p class="d-inline ml-2">ユーザー名（15字以内）</a>
                    @if ($errors->has('name'))
                        <div class="row justify-content-center ml-5">
                            <div class="cal-xs-4">
                                <span style="color:red">{{ $errors->first('name') }}</span>
                            </div>
                        </div>
                    @endif
                    <input type="text" name="name" class="form-control-sm col-sm-8 ml-5 d-inline float-right">
                </div>
                
                <div class="mt-1 clearfix">
                    <div class="form-control-sm d-inline float-left">
                        <h6>年齢</h6>
                    </div>
                    @if ($errors->has('birthday'))
                        <div class="row justify-content-center">
                            <div class="cal-xs-4">
                                <span style="color:red">{{ $errors->first('birthday') }}</span>
                            </div>
                        </div>
                    @endif
                    @livewire('birthday')　　←   //追記
                </div>

                <div class="mt-1 clearfix">
                    <p class="d-inline ml-2">性別</p>
                    @if ($errors->has('genders_id'))
                        <div class="row justify-content-center">
                            <div class="cal-xs-4">
                                <span style="color:red">{{ $errors->first('genders_id') }}</span>
                            </div>
                        </div>
                    @endif
                    <select class="form-control-sm col-sm-8 ml-5 d-inline float-right" id="changeSelect" name="genders_id" onchange="entryChange2();">
                        <option value="">未選択</option>
                        @foreach ($genders as $gender)
                            <option value="{{ $gender->id }}">{{ $gender->gender }}</option>
                        @endforeach
                    </select>
                </div>
                
                <div class="mt-1 clearfix">
                    <p class="d-inline ml-2">ひとこと（15字以内）</p>
                    @if ($errors->has('single_comment'))
                        <div class="row justify-content-center ml-5">
                            <div class="cal-xs-4">
                                <span style="color:red">{{ $errors->first('single_comment') }}</span>
                            </div>
                        </div>
                    @endif
                    <input type="text" name="single_comment" class="form-control-sm col-sm-8 ml-5 d-inline float-right">
                </div>
                
                <div class="mt-1 clearfix">
                    <p class="d-inline ml-2">メースアドレス</p>
                    @if ($errors->has('email'))
                        <div class="row justify-content-center ml-5">
                            <div class="cal-xs-4">
                                <span style="color:red">{{ $errors->first('email') }}</span>
                            </div>
                        </div>
                    @endif
                    <input type="text" name="email" class="form-control-sm col-sm-8 ml-5 d-inline float-right">
                </div>
                
                <div class="mt-1 clearfix">
                    <p class="d-inline ml-2">パスワード（6桁以上）</p>
                    @if ($errors->has('password'))
                        <div class="row justify-content-center ml-5">
                            <div class="cal-xs-4">
                                <span style="color:red">{{ $errors->first('password') }}</span>
                            </div>
                        </div>
                    @endif
                    <input type="text" name="password" class="form-control-sm col-sm-8 ml-5 d-inline float-right">
                </div>
                
                <div class="mt-1 clearfix">
                    <p class="d-inline ml-2">パスワード再入力</p>
                    @if ($errors->has('password_confirmation'))
                        <div class="row justify-content-center">
                            <div class="cal-xs-4">
                                <span style="color:red">{{ $errors->first('password_confirmation') }}</span>
                            </div>
                        </div>
                    @endif
                    <input type="password" name="password_confirmation" class="form-control-sm col-sm-8 ml-5 d-inline float-right">
                </div>
                
                <div class="text-center mt-5">
                     <button type="submit" class="btn btn-primary w-50">登録</button>
    
                     <p class="mt-5">
                         <a href="#" class="text-primary d-inline">ログインはこちらから</a>
                     </p>
                 </div>
             </form>
         </div>    
    </div>
@endsection
```

具体的に見やすくしてみると

```php                    
<form method="POST" action="{{ route('signup.post') }}">
@csrf
              〜略〜

    <div class="mt-1 clearfix">
        <div class="form-control-sm d-inline float-left">
            <h6>年齢</h6>
        </div>
        @if ($errors->has('birthday'))
            <div class="row justify-content-center">
                <div class="cal-xs-4">
                    <span style="color:red">{{ $errors->first('birthday') }}</span>
                </div>
            </div>
        @endif
        @livewire('birthday')　←  //追記
    </div>

　　　　　　　　 〜略〜
</form>
```

<b>@lovewire('birthday')</b>で先ほど記述した<b>年齢計算表示画面</b>を呼び出しています。
つまり、<b>register.blade.php</b>の中に<b>birthday.blade.php</b>が入っている状態です。

<h3>⑨ View(3) 共通画面</h3>

```php:app.blade.php
<!DOCTYPE html>
<html lang="ja">

    <head>
        <meta charset="utf-8">
        <meta name="csrf-token" content="{{ csrf_token() }}">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
        <link rel="stylesheet" href="/css/styles.css">
        @livewireStyles  ←  //追記
    </head>

    <body>
        @include('commons.header')
            <div class="container">

                @include('commons.error_messages')
                @yield('content')

            </div>
        @livewireScripts　←  //追記
        @include('commons.footer')
    <body>
</html>
```


<h1>4,　さいごに</h1>

これで実装は完了です！
DBに登録できていますでしょうか。
最後にこんな初学者のアウトプット記事を読んでいただきありがとうございます。
実装までほとんど殴り書きのような感覚でいたので記述ミス等あるかもしれません。
そこはお許しください...🙇‍♂️

また、現在転職活動に伴いTwitter風×instagram風のポートフォリオを作成しています。
ツイート機能、フォロー機能、いいね機能、画像投稿機能、アップデート機能、ハッシュタグ検索機能、GoogleマップAPI等苦戦しながらも実装中です。
時間があればこれらの機能も解説を交えてアウトプットしていく方針なので、フォローやコメントなどお待ちしております！
お疲れ様でした！



<h1>参考文献</h1>
•　 [Laravel + livewireで誕生日から年齢を自動計算するサンプル](https://blog.capilano-fw.com/?p=4466)
•　 [MySQLで誕生日から年齢を取得する](https://qiita.com/sunnyG/items/eb09370b1f4265aa4503)
•　 [まだ年齢計算で消耗してるの？](https://qiita.com/kawasima/items/8fdd3939be151f33f7cf)
•　[【Laravel】Composer requireでエラー](https://qiita.com/anoonoll/items/5fab1e6682861a406570)