【PHP】is_nullもissetもemptyも絶妙に微妙なので、"is_nullorempty"を自作しよう

# はじめに
先日、Twitterでこんな投稿をしました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">is_nullもissetもemptyも絶妙に自分のニーズに合わないので<br>is_nullorempty自作して使ってます<br><br>null→true<br>&quot;&quot;→true<br>0→false<br>array(0)→true<br>&quot;foo&quot;→false<br>1→false <a href="https://t.co/tRZPWWeoI1">https://t.co/tRZPWWeoI1</a></p>&mdash; 佐藤裕/株式会社カジトリ代表 (@hirossyi73) <a href="https://twitter.com/hirossyi73/status/1192417166080925696?ref_src=twsrc%5Etfw">November 7, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

今回はこの話を、もう少し掘り下げて話を進めていきたいと思います。

# isset, empty, is_nullどれも絶妙に微妙
PHPのnull・空判定には、主に「isset, empty, is_null」の3つが用意されています。
しかし、これらの関数は、私にとって**絶妙に微妙**でした。

まずは、これらの関数の結果を改めて見てみます。なお[PHP isset, empty, is_null の違い早見表](https://qiita.com/shinichi-takii/items/00aed26f96cf6bb3fe62)からの引用です。

|値|if($var)|isset|empty|is_null|
|:--|:--:|:--:|:--:|:--:|
|$var=1|**true**|**true**|*false*|*false*|
|$var="";|*false*|**true**|**true**|*false*|
|$var="0";|*false*|**true**|**true**|*false*|
|$var=0;|*false*|**true**|**true**|*false*|
|$var=NULL;|*false*|*false*|**true**|**true**|
|$var|*false*|*false*|**true**|**true**|
|$var=array()|*false*|**true**|**true**|*false*|
|$var=array(1)|**true**|**true**|*false*|*false*|


## 空判定したい時は、「入力チェックを行いたい時」
さて、皆さんがこれらの空判定関数を使用したい時は、主にどんな状況でしょうか？
色々あると思いますが、私が思うもっとも頻出な場面として、

**ユーザーやシステムが、値を入力したかどうかの必須チェックを行う**
時だと思います。

ユーザーの画面入力、CSVからの取込やデータベースからのデータ取得など、多くの場面で出てくる、「値の有無チェック」。
そのような前提を考えた時、上記の3関数だと、実はどれも**絶妙にマッチしない**んです。

なので、このような入力チェックで完全に判定できるような関数、「is_nullorempty」を作成しよう！というのが、今回の目的です。

※issetとemptyは厳密には関数じゃないらしいですが、ここでは関数としちゃいます


### 空判定したい内容の分析
ここからは、各値ごとに、「 **空判定すべきかどうか** 」のあるべき姿の分析を行っていきます。この値は空としたいかどうか？という判断ですね。
実際に入力チェックを行った時に、この値は空にしたいかどうか？という観点で見ていきます。

※なお、ここでは「空」は、「値がない」ということを指します。
またこれ以降、空として扱いたい場合は"空とする"と、空として扱いたくない場合は"空としない"と記載していきます。

#### ■ null
これは間違いなく"空とする"ですね。

#### ■ 空文字""
CSVやExcel取込などでよく発生する、「空文字""」の場合。
関数issetだとtrueになる("空としない"となる)この値ですが、これは間違いなく **"空とすべき"** でしょう。
ユーザーが何も入力していないから、空文字となって返ってくるわけです。そのため、空文字は"空とする"として考えます。

#### ■ 0
関数emptyだとtrueになる("空とする"となる)この値ですが、これはどうでしょうか？
ユーザーが「0」と入力した時、CSVに「0」と入力されていた時。これは「未入力」として扱うべきでしょうか。違うと思います。
そのため、0は"空としない"として考えます。もちろん文字列"0"も同様です。

#### ■ array()
要素がない配列。これはユーザーやシステムが値をちゃんと入れていないから、要素数が0の配列が出来ているわけです。
そのため、要素数0の配列は"空とする"として考えます。

#### ■ " "（半角スペース）、ならびに"　"（全角スペース）のみの文字列
さて半角・全角スペースのみの文字列。これはどうでしょうか。これに関しては判断が迷いそうです。
これは私の考え方ですが、**状況によりけり**だと考えます。常に"空とする"としても、"空としない"としても、場合によって色々と異なってきそうです。

そこでどうするかというと、スペースのみを"空としない"とする"**is_nullorempty**"と、"空とする"とする"**is_nullorwhitespace**"の両方を作ってしまっていいと考えます。
両方作った上で、「基本はこちらを使用する」というルールを決めてしまう。もしくは、あえてどちらかしか実装しない。という方針が良いと思います。

ちなみにこの関数名ですが、C#から来ています。[String.IsNullOrEmpty](https://docs.microsoft.com/ja-jp/dotnet/api/system.string.isnullorempty)と[String.IsNullOrWhiteSpace](https://docs.microsoft.com/ja-jp/dotnet/api/system.string.isnullorwhitespace)があります。


### 空判定まとめ
これが私の考える、空判定のあるべき姿です。

|値|is_nullorempty|is_nullorwhitespace|
|:--|:--:|:--:|
|$var=1|*false*|*false*|
|$var="";|**true**|**true**|
|$var="0";|*false*|*false*|
|$var=0;|*false*|*false*|
|$var=NULL;|**true**|**true**|
|$var|-|-|
|$var=array()|**true**|**true**|
|$var=array(1)|*false*|*false*|
|$var=" "|*false*|**true**|
|$var="　"|*false*|**true**|

※"$var"について、undefinedな変数を関数に代入した時点でエラーになっちゃうので、判定できません。そこだけは欠点ですね


# 実装
では実装に入りたいと思います。
なお実装方法については、もっといい方法がありそうです（・∀・）


~~~ php
<?php
		
	if (!function_exists('is_nullorempty')) {
		/**
		 * validate string. null is true, "" is true, 0 and "0" is false, " " is false.
		 */
		function is_nullorempty($obj)
		{
			if($obj === 0 || $obj === "0"){
				return false;
			}

			return empty($obj);
		}
	}

	if (!function_exists('is_nullorwhitespace')) {
		/**
		 * validate string. null is true, "" is true, 0 and "0" is false, " " is true.
		 */
		function is_nullorwhitespace($obj)
		{
			if(is_nullorempty($obj) === true){
				return true;
			}

			if(is_string($obj) && mb_ereg_match("^(\s|　)+$", $obj)){
				return true;
			}
			
			return false;
		}
	}
	
	/**
	 * テスト用
	 *
	 * @param [type] $value
	 * @return void
	 */
	function strbool($value)
	{
		return $value ? 'true' : 'false';
	}


        // テスト。ザル
	echo('is_nullorempty "foo" is ' . strbool(is_nullorempty("foo")));
	echo('<br />is_nullorempty 1 is ' . strbool(is_nullorempty(1)));
	echo('<br />is_nullorempty "" is ' . strbool(is_nullorempty("")));
	echo('<br />is_nullorempty "0" is ' . strbool(is_nullorempty("0")));
	echo('<br />is_nullorempty 0 is ' . strbool(is_nullorempty(0)));
	echo('<br />is_nullorempty NULL is ' . strbool(is_nullorempty(null)));
	echo('<br />is_nullorempty array() is ' . strbool(is_nullorempty(array())));
	echo('<br />is_nullorempty array(1) is ' . strbool(is_nullorempty(array(1))));
	echo('<br />is_nullorempty " " is ' . strbool(is_nullorempty(" ")));
	echo('<br />is_nullorempty "　" is ' . strbool(is_nullorempty("　")));
	echo('<br />');
	echo('<br />is_nullorwhitespace "foo" is ' . strbool(is_nullorwhitespace("foo")));
	echo('<br />is_nullorwhitespace 1 is ' . strbool(is_nullorwhitespace(1)));
	echo('<br />is_nullorwhitespace "" is ' . strbool(is_nullorwhitespace("")));
	echo('<br />is_nullorwhitespace "0" is ' . strbool(is_nullorwhitespace("0")));
	echo('<br />is_nullorwhitespace 0 is ' . strbool(is_nullorwhitespace(0)));
	echo('<br />is_nullorwhitespace NULL is ' . strbool(is_nullorwhitespace(null)));
	echo('<br />is_nullorwhitespace array() is ' . strbool(is_nullorwhitespace(array())));
	echo('<br />is_nullorwhitespace array(1) is ' . strbool(is_nullorwhitespace(array(1))));
	echo('<br />is_nullorwhitespace " " is ' . strbool(is_nullorwhitespace(" ")));
	echo('<br />is_nullorwhitespace "　" is ' . strbool(is_nullorwhitespace("　")));

    // テスト結果
    // is_nullorempty "foo" is false
    // is_nullorempty 1 is false
    // is_nullorempty "" is true
    // is_nullorempty "0" is false
    // is_nullorempty 0 is false
    // is_nullorempty NULL is true
    // is_nullorempty array() is true
    // is_nullorempty array(1) is false
    // is_nullorempty " " is false
    // is_nullorempty "　" is false
    // 
    // is_nullorwhitespace "foo" is false
    // is_nullorwhitespace 1 is false
    // is_nullorwhitespace "" is true
    // is_nullorwhitespace "0" is false
    // is_nullorwhitespace 0 is false
    // is_nullorwhitespace NULL is true
    // is_nullorwhitespace array() is true
    // is_nullorwhitespace array(1) is false
    // is_nullorwhitespace " " is true
    // is_nullorwhitespace "　" is true
?>

~~~

# まとめ
- 値の入力チェックは、基本3関数どれも微妙なので、自作の関数を作ろう
- "is_nullorempty"と"is_nullorwhitespace"の両方作って、スペースのみを振り分けよう
- "タブのみ"って空とすべきなのか・・・？