issetだらけのコードはやめてください。

## はじめに

メタップス(Metaps)アドベントカレンダーの第9日目の記事です。

アドベントカレンダーは、キリスト教の行事であるイエスの誕生を祝うものに由来していると言われています。イエスは人間の救いという良いメッセージを伝えに来たとされています。彼の誕生は西洋の歴史を変え、人類史に多くの議論と論争の火をつけました。

今回はphpから変数の使用法という良いメッセージをお届けしに来ました。今回のテーマは、phpで議論と論争を引き起こすかもしれません。(💦) 何卒、よろしくお願いします。

非常に多くのphpコードで、`isset`を乱用していると思います。どのケースで`isset`を使用すべきか、`isset`をなぜむやみに使用してはならないのか、その理由を理解しましょう。

`isset`が不適切に使用されたコードを見るたびに好ましくない感じがありました。この理由を明確にして`isset`を乱発するコードを最小化するためにこの記事を作成しました。

## 展開

### 良いコードは何か？

良いコードとは、解釈の幅を狭めることです。解釈できる場合の数を減らすことで、コードを迅速かつ明確に理解できます。

例えば、phpで`const CONSTANT_VALUE = '不変値'`とコードを使用すると、この値は固定されており、変更できないことが分かります。変数とは異なり、定数は最初に宣言された時点の値と異なる可能性がないため、定数の変更過程を追跡する必要はありません。定数を使用することは、値の変更可能性の判断を減らすことで、解釈の多様性を減らすことです。

迅速かつ明確に理解できるコードを作成するためには、プログラミング言語でサポートされている文法をよく理解し、できるだけ少ない様相を持つようにコードを作成する必要があります。


### isset関数

phpの`isset`関数は、対象の値が定義されているかまたは`null`なのかを判別する関数です。

値が定義されていない場合や`null`の場合、`isset(対象)`は`true`を返します。それとも`false`を返します。


### 定義されていない変数を確認する機能

phpで値が定義されているか未定義なのかを確認する手段は、`isset`、`??`、`??=`の機能があります。phpで多くの機能と関数が存在しますが、対象が定義された値なのか未定義なのかを確認する文法はこれら3つに制限されます。

そのため、これらの未定義なのかを確認する機能を、単に`null`を確認する用途で使うのではなく、値の定義・未定義を判別するために使うことにして、値が`null`なのかを判断する際には`is_null(対象)`や`対象===null`の文法を使うことをお勧めします。

このようなコーディングスタイルを持つと、`isset`を使用した場合は変数の定義を確認する用途として考えるようになり、`is_null`や`===null`を使用した場合は変数のタイプが`null`なのかを確認する用途として考えるようになり、未定義の値なのかを明確に分かるロジックを組むことができます。

### `??`, `??=`に関して説明

#### `??` : [Null coalescing operator](https://www.php.net/manual/en/language.operators.comparison.php#language.operators.comparison.coalesce)
`??` : `変数 = 対象 ?? デフォルト値`の文法は、`変数 = (fn()=>isset(対象) ? 対象 : デフォルト値)();`の文法と同じです。

#### `??=` : [Null coalescing assignment operator](https://www.php.net/manual/en/migration74.new-features.php#migration74.new-features.core.null-coalescing-assignment-operator)

`対象 ??= デフォルト値`の文法は、`if(!isset(対象)) { 対象 = デフォルト値; }`の文法と同じです。 

#### `isset`, `??`, `??=`は値の存在を確認できる特別な機能

変数が定義されている場合、`isset`は`null`か確認するので`is_null`を代わりに使用できますが、`??`, `??=`は縮約文法なので代わりに使える表現がありません。したがって、縮約文法は必ずしも定義されていないかどうかを確認する用途だけに使うことを強制するのではなく、必要に応じて値が `null`の場合にも使用しても良いと思います。しかし、phpで未定義の変数を確認する手段が、 `isset`, `??`, `??=`の3つに制限されているため、可能な限りこれらの文法は変数の値が定義されているときに使用される文法と区別されるように扱うことをお勧めします。


### 変数の定義と未定義の区別の重要性

