isset, empty, is_null の動作まとめ

[isset]: http://www.php.net/manual/ja/function.isset.php
[empty]: http://www.php.net/manual/ja/function.empty.php
[is_null]: http://www.php.net/manual/ja/function.is-null.php
[compact]: http://www.php.net/manual/ja/function.compact.php
[filter_input]: http://www.php.net/manual/ja/function.filter-input.php
[property_exists]: http://www.php.net/manual/ja/function.property-exists.php
[array_key_exists]: http://www.php.net/manual/ja/function.array-key-exists.php

# 空値

以下に示されるものは全て `if ($var) { ... }` のように用いたとき `false` と評価されます。これらは俗に **空値** と呼ばれる値です。

|名称|具体例|
|:--:|:--:|
|数値のゼロ|`0`<br />`0.0`|
|**文字列のゼロ**|**`"0"`**|
|空文字列| `""` |
|空配列| `[]`<br />`array()`|
|偽|`false`|
|ヌル|`null`|
|**空要素から成る<br />`SimpleXMLElement`**|`new SimpleXMLElement('<foo></foo>')`|

**※ 未定義の変数は `Notice: Undefined variable` を発生しながら `null` として扱われます。**

複雑な表を書き並べている記事が多いですが、実際は

- `null` であれば
- `null` でなければ
- 空値であれば
- 空値でなければ

の4種類の内容しか取り扱っていないものがほとんどであり、覚えるのが難しいなどと悩む必要はありません。至ってシンプルです。但し、(空配列はまだ分かるとして) **文字列のゼロ**と**空要素から成るSimpleXMLElement**の扱いには特に注意してください。これはPHPにおいて直感的ではない、最もクセのある実装のひとつです。JavaScriptなどと比較してもこれらの点だけ異なっていたりします。

# 変数の存在を確認するショートコード

これから紹介する[isset]や[empty]は、**未定義と`null`を区別することが出来ません**。[isset]や[empty]について紹介する前に、純粋に**「変数が定義されているか」**だけをチェックする方法を紹介します。[compact]という関数を用います。

```php:compact関数の利用例
$a = 'hoge';
$b = 1;
$d = null;
var_dump(compact('a', 'b', 'c', 'd'));
/*
array(3) {
  ["a"]=>
  string(4) "hoge"
  ["b"]=>
  int(1)
  ["d"]=>
  NULL
}
*/
```

この関数は現在のスコープに変数が存在している場合のみ、変数名をキーとして値を配列に取り込む動作をします。これを利用すれば、定義済みかどうかを以下のように確認することが出来ます。上記の表で紹介した**「空配列以外の配列は `true` として扱われる」**という性質を利用しています。

```php:変数varが現在のスコープ内で定義済みならば
if (compact('var')) { ... }
```

```php:変数varが現在のスコープ内で未定義ならば
if (!compact('var')) { ... }
```

# [isset], &#33;[empty], &#33;[is_null] の比較

上記のコードを用いると、これら3つは以下のように表現することが出来ます。

```php:if&nbsp;(isset($var))&nbsp;{&nbsp;...&nbsp;}
if (compact('var') && $var !== null) { ... }
```

```php:if&nbsp;(!empty($var))&nbsp;{&nbsp;...&nbsp;}
if (compact('var') && $var) { ... }
```

```php:if&nbsp;(!is_null($var))&nbsp;{&nbsp;...&nbsp;}
if ($var !== null) { ... }
```

以下の原則が挙げられます。

- [isset]は変数の存在をチェックするためのオーバーヘッドがあるので、定義済みであると確定している場合は `$var !== null` によるチェックで済ませるほうが良い。(但し複数同時にチェックする際に簡略化出来る場合を除く)
- &#33;[empty]は変数の存在をチェックするためのオーバーヘッドがあるので、定義済みであると確定している場合は `$var` によるチェックで済ませるほうが良い。**(但し`empty`という語のわかりやすさから，配列が空かどうか調べるときには定義済みが確定していてもこれが用いられる場合がある)**
- &#33;[is_null]は関数呼び出しのオーバーヘッドがあるので、この用途では使う必要が全くない。

# [isset]の特徴

- これは**関数では無く**言語構造であるため、関数ほどオーバーヘッドが大きくありません。
- 変数が未定義の場合でもエラーを出さずに`null`の確認ができます。
- **変数のみ**を引数として受け取ります。つまり、関数の返り値や値リテラルを直接渡すことは不可能となります。一時的に変数に代入する必要があります。

```php:動作しません
if (isset(my_example_function($arg))) { ... }
```

- 複数の変数を同時にチェックすることが出来ます。以下の記述はそれぞれ各行が等価になります。

```php:\$a,\$b,\$cの値が全てnullでなければ
if (isset($a, $b, $c)) { ... } 
if (isset($a) && isset($b) && isset($c)) { ... }    
```

```php:\$a,\$b,\$cの値のうちどれか一つでもnullであれば
if (!isset($a, $b, $c)) { ... } 
if (!isset($a) || !isset($b) || !isset($c)) { ... }    
```

- 配列キーやオブジェクトプロパティに対しても使用することができ、その場合は再帰的に未定義チェックを行ってくれます。**一階層ずつチェックする必要がなくなります。**
 
```php:配列キーやオブジェクトプロパティの存在を再帰的に確認する
if (isset($response->errors[0]->message)) { /* TwitterAPIでよくあるエラーチェック */ }
```

- 文字列のオフセットをチェックすることにも使えます。以下の各行は等価です。但し、**[罠が多い](http://qiita.com/takc923/items/7e4b12a126bbf964ccaa)**ので注意が必要です。

```php:$bufが5バイト以上であるかどうかチェックする
if (strlen($buf) >= 5) { ... }
if (isset($buf[4])) { ... }
```

# [empty] の特徴

- これは**関数では無く**言語構造であるため、関数ほどオーバーヘッドが大きくありません。
- 変数が未定義の場合でもエラーを出さずに空値の確認ができます。
- [isset]とは異なり、**PHP5.5以降**では関数の返り値や値リテラルを直接渡すことが出来ます。但し、PHP5.4以前では変数のみを引数として受け取ります。
- [isset]とは異なり、複数を同時にチェックすることは出来ません。
- 配列キーやオブジェクトプロパティに対しても使用することができ、その場合は再帰的に未定義チェックを行ってくれます。**一階層ずつチェックする必要がなくなります。**

# [is_null] の特徴

- コールバック関数として利用することが出来ます。

```php:\$arrayに要素として含まれるnullの個数を数える
$num_of_null = count(array_filter($array, 'is_null'));
```

# [array_key_exists], [property_exists] の利用

[isset]には少々汚いやり方をしないと未定義と`null`を区別できない問題がありましたが、配列やオブジェクトにおいてはそれを可能にする専用関数が用意されています。但し、引数の順序に注意してください。

```php:$arrayにキーkeyが定義されていれば
if (array_key_exists('key', $array)) { ... }
```

```php:$objectにプロパティpropertyが定義されていれば
if (property_exists($object, 'property')) { ... }
```

# 個人的用途まとめ

- **[$_GET, $_POSTなどを受け取る際の処理](http://qiita.com/mpyw/items/2f9955db1c02eeef43ea)**でも紹介している[filter_input]を用いれば、[isset]や[empty]のように存在を判定する言語構造が必要になるシーンはかなり減少する。
- 文字列が格納される可能性のある変数に関して[empty]を使用するのは控える。**`"0"`の扱いに問題がある**ためである。
- 文字列のオフセットに対して[isset]を用いる際はPHPバージョンに気を付ける。古いバージョンでは避けたほうが無難。