web-dev-qa-db-ja.com

反復可能なオブジェクトと配列型のヒント?

配列の型ヒントがあるか、is_array()を使用して変数の配列性をチェックする関数がたくさんあります。

今、私は反復可能なオブジェクトを使い始めています。 IteratorまたはIteratorAggregateを実装します。これらが型ヒントを通過する場合、またはis_array()を受ける場合、これらは配列として受け入れられますか?

コードを変更する必要がある場合、is_iterable()の一般的な種類がありますか、それとも次のようなことをする必要がありますか?

if ( is_array($var) OR $var instance_of Iterable OR $var instanceof IteratorAggregate ) { ... }

他にどんな反復可能なインターフェースがありますか?

54
user151841

Instanceof Iterator、PHPにはIterableインターフェースがありません。 Traversable)はありません インターフェースですが、IteratorIteratorAggregateはどちらもTraversableを拡張します(そして、AFAIKはそれらを拡張する唯一のものです)。

ただし、Traversableを実装するオブジェクトはis_array()チェックに合格せず、組み込みのis_iterable()関数もありません。あなたが使用できるチェックは

function is_iterable($var) {
    return (is_array($var) || $var instanceof Traversable);
}

明確にするために、allPHPオブジェクトはforeachで反復できますが、someそれらのうちTraversableを実装します。したがって、提示されたis_iterable関数は、foreachが処理できるすべてのものを検出するわけではありません。

75

PHP 7.1.0 導入されましたiterable疑似タイプおよびis_iterable()関数 、これはそのような目的のために特別に設計されています:

これは[…]新しいiterable疑似型を提案します。このタイプはcallableに類似しており、単一のタイプではなく複数のタイプを受け入れます。

iterableは、arrayまたはTraversableを実装するオブジェクトを受け入れます。これらのタイプはどちらもforeachを使用して反復可能であり、ジェネレーター内からyieldとともに使用できます。

_function foo(iterable $iterable) {
    foreach ($iterable as $value) {
        // ...
    }
}
_

この[…]は、ブール値を返す関数is_iterable()も追加します:true値が反復可能で、iterable疑似型falseその他の値。

_var_dump(is_iterable([1, 2, 3])); // bool(true)
var_dump(is_iterable(new ArrayIterator([1, 2, 3]))); // bool(true)
var_dump(is_iterable((function () { yield 1; })())); // bool(true)
var_dump(is_iterable(1)); // bool(false)
var_dump(is_iterable(new stdClass())); // bool(false)
_
27
Blackhole

StdClassのインスタンスはforeachループで機能するため、実際にはstdClassのチェックを追加する必要がありましたが、stdClassはTraversableを実装していません。

function is_iterable($var) {
    return (is_array($var) || $var instanceof Traversable || $var instanceof stdClass);
}
12
Isaac

「反復可能性」をテストするために、私はシンプルな(そしておそらく少しハックな)方法を使用しています。

function is_iterable($var) {
    set_error_handler(function ($errno, $errstr, $errfile, $errline, array $errcontext)
    {
        throw new \ErrorException($errstr, null, $errno, $errfile, $errline);
    });

    try {
        foreach ($var as $v) {
            break;
        }
    } catch (\ErrorException $e) {
        restore_error_handler();
        return false;
    }
    restore_error_handler();
    return true;
}

反復可能でない変数をループしようとすると、PHPは警告をスローします。反復する前にカスタムエラーハンドラーを設定することにより、エラーを例外に変換して、 try/catchブロック。その後、プログラムフローを中断しないように、以前のエラーハンドラーを復元します。

これは小さなテストケースです(PHP 5.3.15)でテスト済み):

class Foo {
    public $a = 'one';
    public $b = 'two';
}

$foo = new Foo();
$bar = array('d','e','f');
$baz = 'string';
$bazinga = 1;
$boo = new StdClass();    

var_dump(is_iterable($foo)); //boolean true
var_dump(is_iterable($bar)); //boolean true
var_dump(is_iterable($baz)); //boolean false
var_dump(is_iterable($bazinga)); //bolean false
var_dump(is_iterable($boo)); //bolean true
4
Tivie

残念ながら、これには型ヒントを使用できず、is_array($var) or $var instanceof ArrayAccessの処理を行う必要があります。これは既知の問題ですが、まだ解決されていません。少なくとも、私がテストしたPHP 5.3.2では動作しません。

1
Raoul Duke

反復可能なオブジェクトの使用に切り替える場合は、型ヒントを使用できます。

protected function doSomethingWithIterableObject(Iterator $iterableObject) {}

または

protected function doSomethingWithIterableObject(Traversable $iterableObject) {}

ただし、これを使用して、反復可能なオブジェクトと配列を同時に受け入れることはできません。本当にやりたい場合は、次のようなラッパー関数を作成してみてください。

// generic function (use name of original function) for old code
// (new code may call the appropriate function directly)
public function doSomethingIterable($iterable)
{
    if (is_array($iterable)) {
        return $this->doSomethingIterableWithArray($iterable);
    }
    if ($iterable instanceof Traversable) {
        return $this->doSomethingIterableWithObject($iterable);
    }
    return null;
}
public function doSomethingIterableWithArray(array $iterable)
{
    return $this->myIterableFunction($iterable);
}
public function doSomethingIterableWithObject(Iterator $iterable)
{
    return $this->myIterableFunction($iterable);
}
protected function myIterableFunction($iterable)
{
    // no type checking here
    $result = null;
    foreach ($iterable as $item)
    {
        // do stuff
    }
    return $result;
}
0
Jon Gilbert