未定義の変数は、phpで値が存在されていないものであり、ほとんどのphp関数や文法は値の存在を前提として動作するため、`isset`, `??`, `??=`以外のコードでエラーが発生する場合がほとんどです。

phpコードの多くの箇所で未定義の変数を使用するには、`isset`を使用して変数の存在を確認し、コードを構築する必要があります。

ロジックを作成する際、ロジックの流れとは関係のない文法的な要素が混ざるのは好ましくありません。ロジックを展開する前に、ロジックの流れに関係ない部分を事前に処理し、ロジックの流れだけを記述する方が良いです。

変数を事前に定義せずにコードを組むと、途中で`isset`が入って、きれいなコードが作られません。そのため、`isset`などの未定義の変数を確認するコードを使用する場合は、コードが以下のような順序で記述されることをお勧めします。

#### コード作成の手順

1. 変数を宣言する際、変数のデフォルト値を指定します。 変数の値が複数の条件によって変わる場合は、メソッドや関数などの戻り値を使用して変数の値を受け取るようにします。
2. 前のコードの実行で必ず伝達されるべき値の場合はデフォルト値を割り当てしません。単に割り当てられていないため未定義の変数が伝達される場合、 `isset`, `??`, `??=` の文法を使用して、各変数の役割に応じる未割り当ての値をデフォルト値として代入し、未割り当てが未定義の変数になることを防止します。(前のコードの実行で必ず生成され伝達されるべき値は、未定義になって、ロジックの実行過程でエラーを発生させることが妥当であるため、デフォルト値を割り当てません。これに関しては後の例のコードの必須・オプショナルメンバー部分をご参考ください。)
3. ロジックを展開するのに必要な変数がすべて集まった後、ロジックの流れを表すコードを作成します。

もし、ある流れのロジックの結果によって新しい変数が必要になり、その次のロジックで新しい変数を追加して使用しなければならない場合、次のような手法でコードを作成します。

#### コード作成の手順

1. 
    1. 変数を宣言する際、変数のデフォルト値を指定します。 変数の値が複数の条件によって変わる場合は、メソッドや関数などの戻り値を使用して変数の値を受け取るようにします。
    2. 前のコードの実行で必ず伝達されるべき値の場合はデフォルト値を割り当てしません。単に割り当てられていないため未定義の変数が伝達される場合、 `isset`, `??`, `??=` の文法を使用して、各変数の役割に応じる未割り当ての値をデフォルト値として代入し、未割り当てが未定義の変数になることを防止します。
    3. ロジックを展開するのに必要な変数がすべて集まった後、ロジックの流れを表すコードを作成します。
2. 
    1. 1段階のロジックの結果によって新しい変数が導入される場合、途中で変数を追加する。 できるだけこのプロセスで未定義の変数が生じないようにします。
    2. 2段階のロジックを展開するために必要な変数を追加した後、2段階ロジックの流れを表すコードを作成します。
3. 2段階でロジックが終わらずに新しい変数が追加される場合、3は2段階の繰り返します。
4. 3段階でロジックが終わらずに新しい変数が追加される場合、4は3段階の繰り返します。
5. ...

上記の作成単位は、一つのロジック単位を指し、ロジックの単位にA、B、C、Dが存在し、ロジックで使われる変数のコードにおいて「a, b, c, d」がある場合、「a b c d A B C D」の順序ではなく「a A b B c C d D」の順序の並びです。このようにロジックが多数に分かれる場合は、コードを書き続けるのではなく、関数やメソッドのスコープ内で変数を使用できるようにロジックを分離したり、クラスでロジックを分離する方法を考慮するのが良いです。

### データ伝達プロトコルに対する理解欠如

ここでデータ伝達プロトコルとは、あるコアロジックを展開する前に、必要な変数を事前に定義することを意味します。ある核心ロジックを展開するために必要な変数をあらかじめ用意しておいた変数の集合をプロトコルにして、これを核心ロジックに伝達することをデータ伝達とします。

`isset`でロジックを組むと、値の存在と非存在を確認しやすいとして`isset`を中心に使用する場合が多いです。メンテナンスする時、値の伝達プロトコルに対する理解をしなく`isset`を使用して値がある時に処理し、値がない時に処理しないことで、特に悩みなくコードを組むことができるから`isset`を使用します。

