HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

count関数の仕様

PHPのcount関数ではまったらしい。まぁ、ちゃんと仕様を理解していない自分が悪いのだが‥

何にはまったかというと、ある関数Fは戻り値として「空でない配列」か「false」を返す。で、「count(関数Fの戻り値)」を判定条件に含むループがあるのだが、「count(false)」となるときにループ内の処理が実行されて、なんでだ!?ということに。
count関数は、配列以外を渡した場合、nullまたはCountableでなければ「1」を返すのですね。やれやれ。
一応、確認プログラムを作って動かしてみたメモ。

<?php

ini_set('display_errors', 'yes');

class C1 {
    private $c = 5;
    function count() {
        return $this->c;
    }
}
class C2 implements Countable {
    private $c = 5;
    function count() {
        return $this->c;
    }
}

$data = array();
$data['empty string']       = '';
$data['non-empty string']   = 'hoge';
$data['string \'0\'']       = '0';
$data['empty array']        = array();
$data['one elem array']     = array(1);
$data['two elems array']    = array(1, 2);
$data['array in array']     = array(array());
$data['array in array']     = array(array(3));
$data['bool true']          = true;
$data['bool false']         = false;
$data['int 0']              = 0;
$data['int 1']              = 1;
$data['null']               = null;
$data['C1']                 = new C1();
$data['C2']                 = new C2();

print('<pre>');
foreach ($data as $i => $d) {
    printf("| %-16s |", $i);

    ob_start();
    var_dump($d);
    $s = ob_get_contents();
    $s = preg_replace(
        array("/ *\n */", "/^ +/", "/  +/", "/ +$/"),
        array(" ", "", " ", ""), $s);
    ob_end_clean();
    printf(" %-48s |", $s);

    printf(" %1s |", is_array($d));

    printf(" %1s |", ($d instanceof Countable));

    ob_start();
    var_dump(count($d));
    $s = str_replace("\n", " ", ob_get_contents());
    $s = preg_replace(array("/^ +/", "/  +/", "/ +$/"), array("", " ", ""), $s);
    ob_end_clean();
    printf(" %-6s |", $s);

    ob_start();
    var_dump(count($d, COUNT_RECURSIVE));
    $s = str_replace("\n", " ", ob_get_contents());
    $s = preg_replace(array("/^ +/", "/  +/", "/ +$/"), array("", " ", ""), $s);
    ob_end_clean();
    printf(" %-6s |", $s);

    print("\n");
}
print('</pre>');
?>

実行結果。ちょっと横幅があって見づらいけど‥

| empty string     | string(0) ""                                     |   |   | int(1) | int(1) |
| non-empty string | string(4) "hoge"                                 |   |   | int(1) | int(1) |
| string '0'       | string(1) "0"                                    |   |   | int(1) | int(1) |
| empty array      | array(0) { }                                     | 1 |   | int(0) | int(0) |
| one elem array   | array(1) { [0]=> int(1) }                        | 1 |   | int(1) | int(1) |
| two elems array  | array(2) { [0]=> int(1) [1]=> int(2) }           | 1 |   | int(2) | int(2) |
| array in array   | array(1) { [0]=> array(1) { [0]=> int(3) } }     | 1 |   | int(1) | int(2) |
| bool true        | bool(true)                                       |   |   | int(1) | int(1) |
| bool false       | bool(false)                                      |   |   | int(1) | int(1) |
| int 0            | int(0)                                           |   |   | int(1) | int(1) |
| int 1            | int(1)                                           |   |   | int(1) | int(1) |
| null             | NULL                                             |   |   | int(0) | int(0) |
| C1               | object(C1)#1 (1) { ["c":"C1":private]=> int(5) } |   |   | int(1) | int(1) |
| C2               | object(C2)#2 (1) { ["c":"C2":private]=> int(5) } |   | 1 | int(5) | int(5) |

COUNT_RECURSIVEオプションを指定するというのも一緒に試してみたが、配列の中の配列の中の‥という構造だと、末端の要素だけでなく、配列自体の個数もカウントに入っているらしい。