isset()およびempty()を回避する方法


98

E_NOTICEエラーレベルで実行すると、多くの「xyz is undefined」および「undefined offset」メッセージをスローする古いアプリケーションがいくつかあります。変数の存在は、consortsを使用isset()して明示的にチェックされないためです。

変数やオフセットの欠落に関する通知はライフセーバーになる可能性があるため、それらを使用してE_NOTICE互換にすることを検討しています。いくつかのマイナーなパフォーマンスの向上が得られる可能性があり、全体的にクリーンな方法です。

しかし、私が負わ何百好きではないisset() empty()し、array_key_exists()私のコードにありませんね。価値や意味の点で何も得ることなく、肥大化し、読みにくくなります。

E_NOTICE互換でありながら、変数チェックを過剰に行わずにコードを構造化するにはどうすればよいですか?


6
完全に同意します。それが私がZend Frameworkをとても気に入っている理由です。リクエストモジュールはそこで非常に優れています。小さなアプリで作業している場合は、通常、ZFのリクエストと同様に機能するマジックメソッド__setおよび__getを使用していくつかのシンプルなリクエストクラスをコーディングします。これにより、コード内でissetおよびemptyが発生するのをすべて回避できます。そのように使用する必要があるのは、配列を反復する前に配列で(count($ arr)> 0)である場合と、いくつかの重要な場所で(null!== $ variable)である場合です。きれいに見えます。
Richard Knop

回答:


128

興味のある方のために、私はこのトピックを小さな記事に拡張しました。これは、以下の情報をやや構造化された形式で提供します:PHPのissetと空の決定的なガイド


私見では、アプリを「E_NOTICE互換」にするだけでなく、全体を再構築することを検討する必要があります。持つ何百ものあなたのコード内のポイントのことは定期的にかなりひどく構造化プログラムのように、存在しない変数のサウンドを使用しようということ。存在しない変数にアクセスしようとすることは決してありません、他の言語はコンパイル時にこれを心配します。PHPで許可されているからといって、そうするべきではありません。

これらの警告は、あなたを困らせるためではなく、あなたを助けるためにあります。「存在しないもので作業しようとしている!」という警告が表示された場合 、あなたの反応は「おっと、残念です。早急に修正しましょう。」「未定義でも問題なく機能する変数」深刻なエラーにつながる可能性のある正直に間違ったコードの違いを他にどのように伝えますか?これは、常に、常に、エラー報告を11にして開発を行い、1つにならなくなるまでコードを接続し続ける理由でもあります。NOTICE発行される。エラー報告をオフにするのは、情報漏えいを回避し、バグの多いコードに直面してもより良いユーザーエクスペリエンスを提供するために、本番環境のみを対象としています。


詳しく説明するには:

あなたは常に必要になりますissetemptyその発生を低減する唯一の方法は、適切にあなたの変数を初期化することで、あなたのコード内のどこかで。状況に応じて、さまざまな方法があります。

関数の引数:

function foo ($bar, $baz = null) { ... }

かどうかをチェックする必要はありません$barかは、$bazあなたがちょうどそれらを設定するための関数の内部で設定されているが、あなたが心配する必要はすべてがあればその値に評価され、trueまたはfalse(または任意の他)。

どこでも通常の変数:

$foo = null;
$bar = $baz = 'default value';

変数を使用するコードのブロックの先頭で変数を初期化します。これは!isset問題をし、変数が常に既知のデフォルト値を持つようにし、次のコードが何に作用するかを読者に知らせ、それによって一種の自己文書化の役割も果たします。

配列:

$defaults = array('foo' => false, 'bar' => true, 'baz' => 'default value');
$values = array_merge($defaults, $incoming_array);

上記と同じように、デフォルト値で配列を初期化し、実際の値で上書きします。

残りのケースでは、コントローラーによって設定される場合とされない場合がある値を出力するテンプレートを考えてみましょう。次のことを確認するだけです。

<table>
    <?php if (!empty($foo) && is_array($foo)) : ?>
        <?php foreach ($foo as $bar) : ?>
            <tr>...</tr>
        <?php endforeach; ?>
    <?php else : ?>
        <tr><td>No Foo!</td></tr>
    <?php endif; ?>
