Argumento inválido fornecido para foreach ()

304

Muitas vezes acontece comigo lidar com dados que podem ser uma matriz ou uma variável nula e alimentar alguns foreachcom esses dados.

$values = get_values();

foreach ($values as $value){
  ...
}

Ao alimentar um foreach com dados que não são uma matriz, você recebe um aviso:

Aviso: argumento inválido fornecido para foreach () em [...]

Supondo que não seja possível refatorar a get_values()função para sempre retornar uma matriz (compatibilidade com versões anteriores, código-fonte não disponível, qualquer que seja o motivo), estou me perguntando qual é a maneira mais limpa e eficiente de evitar esses avisos:

  • Transmitindo $valuespara matriz
  • Inicializando $valuesna matriz
  • Envolvendo o foreachcom umif
  • Outro (por favor, sugira)
Roberto Aloi
fonte
É altamente possível que $valuesnão seja uma matriz.
Bhargav Nanekalva

Respostas:

509

Pessoalmente, acho que isso é o mais limpo - não tenho certeza se é o mais eficiente, mente!

if (is_array($values) || is_object($values))
{
    foreach ($values as $value)
    {
        ...
    }
}

A razão da minha preferência é que ela não aloca uma matriz vazia quando você não tem nada para começar.

Andy Shellam
fonte
4
Ou contagem de uso () para descobrir se a matriz não está vazia
Kemo
76
@ Kemo: count()não é confiável. Se você passar count()nulo, ele retornará 0. Se você passar um argumento não nulo e sem matriz, retornará 1. Portanto, é impossível usar count()para determinar se a variável é uma matriz quando a variável pode ser uma matriz vazia, ou uma matriz contendo 1 item.
Andy Shellam
12
Observe que alguns objetos são iteráveis, e esta resposta não explica isso.
21813 Brad Koch
32
Deveria ser if (is_array($values) || $values instanceof Traversable).
Bob Stein
3
É uma pena que ele não tenha dito que não era com certeza o mais eficiente: D
Gui Prá
116

Que tal este? muito mais limpo e tudo em linha única.

foreach ((array) $items as $item) {
 // ...
 }
Ajith R Nair
fonte
7
Esta é a única coisa que funcionou para mim. Por alguma razão, o PHP não acreditava que a matriz multidimensional que eu construí era na verdade uma matriz de matrizes.
297 Justin
1
Mesmo aqui, esta é uma correção muito boa para uma matriz que contém matrizes ou valores nulos. Basta adicionar um teste no loop foreach para continuar se os dados forem nulos.
Lizardx
Resolveu o meu problema. Obrigado!
Hitesh
1
Código brilhante, ignorado o if, else para valor de array e nenhum array usando $ _POST com a caixa de seleção!
Yann Chabot
2
NOTA: Embora tenha uma aparência bonita e resolva o aviso de exclusão inválida, esse método retornará um aviso de variável indefinida se a variável não estiver definida de forma alguma. Use isset()ou is_array()ou ambos, dependendo inteiramente do seu cenário, etc. #
James
42

Eu costumo usar uma construção semelhante a esta:

/**
 * Determine if a variable is iterable. i.e. can be used to loop over.
 *
 * @return bool
 */
function is_iterable($var)
{
    return $var !== null 
        && (is_array($var) 
            || $var instanceof Traversable 
            || $var instanceof Iterator 
            || $var instanceof IteratorAggregate
            );
}

$values = get_values();

if (is_iterable($values))
{
    foreach ($values as $value)
    {
        // do stuff...
    }
}

Observe que esta versão em particular não é testada, é digitada diretamente no SO a partir da memória.

Editar: adicionada verificação Traversable

