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?
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.
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 toutarray
ou objet implémentantTraversable
. Ces deux types sont itérables en utilisantforeach
et peuvent être utilisés avecyield
à 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-typeiterable
,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)
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);
}
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
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.
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;
}