</table>

を定期的に使用array_key_existsしている場合は、何に使用するかを評価する必要があります。違いが出るのはここだけです:

$array = array('key' => null);
isset($array['key']); // false
array_key_exists('key', $array); // true

ただし、上記のように、変数を適切に初期化している場合は、キーが存在するかどうかを確認しているため、キーが存在するかどうかを確認する必要はありません。あなたは、外部ソースからの配列を取得している場合は、値が最も可能性が高いではないだろうnullけど''0'0'falseまたはそれのようなもの、すなわちA値あなたが評価することができissetたりempty、あなたの目的に応じて、。配列キーを定期的に設定し、nullそれ以外の意味を持たせたいfalse場合、つまり、上記の例でプログラムロジックの結果が異なり、プログラムロジックに違いがある場合、その理由を自問する必要があります。変数の単なる存在は重要ではなく、その値のみが重要です。キーがある場合issetarray_key_existstrue / falseフラグの、次を使用しますtrueまたはfalse、ではないnull。これに対する唯一の例外はnull、何かを意味するサードパーティのライブラリですがnull、PHPでは検出が非常に難しいため、これを実行するライブラリをまだ見つけていません。


4
確かに、しかし失敗するアクセス試行のほとんどは、のif ($array["xyz"])代わりに、isset()またはarray_key_exists()私がやや合法であると確信し、構造上の問題ではない(間違っている場合は修正してください)。追加するarray_key_exists()ことは私にとってひどい無駄のように見えます。
Pekka、

9
array_key_exists単純なisset($array['key'])またはの代わりに使用するケースは考えられません!empty($array['key'])。確かに、どちらもコードに7文字または8文字を追加しますが、それを問題と呼ぶことはほとんどありません。また、コードを明確にするのに役立ちます。if (isset($array['key']))つまり、この変数が実際にオプションであり、存在しない可能性があることを意味しますが、if ($array['key'])「真の場合」を意味します。後者の通知を受け取った場合、ロジックがどこかにねじ込まれていることがわかります。
deceze

6
isset()とarray_key_exists()の違いは、値がNULLの場合、後者はtrueを返すということです。isset()は行いません。
Htbaa 09

1
真実ですが、存在しない変数と値がnullであるセットキーを区別する必要のある、まともな使用例を考えることができませんでした。値がFALSEと評価される場合、違いはなく区別されます。:)
deceze

1
配列キーは、未定義の変数よりも確かに厄介です。あなたは、配列がキーかが含まれているかどうかわからない場合でも、それは意味のいずれか、あなた自身の配列定義していないか、あなたがコントロールしていないことをソースからそれを引っ張っています。どちらのシナリオも頻繁に発生するべきではありません。そして、それが起こった場合、あなたが考えていることを配列が含んでいるかどうかを確認するあらゆる理由があります。これはIMOのセキュリティ対策です。
kijin

37

そのための関数を書くだけです。何かのようなもの:

function get_string($array, $index, $default = null) {
    if (isset($array[$index]) && strlen($value = trim($array[$index])) > 0) {
        return get_magic_quotes_gpc() ? stripslashes($value) : $value;
    } else {
        return $default;
    }
}

あなたはそれを使うことができます

$username = get_string($_POST, 'username');

以下のような些細なもののため同じことを行いget_number()get_boolean()get_array()とのようにします。


5
これは見栄えがよく、magic_quotesチェックも行います。いいね!
Pekka、

素晴らしい機能!共有してくれてありがとう。
マイクムーア

3
$ _POST ['something']が配列を返す場合があることに注意してください<input name="something[]" />。上記のコードを使用するis_stringと、エラーが発生します(トリムを配列に適用できないため)strval。この場合、とを使用する必要があります。get_arrayユーザー入力(悪意のあるもの)はおそらく何でもあり、ユーザー入力パーサーは決してエラーをスローすることはないため、これは単にどちらかを使用する必要があるケースではありません。
Ciantic 2012