Kris
fonte
3
Melhor resposta. Exceto que eu acho que você realmente deveria verificar se $var instanceof Traversable. Veja aqui . Porque, por exemplo, você pode buscar um SimpleXMLElement , mas não é uma instância do Iterator ou do IteratorAggregate.
Bob Stein
2
Você pode remover as outras duas classes, @Kris. Ambos estendem o Traversable agora e parecem ter nascido dessa maneira no 5.0.0. Embora eu esteja sentindo uma pequena dúvida sobre se a instância de sempre se aplica a extensões.
Bob Stein
1
@ BobStein-VisiBone: sim (exceto que são interfaces, não classes) No entanto; Coloquei o Traversable antes desses, nem o Iterator nem o IteratorAggregate precisariam ser verificados (dessa forma, eles não retardarão a execução). Deixei-os para manter a resposta o mais próxima possível da resposta original que dei e para mantê-la óbvia / legível.
Kris
2
Eu acho que seria justo adicionar is_object($var)re. php.net/manual/en/language.oop5.iterations.php #
Mark Fox
1
@ MarkFox: Fique à vontade, no entanto, intencionalmente deixei de fora; Nunca vi um uso que não fosse melhor implementado Iteratorou implementado IteratorAggregate, mas é claro que isso é apenas minha opinião e, portanto, é subjetivo (nunca uso campos públicos).
Kris
15

Por favor, não dependa da conversão como uma solução , mesmo que outros estejam sugerindo isso como uma opção válida para evitar um erro, isso pode causar outro.

Esteja ciente: se você espera que uma forma específica de matriz seja retornada, isso poderá falhar. Mais verificações são necessárias para isso.

Por exemplo moldando uma booleana para uma matriz (array)bool, se não resultar em uma matriz vazia, mas uma matriz com um elemento que contém o valor booleano como um int: [0=>0]ou [0=>1].

Eu escrevi um teste rápido para apresentar esse problema . (Aqui está um teste de backup , caso o primeiro URL de teste falhe.)

Incluem-se testes para: null, false, true, um class, um arraye undefined.


Sempre teste sua entrada antes de usá-la no foreach. Sugestões:

  1. Verificação rápida de tipo :$array = is_array($var) or is_object($var) ? $var : [] ;
  2. Digite matrizes de dicas nos métodos antes de usar um foreach e especificar tipos de retorno
  3. Envolvendo foreach dentro se
  4. Usando try{}catch(){} blocos
  5. Projetando código / teste adequados antes dos lançamentos de produção
  6. Para testar uma matriz contra a forma correta, você pode usar array_key_existsuma chave específica ou testar a profundidade de uma matriz (quando for uma!) .
  7. Sempre extraia seus métodos auxiliares para o espaço de nomes global de forma a reduzir o código duplicado
AARTT
fonte
8

Tente o seguinte:

//Force array
$dataArr = is_array($dataArr) ? $dataArr : array($dataArr);
foreach ($dataArr as $val) {
  echo $val;
}

;)

GigolNet Guigolachvili
fonte
1
Isso não vai funcionar bem com matrizes associativas .. O método is_array é em geral melhor ... e mais fácil ...
AO_
4
$values = get_values();

foreach ((array) $values as $value){
  ...
}

O problema é sempre nulo e a fundição é de fato a solução de limpeza.

boctulus
fonte
3

Antes de tudo, todas as variáveis ​​devem ser inicializadas. Sempre.
A transmissão não é uma opção.
se get_values ​​(); pode retornar variável de tipo diferente, esse valor deve ser verificado, é claro.

Seu senso comum
fonte
A transmissão é uma opção - se você inicializar uma matriz usando $array = (array)null;uma matriz vazia. É claro que é um desperdício de alocação de memória ;-)
Andy Shellam
2
+1: leia do ponto de vista sentimental, não me importo se a linguagem pode prescindir, as variáveis DEVEM ser declaradas e os resultados não confiáveis DEVEM ser verificados. É necessário manter os desenvolvedores sãos e os logs de erros curtos.
Kris
3

Extensão mais concisa do código de @ Kris

function secure_iterable($var)
{
    return is_iterable($var) ? $var : array();
}

foreach (secure_iterable($values) as $value)
{
     //do stuff...
}

especialmente para usar o código interno do modelo

<?php foreach (secure_iterable($values) as $value): ?>
    ...
