PHPのissetと!is_nullは違うという話

## はじめに
こんにちは、普段は業務で求人系サービスの開発や社内向けツールの開発を行なっている@taku-0728です。
今回はPHPの存在チェックによく使われる`isset`と、その逆として取り上げられる`is_null`の違いについて取り上げます。[公式ドキュメントの型の比較表](https://www.php.net/manual/ja/types.comparisons.php)などでは`isset`と`is_null`の結果は正反対で記載されているので`isset = !is_null`だと思う方もいるかもしれませんが、実はこの2つには違いがあります。
知っている人からしたら当たり前かもしれませんが、比較表だけみたら同じように思えるので、詳細を調べたことがない方は参考にしてもらえればと思います。

## 結論

細かいことはいいから先に結論が知りたい人向けに結論をまとめます。
`isset`と`!is_null`で違いがあるのは下記です。

- 未定義の変数を比較した場合
- 実行速度

以下で順番に解説していきます。

## 解説
### 未定義の変数を比較した場合

```test1.php
<?php

$x = 'test';

var_dump(isset($y));
?>
```

上記のコードの実行結果は下記のようになります。

```
bool(false)
```

それに対し、下記のコードはどうなるでしょうか

```test2.php
<?php

$x = 'test';

var_dump(!is_null($y));
?>
```

答えはこうなります。

```
PHP Notice:  Undefined variable: y in test2.php on line 5
```

issetでは未定義の変数を比較してもエラーになりませんが、!is_nullだとエラーになります。
なぜこのようになるか解説していきます。

#### isset

> isset — 変数が宣言されていること、そして NULL とは異なることを検査する

[公式ドキュメント](https://www.php.net/manual/ja/function.isset.php)より
`isset`は変数が宣言されていることと、NULLではないことを検査するので、実行結果はfalseになります。

#### is_null

> is_null — 変数が NULL かどうか調べる

[公式ドキュメント](https://www.php.net/manual/ja/function.is-null.php)より
`is_null`は変数がNULLかどうかを調べるのみであり、`isset`とは違って変数が宣言されてるかどうかは検査してくれません。そのため結果はissetとは異なります。

公式ドキュメントの解説を読んでいただければもう解説する必要はないと思いますが、`isset`は変数が宣言されていることを確認してくれますが、`is_null`はNULLかどうかしか確認してくれません。そのため未定義の変数を比較すると差が生まれるというわけです。

### 実行速度

`isset`と`!is_null`の実行速度に違いがあるのか1000万回比較して検証してみます。

```php
<?php
$check_data = "";

// isset
$start_time1 = microtime(true);
$sum = 0;
for ($i=0; $i<=100000000;$i++) {
    if (isset($check_data)) {
      $sum += $i;
    }
}
$end_time1 = microtime(true);
var_dump('isset処理時間:'.($end_time1 - $start_time1)."秒\n");


// is_null
$start_time2 = microtime(true);
$sum = 0;
for ($i=0; $i<=100000000;$i++) {
    if (!is_null($check_data)) {
      $sum += $i;
    }
}
$end_time2 = microtime(true);
var_dump('!is_null処理時間:'.($end_time2 - $start_time2)."秒\n");
?>
```

3回実行してみましたが、実行結果は過去のようになりました。

```
string(37) "isset処理時間:1.1999080181122秒
"
string(39) "!is_null処理時間:1.6563079357147秒
"
```
```
string(37) "isset処理時間:1.2162489891052秒
"
string(39) "!is_null処理時間:1.6646618843079秒
"
```
```
string(37) "isset処理時間:1.1731791496277秒
"
string(38) "!is_null処理時間:1.658478975296秒
"
```

2つの処理時間には大体0.4~0.5秒ほどの差があることがわかります。
~~今回は条件式の中でカウンタ変数の加算をしているだけなので大した差にはなっていませんが、
条件式の中が複雑になるほど処理時間に差は生まれると思います。~~
**（追記 コメントにてご指摘をいただきましたが、誤った理解でしたので訂正しておきます）**
では同じようなことをやっているのになぜ差が生まれるのでしょうか？

#### 言語構造と関数の違い

`isset`と`!is_null`の処理速度に違いが生まれる理由は`isset`がPHPの言語構造であり、`is_null`は関数だからです。
言語構造とはその名の通り、言語を構成する要素のことです。関数のように定義して使うものではなく、すでにPHPの中に組み込まれた機能のことです。
PHPの言語構造一覧についてはこちらの[公式ドキュメント](https://www.php.net/manual/ja/reserved.keywords.php)を参照ください。
言語構造はPHPの中にすでに組み込まれているので、関数より先に確認にいきます。だから関数である`is_null`よりも、言語構造である`isset`で比較する方が処理速度が早くなるわけです。
また、`isset`は言語構造であるが故に未定義の変数から値を取得しようとしてもエラーにはなりませんが、`is_null `は関数であるため未定義の変数から値を取得しようとするとエラーになるわけです。

## まとめ

PHPの`isset`と`!is_null`には

- 未定義の変数を比較した場合
- 実行速度

の2つに違いがあります。その理由は`isset`がPHPに予め組み込まれた言語構造であるのに対し、`is_null`は関数だからです。
~~基本的に変数の存在チェックには`isset`を使えば問題ないとは思いますが~~`isset`と`is_null`は必要と用途に応じて使い分けた方がいいと思いますが、`isset`は空文字`''`をtrueとしてしまうので値が入っているかどうかを比較する際にはお気をつけください。
また、`!is_null`は` !== null`と等価ですのでこちらも好みに合わせて使い分けてください。

## 参考
- [isset()と!is_null()の値は常に等しいか](https://cnrd.hatenadiary.org/entry/20091127/1259235755)
- [【PHP】isset、empty、is_nullの効率的な使い方(PHP高速化)](https://pecopla.net/web-column/php-spead1)