omoonのブログ

旅の思い出を写真とともに書いてみるか。

きょうのあさごはん

IMG_0353
いくら丼、湯葉とわかめの味噌汁。
ところで、みなさんは毎朝ちゃんと MOCO'S キッチン観てますか?観てない人は、今からでも遅くないので、しっかりと反省して、必ず観るようにしてください。
みんなちゃんとMOCO'Sキッチン観てるの?
息子が昨晩から発熱。回復が卒園行事に間に合いますように。

きょうのあさごはん

IMG_0258
シャルロット・オ・シードル(妻作)。
みなさま、今頃こんなことを言うのもどうかとは思うのですが、新年あけましておめでとうございます。
今年に入ってからは、色々と今までにない動きが出てきておりまして、なかなか刺激的な2ヶ月でした。
今の仕事をはじめて約 10 年。技術的にも体制的にも、そろそろ、次の 10 年の基盤をじっくりと見つめ直すタイミングなのかもしれないなあ、と、なんとなく思っております。
では、みなさま、今頃こんなことを言うのもどうかとは思うのですが、今年もよろしくお願いします。

isset() 関数にご用心! ( #phpadvent2011 Day 17 )

PHP Advent Calendar 2011 の 17 日目です。昨日はhajikamiさんの「http://my-rest.icca.jp/blog/2011/12/769/」でした。

みなさんこんにちは。17 日目担当の omoon です。普段は大阪で WEB アプリケーションを作ったりしている会社におりまして、PHP は触り始めて 10 年ぐらいです。

本当はもっと早く記事を上げる予定だったんですが、急遽下のような仕事が入りまして遅くなりました。ごめんなさい。

訳あってこうなっている

さて、みなさんは配列の要素の存在確認ってどうされてますか?

僕は、PHP を始めてからずっと無条件で isset() 関数 を使っていたのですが、ついこの間、えー、うそん!的なハマりを体験いたしましたので、もうご存知の方には面白くともなんともない話だとは思いつつ、恥を晒す覚悟でまとめてみたいと思います。

isset() 関数はその配列要素が存在しても値が null なら false

null でも false なんです!知らなかった。僕は単純に知らなかった。マニュアルの一番最初に書いてあるのに。。

isset ― 変数がセットされていること、そして NULL でないことを検査する

http://jp.php.net/manual/ja/function.isset.php

<?php
$noodles = array(
    'udon'   => 'white',
    'soba'   => 'gray',
    'pasta'  => 'yellow',
    'malony' => null,
);
isset($noodles['udon']); // true (知ってた)
isset($noodles['malony']); // false (えー、知らんかった!)
isset($noodles['somen']); // false (知ってた)

つまり、単純に配列要素の存在だけを確認するために、isset() を使っていると、要素自体は存在するのに false が返ってくることがあるということです。つい先日、これでバグ出しました。ごめんなさい。

ということで、配列要素の存在の確認には、array_key_exists() が良いみたい。

<?php
$noodles = array(
    'udon'   => 'white',
    'soba'   => 'gray',
    'pasta'  => 'yellow',
    'malony' => null,
);
array_key_exists('udon', $noodles); // true
array_key_exists('malony', $noodles); // true
array_key_exists('somen', $noodles); // false

お恥ずかしい限りですが、またひとつ賢くなりました。

配列の要素の要素などに isset() 関数を使うときの注意

で、ついでにもうひとつ。

配列の要素の存在確認に isset() を使う癖が付いているとこういう勘違いもしてしまいがち、という例をお話しします。そして、これでもバグを出しました。ごめんなさい。

配列の要素の要素の存在を調べたいと思ってやってしまったこんな例です。

<?php
$noodles = array(
    'udon'  => 'white',
    'soba'  => 'gray',
    'pasta' => 'yellow',
    'malony' => null,
);

isset($noodles['udon']['color']); // true (えー、うそん!)
echo $noodles['udon']['color']; // 'w' が出力される (なにそれ?)

これはさっきの話よりは少し複雑ですので、詳しく見ていくことにします。今から説明する2つのことが関係します。

(1) 文字列へは、[] を使って文字単位でアクセスできる

これ、意外と知られていないと言うか、最初知ったときは、何それー、と思うと思うんですが、つまり、

<?php
$name = "Santa Claus";
echo $name[0];  // S
echo $name[1];  // a
echo $name[2];  // n
echo $name[3];  // t
echo $name[4];  // a
echo $name[5];  // ' ' 
echo $name[6];  // C 
echo $name[7];  // l
echo $name[8];  // a
echo $name[9];  // u
echo $name[10]; // s

ということ。

ちなみにこんな風に書き換えることも可能。

<?php
$name = "Santa Claus";
$name[0] = 'P';
$name[3] = 'd';
echo $name; // Panda Claus(誰?)

まずこれが関係することの 1 つ目です。

(2) 型の相互変換

PHP: 型の相互変換 - Manual のページには、

PHP は、変数定義時に明示的な型定義を必要と(または、サポート) しません。ある変数の型は、その変数が使用される文により定義されます。 これは、ある文字列を変数 var に代入した場合には、 var は文字列になることを意味しています。 ある整数値を var に代入した場合には、 その変数は整数になります。

とあります。要は、時と場合によって文字列が勝手に数値になることがあるよ、ってことですね。

で、その時と場合のひとつが文字列変数に続く[] 内の文字列の場合で、

<?php
$name = "Santa Claus";
echo $name['firstName']; // これは'S'と出力される

となります。

つまり、文字列変数 $name に続く [] 内の文字列「firstName」が数値「0」に変換されるため、$name[0] と同義となり、$name の 1 文字目の「S」が出力されることになります。

でも、なぜ「0」なのか。

実はこの挙動、PHPマニュアルのこのページの「文字列の数値への変換」セクションにきちんと書いてあります。

数値として文字列が評価された時、結果の値と型は次のように定義されます。

文字列の中に '.' や 'e'、'E' といった文字が含まれず、 数値が integer 型の範囲内 (PHP_INT_MAX で定義されています) におさまる場合は integer として評価されます。それ以外の場合は、すべて float として評価されます。

文字列の最初の部分により値が決まります。文字列が、 有効な数値データから始まる場合、この値が使用されます。その他の場合、 値は 0 (ゼロ) となります。有効な数値データは符号(オプション)の後に、 1 つ以上の数字 (オプションとして小数点を 1 つ含む)、 オプションとして指数部が続きます。指数部は 'e' または 'E' の後に 1 つ以上の数字が続く形式です。

見落としがちですが、

文字列が、 有効な数値データから始まる場合、この値が使用されます。

とありますので確かめてみます。

<?php
$name = "Santa Claus";
echo $name['1stName']; // これは'a'と出力される

となるんですね。

ここでも文字列が数値に変換されるのは同じですが、今度は [] 内の文字列「1stName」が有効な数値データ「1」から始まっているため、$name[1] と同義となり、$name の 2 文字目「a」が出力されるというわけです。

これが、関係することの 2 つ目です。

で話をもどして、配列の要素の要素の存在確認は、、

さて、この 2 つのことを頭に入れて、先ほどの

<?php
$noodles = array(
    'udon'  => 'white',
    'soba'  => 'gray',
    'pasta' => 'yellow',
    'malony' => null,
);

isset($noodles['udon']['color']); // true (えー、うそん!)
echo $noodles['udon']['color']; // w (なにそれ?)

を順に説明してみると、

  • $noodles['udon'] は「white」という文字列
  • 文字列変数に続く [] 内の文字列「color」は数値「0」に変換される
  • $noodles['udon'][0] は、「white」の 1 文字目の 'w'
  • なので、isset() 関数での評価も true

となります。

ということで、配列の要素の要素の存在確認は、

<?php
$noodles = array(
    'udon'  => 'white',
    'soba'  => 'gray',
    'pasta' => 'yellow',
    'malony' => null,
);

if (is_array($noodles['udon'])) {
    array_key_exists('color', $noodles['udon']);
}

のように、

  • 配列であるかどうかを確認した上で
  • array_key_exists() を使って確認

するのが良いようです。

と、色々話してきましたが、実はこのあたりの内容については、PHPマニュアルにも幾つかコメントがついています。

困ったら PHP マニュアルに帰れ!(コメント欄も含めてね)、という素晴らしい定説を確認できたところで、僕の話しを終わりたいと思います。

みなさん、良いクリスマスを!

明日は、@DaiMotoh さんです。

きょうのあさごはん

R1081319.JPG
おでん。
週末にはじめておでんを作った。昆布とかつお出汁。塩だけで味をつけ、隠し味にオイスターソース。id:rinta6u さんのパクり。
味が濃くなりすぎないように気をつけて作ったら、味が薄すぎるとの評価。「この大根、味がない!」と。こどもは正直だ。
R1081323.JPG
「飛べ飛べ」と僕にけしかけられベッドから飛び降りたものの、見事に顔面から着地し顎を負傷した、とても不機嫌な息子。
色々ごめん。