web-dev-qa-db-fra.com

Objets itérables et indication de type de tableau?

J'ai beaucoup de fonctions qui ont soit une indication de type pour les tableaux, soit qui utilisent is_array() pour vérifier le tableau-ness d'une variable.

Maintenant, je commence à utiliser des objets qui sont itérables. Ils implémentent Iterator ou IteratorAggregate. Seront-ils acceptés comme des tableaux s'ils passent par une indication de type, ou subissent is_array()?

Si je dois modifier mon code, existe-t-il une sorte générique de is_iterable(), ou dois-je faire quelque chose comme:

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

Quelles autres interfaces itérables sont disponibles?

54
user151841

Je pense que vous voulez dire instanceof Iterator, PHP n'a pas d'interface Iterable. Il a une Traversable interface cependant. Iterator et IteratorAggregate étendent tous les deux Traversable (et AFAIK ils sont les seuls à le faire).

Mais non, les objets implémentant Traversable ne passeront pas la vérification is_array(), ni de fonction is_iterable() intégrée. Un chèque que vous pourriez utiliser est

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

Pour être clair, tous les objets php peuvent être itérés avec foreach, mais seulement certains d'entre eux implémentent Traversable. La fonction is_iterable Présentée ne détectera donc pas tout ce que foreach peut gérer.

75
NullUserException

PHP 7.1.0 a introduit le pseudo-type iterable et la fonction is_iterable() , spécialement conçu à cet effet:

Ce […] propose un nouveau pseudo-type iterable. Ce type est analogue à callable, acceptant plusieurs types au lieu d'un seul type.

iterable accepte tout array ou objet implémentant Traversable. Ces deux types sont itérables en utilisant foreach et peuvent être utilisés avec yield à partir d'un générateur.

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

Cette […] ajoute également une fonction is_iterable() qui renvoie un booléen: true si une valeur est itérable et sera acceptée par le pseudo-type iterable, false pour les autres valeurs.

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

J'ai en fait dû ajouter une vérification pour stdClass, car les instances de stdClass fonctionnent dans les boucles foreach, mais stdClass n'implémente pas Traversable:

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

J'utilise un moyen simple (et peut-être un peu hackish) pour tester "l'itérabilité".

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;
}

Lorsque vous essayez de boucler une variable non itérable, PHP lance un avertissement. En définissant un gestionnaire d'erreurs personnalisé avant la tentative d'itération, vous pouvez transformer une erreur en une exception vous permettant ainsi d'utiliser un bloc try/catch. Ensuite, vous restaurez le gestionnaire d'erreurs précédent pour ne pas perturber le déroulement du programme.

Voici un petit cas de test (testé en 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

Malheureusement, vous ne pourrez pas utiliser d'indices de type pour cela et devrez faire les choses is_array($var) or $var instanceof ArrayAccess. C'est un problème connu mais afaik il n'est toujours pas résolu. Au moins ça ne marche pas avec PHP 5.3.2 que je viens de tester.

1
Raoul Duke

Vous pouvez utiliser l'indicateur de type si vous passez à l'utilisation d'objets itérables.

protected function doSomethingWithIterableObject(Iterator $iterableObject) {}

ou

protected function doSomethingWithIterableObject(Traversable $iterableObject) {}

Cependant, cela ne peut pas être utilisé pour accepter des objets et des tableaux itérables en même temps. Si vous voulez vraiment le faire, essayez de construire une fonction wrapper quelque chose comme ceci:

// 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