Attempt to read property on nullの解決

<h2>LaravelでAttempt to read property on nullが出た話</h2>

<h3>記事内容</h3>

本記事ではLaravelで自作の認証機能を実装していた際に、本題のエラーが出たので、その解決方法を書き下ろします。


<h2>開発環境</h2>
Laravel 8.22.1
PHP：8.0.1
MySQL：8.0.23
MacOS：11.1 ( Big Sur )


<h2>やろうとしていること</h2>
ビューで入力フォームと、データベースにcompaniesテーブルを作成し、
ユーザーから入力された値と、テーブルに格納されているデータ(カラム：company_id)と一致するものがあれば、ページを遷移させる。
一致するものがなければ、簡単なエラーメッセージを返すというもの。

<h2>テーブル</h2>

```○○_create_companies_table.php
<?php

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

class CreateCompaniesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('companies', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('company_id')->unique();
            $table->string('name');
            $table->timestamps();
        });
    }
```

今回の認証に利用したのはcompany_idカラムのみの簡単なもの。

<h2>Blade</h2>

```login.blade.php(一部)
<div class="cpylogin-block">
  <form action="/company/login" method="post">
    <label for="company_id">会社/団体ID</label>
    <input type="text" name="company_id" value="{{ old('company_id') }}" placeholder="ID" required>
    <div class="btn-area">
      <button>ログイン</button>
    </div>
    {{ csrf_field() }}
  </form>
```

フォームの入力値も1つのみ。inputタグのname属性をcompany_idとする。
ちなみにloginブレードはコントローラの記述より、route/web.phpから呼び出しています。

<h2>Controller</h2>

```LoginController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Models\Company;

class CpyLoginController extends Controller
{
    //
    public function index(Request $request)
    {
        return view('pages.cpylogin');
    }

    public function post(Request $request)
    {
        // Companyモデルからcompany_idで情報を検出→オブジェクト化
        $company = Company::where('company_id', $request->company_id)->first();
        $msg = ['msg' => '入力されたIDは存在しません'];

        if ($request->company_id === $company->company_id) {
            return redirect()->route('usrlogin');
        }
        else {
            return view('pages.cpylogin', $msg);
        }
    }
}
```

・LoginControllerのpostメソッドで認証機能が動くように記述。
Authを使っていない上に、よくあるuserモデルでのログイン機能ではないことをあらかじめ把握しておいてください。

・if文のところで、フォームのinputタグに入力された値がcompanyモデルのcompany_idカラムに保存されているデータと一致するものがあれば、usrloginビューを表示する。

<h2>実行</h2>

![スクリーンショット 2021-02-13 19.04.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1107491/c6745e85-2a21-ad8e-93d5-946c99af6032.png)

Attempt to read property "company_id" on null とエラーになる。
日本語訳すると、「“company_id” プロパティをnullなのに読み込もうとしている。」の意味(少しズレてるかも)。

<h2>原因</h2>
エラーの原因はControllerの中の、companyモデルを呼び出す記述にありました。

```LoginController.php

--- 省略 ---

    public function post(Request $request)
    {
        // Companyモデルからcompany_idで情報を検出→オブジェクト化
        $company = Company::where('company_id', $request->company_id)->first();

--- 省略 ---

    }

```
\$company変数の定義を間違えていました。
\$company変数を、\$request->company_idと、フォームで入力された値と紐付けるのではなく、
companyテーブルに存在する全てのデータをオブジェクト化したものにするべきでした。

<h2>解決策</h2>
ということで、\$company変数の定義を下記のように書き換えると解決しました。

```LoginController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Models\Company;

class CpyLoginController extends Controller
{
    //
    public function index(Request $request)
    {
        return view('pages.cpylogin');
    }

    public function post(Request $request)
    {
        $validate_rule = $request->validate([
            'company_id' => ['required']
        ]);

        // Companyモデルからcompany_idで情報を検出→オブジェクト化
        $company = Company::all()->first();
        $msg = ['msg' => '入力されたIDは存在しません'];

        if ($request->company_id === $company->company_id) {
            return redirect()->route('usrlogin');
        }
        else {
            return view('pages.cpylogin', $msg);
        }
    }
}
```

\$company変数をCompanyモデルから全て取り出しオブジェクト化したデータと定義することで、うまくいきました。