しかし、変数の値をチェックし、その後ロジックで使用するために値を割り当てるための用途ではなく、必要な値が伝わらないからエラーが発生し、`isset`でコードの実行を回避するためにコードを組むと、コードの実行過程で値の伝達が明確なプロトコルによって行われるのではなく、かってにコードが使用されることになり、よく定義されたロジックを構築するよりも、単に動作するコードを作成する可能性が高くなります。

単に動作するコードを作成すると、コードに複雑な条件が追加されるたびに、より流れとはかけ離れたロジックを作る可能性が高くなります。全体的な理解なしにコードを改善する経験が積まれるため、熊の膝をゴムハンマーで叩いた時、反対側の足や手が上がるGIFに「いいね」を押す可能性が高いです。

![Debugging We Bare Bears.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985423/adecc247-8c2d-1e0e-ce84-956c2ffbbc2a.gif)
<sup>:thumbsup:</sup>いいね
(出典: [ぼくらベアベアーズ(We Bare Bears)](https://ja.wikipedia.org/wiki/%E3%81%BC%E3%81%8F%E3%82%89%E3%83%99%E3%82%A2%E3%83%99%E3%82%A2%E3%83%BC%E3%82%BA)の一場面)

### 未定義の変数を使用する際にエラーを発生させる必要性

コードを作成する際、エラーを処理する方法の一つは、予期していなかった動作が発生した場合にエラーを発生させてエラーが発生したロジックを補完する方法です。

あらかじめ必要な変数の値をすべて集めてから、これらを使用してロジックを組むと、必要な変数が伝わらない場合にエラーが発生し、プログラマーはエラーを確認し、未定義の変数がなぜ使用されたのか、データの伝達プロトコルの形を考える過程を通じてコードの流れをより理解することができます。これにより、明確なデータ伝達の流れを理解し、整頓された、よく定義されたコードを作成する可能性を高めることができます。

特定の条件で変数が定義されないまま伝達される場合、どのような条件で変数が定義されていないかについての悩まなく、`isset`を使用して変数が定義されていない場合にはロジックを実行しない方法で処理すると、もはやエラーが発生しないため、データ伝達プロトコルに対する悩みの機会が失われますので、このような解決策はお勧めしません。

### 割り当てと未割り当ての区別

割り当てと未割り当てを区別するためにわざわざ変数を定義しない場合があるかもしれません。変数が定義されなければ割り当てられていない値であり、変数が定義されれば使用してもよい値になるからです。 しかし、このような割り当て・未割り当ての状態を表現するのにあえて `isset`を使用する必要はなく、代わりに使えるphp表現はかなり多いです。

また、値の存在有無で割り当てと未割り当てを区分する場合問題があります。php7からタイプヒントが導入され、ほとんどのモダンなphpのコードでは積極的にタイプヒントが利用されます。関数やメソッドに値を渡す際にタイプヒントを使用すると、その値はすべて定義された変数に渡されます。関数やメソッドスコープ内で値の割り当て・未割り当てを確認する際に、タイプヒントとして渡された値は割り当てられているかどうかは、既に定義されているため、`isset`を使用しないコードで作成するのができます。`isset`を未定義の変数を確認する用途に使おうとしたので、タイプヒントで伝達された値には`isset`を使用しないスタイルの状況で、スコープ内で新たに使用する変数は`isset`で変数の存在を確認する方式で割り当て・未割り当てを区別すると、一貫性が損なわれます。定義されてない値で割り当て・未割り当てを区分するコードと定義された値で値で割り当て・未割り当てを区分するコードが混ざるので、一貫性を保つためには、定義された変数で割り当て未割り当てを区分する方法を採用するのが良いと思います。

(24.01.29追加)
[未定義変数エラーのプロモーション](https://wiki.php.net/rfc/undefined_variable_error_promotion)というRFCを見ると、未定義の変数を使用する際にエラーを発生させるのは、コーディングのミスを減らす役割を果たすためだと説明しています。 コーディングのミスを減らすために未定義の変数を使用するとエラーが発生するようにすることは重要です。ただし、未定義の変数で割り当てと非割り当てを区分すると、次のようなミスが発生する可能性があります`if(isset($tset)) { echo $tset; }` `$test`と書くべきですが、`$tset`と間違って書きましたが、エラーを感知することができないため、条件文は実行されません。 `$test = null; if(is_null($tset)) { echo $tset; }`のように変数を定義して使用すると、`$tset`が未定義の変数であることを実行と同時に感知することができ、素早くコードを修正できます。

RFCの投票の「Using undefined variables is elegitimate coding style」の部分には誰も投票しませんでした。 これを通じて、phpの言語スペックを定義する人たちも定義せずに変数を使用することが適切なコーディングスタイルではないことに同意していることが分かります。
![undefined_variable_error_promotion__main_reason_for_voting_against_if_you_did.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985423/fba12a9d-8f97-e3ba-7345-5e54eabd2693.png)


#### 一つのタイプで割り当てと未割り当てを区別する

多くの場合、ロジックの展開過程で複数のタイプに対する処理よりも、1つのタイプに対する処理を行う機能が多いです。また、変数が持つタイプが多くなるほど、変数を使用する際にロジックの分岐が増えます。そのため、変数はできるだけ少ない種類のタイプを持つことが良いです。

1つのタイプだけを使用しても、割り当て・未割り当ての状態を表現できる場合があります。デフォルト値として空の値を利用する方法です。通常、文字列の場合は空の文字列`""`と空でない文字列で、数の場合は`0`と`0`でない数で、配列は空の配列`[]`と空でない配列`[1,2,3]`で、1つのタイプで空の値と空でない値を区分する方式を使用し、デフォルト値で空を割り当てる方式です。

#### nullタイプで未割り当て状態を区分する

もちろん、割り当てられた値が空の値なので、空の値であるかどうかだけでは割り当てと未割り当てを区別できない場合があります。この場合は、タイプを追加して`null`と一緒に使用します。また、オブジェクトの場合は空の値を定義するのが難しいため、空かどうかを示すために`null`タイプを追加して一緒に使用します。

#### 空の値とnull使用のケース

例えば、購入数量というのは、0なら購入していないもので、0でない数なら購入したものです。そのため、1つのタイプで割り当ての状態を区分することができます。しかし、価格は0であることもあるので、価格は1つのタイプで割り当てるかどうかを区分できない特性があります。 価格の場合、未割り当てを示すために数タイプ以外に`null`タイプを追加して使用します。

#### 割り当てられたnull

プログラミングは、データを処理し、人間との相互作用を可能にするために作られたものです。プログラミング言語はデータを受け取り、そのデータを変数として扱って加工処理を行います。

通常、データベースはデータを保存するためのツールとして使用されます。しかし、データベースにデータがない場合は`null`となり、この値をプログラミング言語で使用すると、変数に`null`が割り当てられることになります。プログラミングはデータを処理するものであり、`null`はデータを処理するためのものではないため、実際には割り当てても割り当てない場合と同様に扱われます。

そして、`null`は扱うためのデータとして作ることは一般的に考えられません。`null`の代わりに、特定の状態を示すために`boolean`型の変数を追加する方式でコードを作ります。デフォルトや未割り当ての状態を示すための用途ではない`null`を使用していると、コードが誤って組まれている可能性が高いため、注意が必要です。

したがって、時々`null`という値が割り当てられることがありますが。この場合、`null`の変数に再度`null`を割り当てることができるので、特に問題はありません。

#### 型安定なコード

ロジックを展開するために必要なすべての変数をあらかじめ定義した後は、その値にどのタイプの値が入っているかを確認できる機能は多いです。

is_​array, is_​bool, is_​double, is_​float, is_​int, is_​integer, is_​long, is_​null, is_​numeric, is_​object, is_​resource, is_​string, is_callable

これらのキーワードは、`null`タイプと併用して値が用意されているか否かを確認できるし、途中で変数のタイプを知ることができるため、タイプ安定的なコードを組むのに役立ちます。

#### 空の値またはnull使用

ある変数のデフォルト値を先に宣言したい場合は、各タイプの空の値または`null`タイプを使用したコードを作成しましょう。

### empty
- `empty`は `isset`, `??`, `=??` これ以外にも未定義の値を処理する際にエラーが発生しません。
- `empty(対象)` 関数は `(fn() => !isset(対象) || 対象 == false)()` コードと同じです。

#### emptyが未定義の変数を確認のに使用されない理由

`isset`, `？？`, `=??`は未定義や`null`の場合は`true`を返すため、`true`の場合変数が未定義かどうかを確認するためには`null`ではないかを確認することで、はっきりと定義か未定義かが分かります。 しかし、`empty`は未定義の場合だけでなく、`0`, `""`, `[]`, `0.0`, `"0"`, `null`, `false`などさまざまな値でないかをすべて確認してからこそ変数が未定義であることを確実に確認できます。そのため、変数の存在と非存在の確認としては一般的に使用されません。

各タイプの空の値が有効に割り当てられた値であるため、未割り当てを表すのに空の値を使用できない場合、未割り当ての状態を追加するために`null`タイプを追加します。 `null`ではなく、各タイプの空の値を使用するのは、未割り当てを示すのに、1つのタイプを使っても十分な場合に使用することで、未割り当ての様相を示すタイプの数を減らすことです。

`null`が割り当てられた変数にデフォルト値として各タイプの空の値を割り当て、割り当てと未割り当てを区分することは、`null`が未割り当てとして使用される可能性を減らしたもので、一つのタイプを作って曖昧さを減らします。しかし、`empty`の場合、通過できる値が多数あるため、未割り当ての状態を表す数を増やす場合があります。

例えば、変数で`0`という値を未割り当てとして使用していましたが、`empty`が`true`を返すため、デフォルト値として`null`を割り当てるケースを考えて見ましょう、未割り当てを空の値(`0`)と`null`の2つを考慮しなければならない曖昧なものにしてしまう可能性があります。それに対し、`isset`は定義されている場合は`null`なので`null`を再度割り当てるので、何の違いもありません。`null`なので空の値を割り当てることで、未割り当てタイプの数を減らすことができます。

#### emptyの使用をお勧めしない理由

`empty`は空の値を確認するために使用できますが、`empty`は値が存在する場合、各タイプの値をブール型に型変換して`false`になった時、`empty`は`true`を返します。　phpのタイプジャグリングによって文字列0(`"0"`)は、ブールに変換される場合、`false`となるので、`empty`は`"0"`を空とみなします。 文字列の空の値`""`だけを区別できません。数で構成された文字列を使用する場合は、ミスをする可能性があります。 これは他の動的言語とは異なり、phpの特殊な型変換によって発生するもので、`empty`をよく知らずに使用するとミスする可能性が高くなるので、空の値を確認する際は`=== 0`, `=== ""`, `=== []`を使用することをお勧めします。

#### emptyを使ったら？
`empty`を使用した場合、該当変数は単一タイプで使用され、未割り当てを空の値とする変数と仮定しても何の問題もないように組まなければなりません。 そうでない場合は、`empty`が持つ値がどの`falsy`の値かであるかを確認するために、コンテキスト把握およびデバッグを行う手間が伴います。

もしくは、複数のタイプを使用する場合、各タイプのデフォルト値を確認するのではなく、あるタイプのデフォルト値が使用されているかを`empty`で判断できます。例えば、変数が文字列と数値のタイプで定義されている場合、`$var === 0 | $var === ''`を使用することは明確ですが、コードが長くなるので、簡単にemptyを使うことがあります。フレームワークなどが利用者の利便性のために複数のタイプをサポートする場合ではなく、一般的な開発領域では`null`を使用するケースではなく、必ず複数のタイプを使用するケースはまれです。個人的には、古いコードやライブラリを使用しない限り、あまり使用していません。複数のタイプを使用する場合は、各プロジェクトや部署ごとにコーディングスタイル決めることをお勧めします。

### 例のコード

```php
class Conversation
{
    private readonly string $sayMessage;
    private readonly string $receiverName;

    public function setSayMessage(string $message): self
    {
        $this->sayMessage = $message;
        return $this;
    }

    public function withReceiverName(string $name): self
    {
        $this->receiverName = $name;
        return $this;
    }

    public function getConversation(): string
    {
        $this->fillUndefinedOptionalMember();
        $objectPhrase = $this->receiverName === "" ? "" : " to {$this->receiverName}";
        return "Jimmy said{$objectPhrase}, {$this->sayMessage}";
    }

    private function fillUndefinedOptionalMember(): void
    {
        $this->receiverName ??= "";
    }
}

echo (new Conversation)->setSayMessage("Nice to Meet You")->getConversation();
echo PHP_EOL;
echo (new Conversation)->withReceiverName("David")->setSayMessage("Nice to Meet You")->getConversation();
```
- `withReceiverName` メソッドと `setSayMessage` メソッドを使用して `undefined`のメンバーに値を定義しました。
- `withReceiverName` メソッドを使用しない場合 `fillUndefinedOptionalMember` メソッドによって空の文字列が `$receiverName`に割り当てられます。 したがって、`withReceiverName`はオプションのメソッドです。
- `setSayMessage` メソッドを使用していない場合, `fillUndefinedOptionalMember`によってデフォルト値が割り当てられないため `$sayMessage`メンバーを使用する際にエラーが発生すします。 これはわざとエラーを発生させたものであり、`setSayMessage`は必須メソッドです。
- `private string $receiverName = "";`で最初にメンバー変数を宣言する際にデフォルト値を割り当ててもよいですが、`readonly`を使用する場合は変数を一度だけ割り当てることができます。そのため、`setter`で値を受け取る場合はデフォルト値をメンバー宣言と同時に割り当てませんでした。
- 名前が伝わらない場合は空の文字列であるため、`$this->receiverName === ""`として、名前が割り当てられているかどうかを確認しました。
- 上記のプロセスは、`setter` メソッドを介して1に該当する「変数のデフォルト値指定」 → `fillUndefinedOptionalMember`メソッドを使用して、2に該当する「定義されていない場合、デフォルト値指定」 → `getConversation`のリターン値から3に該当する「ロジックを展開するのに必要な変数がすべて集まった後、ロジックの流れを表すコードを実行」の段階に分かれています。
- php8では、未定義の変数はほとんどのphp組み込み関数やタイプヒントでエラーを引き起こし、一部の構文ではWarningレベルのログが生成されます。Warningは、その構文を無視して以降のコードを実行しますが、問題が発生する際の処理を止めないので、未定義の変数処理時にWarningが発生するロジックを作らないようにすると良いでしょう。php9では、未定義の変数を使用すると必ずエラーが発生するため、コードの実行を確実に停止させることができます。
- エラーの原因をより明確にするために、未定義の変数が伝播した場合には、例外を用いて「Required Method Not Defined」などのメッセージを投げるコードを追加することも良いです。

### issetの曖昧さ

`isset()`の括弧内には、値へのアクセスによるエラーは無視されます。例えば、`$ten = 10; var_dump($ten[1]);`のようなコードを実行すると、`$ten[1]`が`isset`関数の引数でない場合、数に`[キー]`でアクセスした表現でエラーが発生ます。一方、`isset`関数の引数として`$ten = 100; var_dump(isset($ten[1]));`のように使用すると、値のアクセスに関するエラーではなく、単に`false`が返ります。このような`isset`の特性のため、`isset`内部に変な表現の値が入ってもエラーが発生せず、単に`false`を返すことがあります。

どの程度曖昧なのか考えてみましょう。 配列の特定の要素に`isset`を使用する場合、`isset(変数[キー])`の形になります。しかし、この形態は変数が存在しないか、変数が配列ではないためアクセスできないか、変数は存在するのに変数のキーに割り当てられた値が存在しないか、いずれの場合も単に `false` を返すため、区別が難しい曖昧な表現です。`true`の場合は値が存在するため`変数[キー]`は使用できる対象になりますが、`false`の場合は何が誤っているのかがこの表現だけでは分かりません。具体的な問題が分かるまでデバッグが必要です。また、配列の値が`null`であるためにも`false`を返される可能性も考えられます。先ほど`isset`を値の存在を確認する用途で使用することにしたので、この場合は排除して考えても`isset`は引数として受け取る値の表現式が少しでも複雑になると、何が間違って`false`を返したのか分かりにくいという問題があります。

`isset`のこのような曖昧さから、これを解決できる代替できるより明確な表現があれば、代わりにその機能を使用することをお勧めします。

#### 配列の曖昧さを解決
配列の場合、`isset`に代わる表現として、`array_key_exists`という関数があります。この関数の使用方法は、`array_key_exists(string|int $key, array $array): bool`です。この関数は、配列とキーを受け取り、そのキーが配列内に存在するかどうかを確認するものです。

最初の引数が文字列や整数でない場合はエラーが発生し、2番目の引数が配列でない場合もエラーが発生するため、エラーなしでこの関数を実行すると、配列にキーが存在するのか配列にキーが存在しないのか以外の様相がないので、何が問題なのか明確に分かります。

phpでは、配列が元素の値が定義されていない場合、そのキーも存在しない構造です。キーがなければ値がないのことで、キーがあれば値が何でもあることです。したがって、配列において値の存在ではなく、特定のキーが存在しないだけでも、「undefined」かどうかを区別できます。

従って、配列内の特定の要素の値の存在を確認する際には、`isset`ではなく、`array_key_exists`を使用することをお勧めします。

#### 多次元配列の場合

`array_key_exists`は配列中の一次元の元素に対してのみ適用できます。 多次元の配列の場合、`array_key_exists(n次元キー, 配列[1次元キー][2次元キー][3次元キー]···[n-1次元鍵]）`の形式になります。 `isset([1次元キー][2次元キー][3次元キー]···[n次元キー])` と異なり、`array_key_exists`は`n`次元のキーにアクセスする前に`n-1`次元キーにアクセスできないとエラーが発生します。`isset`はアクセスできないか、または`n`次元の配列値が存在しないか、`null`であれば`false`で、値が存在すると`true`となります。配列にアクセスできるかどうかは無視してもいいですし、最終的に値の存在だけ確認すればよい時は、アクセスする過程でのエラーをある程度無視できる`isset`を使用でできます。一方、アクセスできない場合を明確に確認したい場合は`array_key_exists`を使用します。

外部から伝達されるデータによって、どのような形式の配列が生成されるか分からない場合、特定の値の存在だけを確認したい場合は、アクセス過程でのエラーをある程度無視できる`isset`を使ってもよいと思います。ただし、アプリケーションでプログラマの定義によって生成される配列の場合は、誤ったアクセスであってもエラーを発生させない`isset`の代わりに、`array_key_exists`を使用することで、値の接近過程で何が間違っているのか、より分かりやすいから、もっと明確なコードを作成することができます。もし、`array_key_exists`ではなく`isset`を必ず使わなければならない場合は、形態を予測しにくい配列を作ることで、自分が書いたコードが生成する結果に対するコントロールが不足している可能性が高いため、コードを間違って組んでいるか悩んでみたほうがいいです。

多次元配列においても、`isset`よりは`array_key_exists`を使用することをお勧めします。 ただし、時にはプログラマーの統制以外の領域で生成される配列であるため、値の配列の構造と形態に対する確信が持てない比較的例外的な場合には`isset`を使用することもできます。

## 結論
#### isset
- `isset`を`null`を確認するのに使うのではなく、できる限り変数が定義されていないかどうかを確認すること中心に使うようにしましょう。
- `isset`を使用する際は、未定義の場合ロジックを実行しなく、定義の場合ロジックを実行する方式で使わなく、まだ定義されていないときに値を定義するために変数を確認する用途や、未定義が未割り当てを意味する場合、存在する値の未割り当て値を設定する用途で使用しましょう。
- このように`isset`を使うスタイルを採用すると、`isset`を使ったコードを読む時、`null`を確認で解釈する場合の数を減らしてより速く明確に理解できるコードを作ることができます。

#### 割り当てと未割り当て
- 割り当てと未割り当てを分ける際は、定義・未定義で区切るのではなく、空の値またはまたNull等デフォルト値を使いましょう。

#### 配列
- 検証対象が配列のように値にアクセスする形が少しでも複雑になったら、何が問題なのか明確に分かる`array_key_exists`ような機能を`isset`の代わりに使いましょう。