<?php endforeach; ?>
HongKilDong
fonte
2
Você não quer dizer return is_iterable($var) ? $var : array($var);?
SQB 06/07/2015
3

Se você estiver usando o php7 e quiser lidar apenas com erros indefinidos, este é o IMHO mais limpo

$array = [1,2,3,4];
foreach ( $array ?? [] as $item ) {
  echo $item;
}
Edwin Rodríguez
fonte
2
foreach ($arr ? $arr : [] as $elem) {
    // Does something 
}

Isso não verifica se é uma matriz, mas ignora o loop se a variável for nula ou vazia.

T30
fonte
1

Não tenho certeza se esse é o caso, mas esse problema parece ocorrer várias vezes ao migrar sites wordpress ou sites dinâmicos em geral. Se esse for o caso, verifique se a hospedagem para a qual você está migrando usa a mesma versão PHP usada pelo site antigo.

Se você não está migrando seu site e este é apenas um problema que surgiu, tente atualizar para o PHP 5. Isso soluciona alguns desses problemas. Pode parecer uma solução boba, mas fez o truque para mim.

Erik
fonte
1

Ocorre um caso excepcional para este aviso se você definir a matriz como nula dentro do loop foreach

if (is_array($values))
{
    foreach ($values as $value)
    {
        $values = null;//WARNING!!!
    }
}
Farid Movsumov
fonte
1

Que tal esta solução:

$type = gettype($your_iteratable);
$types = array(
    'array',
    'object'
);

if (in_array($type, $types)) {
    // foreach code comes here
}
Julian
fonte
1

Argumento de aviso inválido fornecido para foreach()exibição de tweets. vá para /wp-content/plugins/display-tweets-php. Em seguida, insira esse código na linha número 591, ele funcionará perfeitamente.

if (is_array($tweets)) {
    foreach ($tweets as $tweet) 
    {
        ...
    }
}
Saad Khanani
fonte
Impressionante! Essa deve ser a solução aceita. no meu caso, eu adicionei o seguinte:if (is_array($_POST['auto'])){ // code }
Jodyshop 10/07/19
0

Parece também haver uma relação com o meio ambiente:

Eu tive esse erro "argumento inválido fornecido foreach ()" apenas no ambiente de desenvolvimento, mas não no prod (estou trabalhando no servidor, não no host local).

Apesar do erro, um var_dump indicou que a matriz estava bem lá (nos dois casos, app e dev).

A if (is_array($array))volta do foreach ($array as $subarray)resolveu o problema.

Desculpe, mas não posso explicar a causa, mas como demorei um pouco para encontrar uma solução, pensei em compartilhar isso melhor como uma observação.

araldh
fonte
0

Use a função is_array quando passar o array para o loop foreach.

if (is_array($your_variable)) {
  foreach ($your_variable as $item) {
   //your code
}
}
Super Model
fonte
0

Que tal definir uma matriz vazia como fallback se get_value()estiver vazia?
Não consigo pensar no caminho mais curto.

$values = get_values() ?: [];

foreach ($values as $value){
  ...
}
Quentin Veron
fonte
0

Vou usar uma combinação de empty, issete is_arraycomo

$array = ['dog', 'cat', 'lion'];

if (!empty($array) && isset($array) && is_array($array) {
    //loop
    foreach ($array as $values) {
        echo $values; 
    }
}
Rotimi
fonte
-3

eu faria o mesmo que Andy, mas usaria a função 'vazia'.

igual a:

if(empty($yourArray))
{echo"<p>There's nothing in the array.....</p>";}
else
{
foreach ($yourArray as $current_array_item)
  {
    //do something with the current array item here
  } 
}
as_bold_as_love
fonte
3
-1, se $yourArray = 1;ele tentar iterar, e você receberá o erro. empty()Não é um teste adequado.
Brad Koch
@BradKoch está absolutamente certo. is_array () é a única maneira confiável de testar se $ yourArray é uma matriz. Veja outras respostas para obter detalhes sobre por que is_array () não é suficiente - o foreach também pode manipular iteradores.
Cgeisel