1
私は同じ種類の関数を使用していますが、そのように定義されています:function get_value(&$ item、$ default = NULL){return isset($ item)?$ item:$ default; この関数の利点は、配列、変数、オブジェクトを使用して呼び出すことができることです。欠点は、$ itemが初期化されていない場合、後で初期化される(nullになる)ことです。
マット

マジッククオートを1つの関数で処理するのではなく、グローバルにオフにする必要があります。魔法の引用を説明するインターネット上のソースはたくさんあります。
Kayla 14

13

この問題に対処する最良の方法の1つは、クラスを介してGETおよびPOST(COOKIE、SESSIONなど)配列の値にアクセスすることです。

これらの配列と宣言__getおよび__setメソッドのそれぞれにクラスを作成します(オーバーロード)。__get値の名前となる1つの引数を受け入れます。この方法は、いずれかを使用して、対応するグローバル配列内のこの値をチェックしなければならないisset()か、empty()それが存在する場合、値を返しますかnull、そうでない場合には(またはいくつかの他のデフォルト値)。

その後、この方法で自信を持って配列値にアクセスできます。また$POST->usernameisset()s を使用せずに、必要に応じて検証を実行できempty()ます。username対応するグローバル配列に存在しない場合はnullが返されるため、警告や通知は生成されません。


1
これは素晴らしいアイデアであり、コードを再構築する準備ができています。+1
ペッカ

残念ながら、これらのインスタンスを$ _GETまたは$ _POSTに割り当てない限り、これらのインスタンスをスーパーグローバルにすることはできません。もちろん、静的クラスを使用することもできます...
ThiefMaster

1
「静的クラス」ではゲッターとセッターを使用できません。変数ごとに1つのクラスを記述することは、コードの重複を意味するため、不適切です。この解決策が最も適切だとは思いません。
マット

クラスのパブリックスタティックメンバーはスーパーグローバルのように機能します。つまり、HTTP :: $ POST-> usernameです。HTTP:: $ POSTをインスタンス化してから、その使用前のある時点でインスタンス化します。クラスHTTP {public static $ POST = array(); ...}; HTTP :: $ POST = new someClass($ _ POST); ...
velcrow 2013

6

array_key_exists()関数を使用してもかまいません。実際に、私は使用して好むこの特定の機能をよりむしろに頼るハック将来的に自分の行動を変える可能性がある機能のようにemptyしてisset(避けるためにstrikedthrough 感受性を)。


ただし、ここでは便利な簡単な関数を使用します。また、配列インデックスを処理する他のいくつかの状況も使用します

function Value($array, $key, $default = false)
{
    if (is_array($array) === true)
    {
        settype($key, 'array');

        foreach ($key as $value)
        {
            if (array_key_exists($value, $array) === false)
            {
                return $default;
            }

            $array = $array[$value];
        }

        return $array;
    }

    return $default;
}

次の配列があるとします。

$arr1 = array
(
    'xyz' => 'value'
);

$arr2 = array
(
    'x' => array
    (
        'y' => array
        (
            'z' => 'value',
        ),
    ),
);

どのようにして配列から「値」を取得しますか?シンプル:

Value($arr1, 'xyz', 'returns this if the index does not exist');
Value($arr2, array('x', 'y', 'z'), 'returns this if the index does not exist');

すでに1次元および多次元の配列がカバーされていますが、他に何ができるでしょうか?


たとえば、次のコードを見てください。

$url = '/programming/1960509';
$domain = parse_url($url);

if (is_array($domain) === true)
{
    if (array_key_exists('host', $domain) === true)
    {
        $domain = $domain['host'];
    }

    else
    {
        $domain = 'N/A';
    }
}
else
{
    $domain = 'N/A';
}

かなり退屈じゃないですか。Value()関数を使用した別のアプローチを次に示します。

$url = '/programming/1960509';
$domain = Value(parse_url($url), 'host', 'N/A');

追加の例として、取るRealIP()機能をテストするために:

$ip = Value($_SERVER, 'HTTP_CLIENT_IP', Value($_SERVER, 'HTTP_X_FORWARDED_FOR', Value($_SERVER, 'REMOTE_ADDR')));

きちんとね?;)


6
「将来的に動作が変わる可能性のあるハッキング機能に依存している」?!申し訳ありませんが、それは私が一週間聞いた中で最も馬鹿げたことについてです。まず第一に、issetemptyされている言語構造、機能ではありません。あれば第二に、任意のコアライブラリ関数/言語構造は、その動作を変更するには、またはねじ込みしてもしなくてもよいです。array_key_exists動作が変わった場合はどうなりますか?答えは、ドキュメントに記載されているとおりに使用している限りはありません。そしてisset、そのように使用するために文書化されています。最悪の場合の機能は、1つまたは2つのメジャーリリースバージョンでは非推奨です。NIH症候群は悪いです!
deceze

申し訳ありませんが、気づかなかった場合のために、まず最初にハック斜体にしました。=)第二に、あなたは1つが頼るべきではないという意味array_key_exists()かどうかを確認するためにキーが存在する中で、アレイ?!そのためarray_key_exists()作成されたものであり、この目的のために私はむしろそれを利用しています。isset()特にempty()、「変数が空かどうかを判断する」という公式の説明は、実際に存在する場合は何も言及していません。あなたのコメントと反対票は、私が毎月見た中で最もばかげているものの1つです。
Alix Axel

3
私は言ってissetempty多かれ少なかれ信頼性array_key_existsがあり、まったく同じ仕事をすることができます。2番目の長い例は$domain = isset($domain['host']) ? $domain['host'] : 'N/A';、コア言語機能のみで記述でき、追加の関数呼び出しや宣言は必要ありません(私は必ずしも三項演算子の使用を推奨しているわけではないことに注意してください; o)))。通常のスカラー変数の場合も、issetまたはを使用する必要がありempty、配列に対してもまったく同じ方法で使用できます。「信頼性」はそうしない悪い理由です。
deceze

1
あなたの言ったことのほとんどに同意しませんが、あなたはあなたの主張をしました。90%以上の場合は間違いだと思います。たとえば、フォームの非表示フィールドに常に「0」の値を使用しています。それでも、私が提供したソリューションは反対票を投じる価値がなく、Pekkaにとっても役立つ思います。
Alix Axel

2
@decezeはカスタム関数にポイントを持っていますが、私は通常同じスタンスをとりますが、value()アプローチは、私がそれを検討するのに十分興味深く見えます。答えとフォローアップは、後でそれにつまずくすべての人が自分の決心をすることができると思います。+1。
Pekka、

3

君と一緒にいるよ。しかし、PHPデザイナーはそれよりもずっと悪い間違いを犯しています。値を読み取るためのカスタム関数を定義する以外に、それを回避する方法はありません。


1
isset()のもの。デフォルトですべてをnullにすることで、多くのトラブルを回避できます。
vava

2
そして、この「すべて」は何ですか?怠惰な開発者が5文字を入力するのを避けるために、PHPが考えられるすべての変数名を想像してそれぞれにNULLを設定する必要があるのは、無駄なことのように思われます。
ロータスノーツ

5
@バイロン、見て、それは本当に簡単です、他の多くの言語がそれを行います、RubyとPerlはいくつかの例です。VMは変数が以前に使用されたかどうかを知っていますか?エラーメッセージの有無にかかわらず失敗する代わりに、常にnullを返すことができます。そして、これはお粗末な5文字ではなくparams["width"] = params["width"] || 5isset()呼び出しで意味のないすべてのものではなく、デフォルトを設定するように書くことです。
10

3
古いスレッドを復活させて申し訳ありません。PHPの最悪のミスの2つはregister_globals、とmagic_quotesでした。これらの助成プログラムが初期化されていない変数を作る問題は、比較するとほとんど無害に見えます。
staticsan 2010年

3

これらの機能を使用します

function load(&$var) { return isset($var) ? $var : null; }
function POST($var) { return isset($_POST[$var]) ? $_POST[$var] : null; }

$y = load($x); // null, no notice

// this attitude is both readable and comfortable
if($login=POST("login") and $pass=POST("pass")) { // really =, not ==
  // executes only if both login and pass were in POST
  // stored in $login and $pass variables
  $authorized = $login=="root" && md5($pass)=="f65b2a087755c68586568531ad8288b4";
}

2
私もこれを使用していますが、場合によっては変数が自動的に初期化されることを覚えておいてください。たとえば、load($ array ['FOO'])は$ arrayにFOOキーを作成します。
マット

2

null合体演算子へようこそ(PHP> = 7.0.1):

$field = $_GET['field'] ?? null;

PHPさんのコメント:

null結合演算子(??)は、isset()と組み合わせて三項を使用する必要がある一般的なケースの構文糖として追加されました。存在していてNULLでない場合は、最初のオペランドを返します。それ以外の場合は、2番目のオペランドを返します。


1

false設定されていない場合、および指定されているfalse場合は空の場合に戻る関数を作成します。有効な場合は変数を返します。以下のコードに示すように、さらにオプションを追加できます。

<?php
function isset_globals($method, $name, $option = "") {
    if (isset($method[$name])) {    // Check if such a variable
        if ($option === "empty" && empty($method[$name])) { return false; } // Check if empty 
        if ($option === "stringLength" && strlen($method[$name])) { return strlen($method[$name]); }    // Check length of string -- used when checking length of textareas
        return ($method[$name]);
    } else { return false; }
}

if (!isset_globals("$_post", "input_name", "empty")) {
    echo "invalid";
} else {
    /* You are safe to access the variable without worrying about errors! */
    echo "you uploaded: " . $_POST["input_name"];
}
?>

0

ソフトウェアは神の恵みによって魔法のように実行されません。不足しているものを予期している場合は、適切に処理する必要があります。

これを無視すると、おそらくアプリケーションにセキュリティホールが生じます。静的言語では、未定義の変数にアクセスすることは不可能です。アプリケーションがnullの場合、アプリケーションを単にコンパイルまたはクラッシュすることはありません。

さらに、それはあなたのアプリケーションを保守不能にし、予期しないことが起こったときにあなたは怒るでしょう。言語の厳密さは必須であり、PHPは設計上、非常に多くの点で間違っています。あなたが気づいていない場合、それはあなたを悪いプログラマーにします。


PHPの不足をよく知っています。質問で指摘したように、古いプロジェクトのオーバーホールについて話している。
Pekka、

同意した。執筆期間の長いPHP開発者であるため、すべてを宣言する必要があるJavaのような新しい言語に挑戦するのは非常に困難です。
Dzhuneyt 2012

0

あなたの読みやすさの定義が何かはわかりませんが、empty()、isset()、try / throw / catchブロックの適切な使用は、プロセス全体にとって非常に重要です。

E_NOTICEが$ _GETまたは$ _POSTからのものである場合、それらは、データが通過する必要がある他のすべてのセキュリティチェックとともにempty()に対してチェックされる必要があります。

外部フィードまたはライブラリからのものである場合は、try / catchでラップする必要があります。

データベースからのものである場合は、$ db_num_rows()または同等のものを確認する必要があります。

内部変数からのものである場合は、適切に初期化する必要があります。多くの場合、これらのタイプの通知は、失敗時にFALSEを返す関数の戻り値に新しい変数を割り当てることから生じます。これらは、エラーが発生した場合に、コードが処理できる受け入れ可能なデフォルト値を変数に割り当てるか、コードが処理できる例外をスローできるテストにラップする必要があります。

これらにより、コードが長くなり、ブロックが追加され、テストが追加されますが、間違いなく追加の価値が追加されると思います。


-2

@演算子の使用についてはどうですか?

例えば:

if(@$foo) { /* Do something */ }

$ fooの「内部」で何が起こるかを制御できないため(たとえば、PHPエラーを含む関数呼び出しの場合)、これを悪いと言うかもしれませんが、この手法を変数にのみ使用する場合、これは次と同等です。

if(isset($foo) && $foo) { /* ... */ }

if(isset($foo))実際には十分です。TRUE式がに評価された場合に返されTRUEます。
Dzhuneyt 2012

2
@ ColorWP.com式がfalseと評価された場合もtrueを返します。
Jon Hulka

@パラメーター(通知を無視するため)は、他の人に見せたくない、実際には今後開発されていないコード、または既存のプロジェクトの1回限りのコードまたはクイックフィックスでのみ使用する必要があります。しかし、それは迅速なハックの一般的な回避策です。
rubo77 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.