Como evitar isset () e empty ()

98

Eu tenho vários aplicativos mais antigos que lançam muitas mensagens "xyz is undefined" e "undefined offset" quando executados no nível de erro E_NOTICE, porque a existência de variáveis ​​não é explicitamente verificada usando isset()e consorts.

Estou pensando em trabalhar com eles para torná-los compatíveis com E_NOTICE, já que avisos sobre variáveis ​​ausentes ou deslocamentos podem ser salva-vidas, pode haver algumas pequenas melhorias de desempenho a serem obtidas e, em geral, é a maneira mais limpa.

No entanto, eu não gosto do que infligir centenas de isset() empty()e array_key_exists()s faz com o meu código. Ele fica inchado, fica menos legível, sem ganhar nada em termos de valor ou significado.

Como posso estruturar meu código sem um excesso de verificações de variáveis ​​e, ao mesmo tempo, ser compatível com E_NOTICE?

Pekka
fonte
6
Eu concordo completamente. É por isso que gosto tanto do Zend Framework, o módulo de solicitação é muito bom lá. Se estou trabalhando em algum aplicativo pequeno, normalmente codifico alguma classe de solicitação simples com os métodos mágicos __set e __get que funcionam de maneira semelhante à solicitação de ZF. Assim evito todas as ocorrências de isset e vazio no meu código. Dessa forma, tudo que você precisa usar é if (count ($ arr)> 0) nos arrays antes de iterar sobre eles e if (null! == $ variable) em alguns lugares críticos. Parece muito mais limpo.
Richard Knop

Respostas:

128

Para os interessados, expandi este tópico em um pequeno artigo, que fornece as informações abaixo em uma forma um pouco melhor estruturada: O guia definitivo para isset do PHP e vazio


IMHO você deve pensar não apenas em tornar o aplicativo "compatível com E_NOTICE", mas em reestruturar tudo. Ter centenas de pontos em seu código que tentam regularmente usar variáveis ​​inexistentes soa como um programa mal estruturado. Tentar acessar variáveis ​​inexistentes nunca deveria acontecer, outras linguagens se recusam a isso em tempo de compilação. O fato de o PHP permitir que você faça isso não significa que você deva.

Esses avisos existem para ajudá- lo, não para incomodá-lo. Se você receber um aviso "Você está tentando trabalhar com algo que não existe!" , sua reação deve ser "Ops, que pena, deixe-me corrigir isso o mais rápido possível." De que outra forma você vai dizer a diferença entre "variáveis ​​que funcionam muito bem indefinidas" e código honestamente errado que pode levar a erros graves ? Esta é também a razão pela qual você sempre, sempre , desenvolve com relatórios de erros voltados para 11 e continua plugando em seu código até que nenhumNOTICEé emitido. Desativar o relatório de erros é apenas para ambientes de produção, para evitar vazamento de informações e fornecer uma melhor experiência do usuário, mesmo em caso de código com erros.


Para elaborar:

Você sempre precisará issetou emptyem algum lugar em seu código, a única maneira de reduzir sua ocorrência é inicializar suas variáveis ​​corretamente. Dependendo da situação, existem diferentes maneiras de fazer isso:

Argumentos da função:

function foo ($bar, $baz = null) { ... }

Não há necessidade de verificar se $barou $bazestão definidos dentro da função porque você acabou de defini-los. Tudo o que você precisa se preocupar é se o valor deles é avaliado como trueou false(ou qualquer outra coisa).

Variáveis ​​regulares em qualquer lugar:

$foo = null;
$bar = $baz = 'default value';

Inicialize suas variáveis ​​no topo de um bloco de código no qual você irá usá-las. Isso resolve o !issetproblema, garante que suas variáveis ​​sempre tenham um valor padrão conhecido, dá ao leitor uma ideia de como o código a seguir funcionará e, portanto, também serve como uma espécie de autodocumentação.

Matrizes:

$defaults = array('foo' => false, 'bar' => true, 'baz' => 'default value');
$values = array_merge($defaults, $incoming_array);

Da mesma forma que acima, você está inicializando o array com os valores padrão e os substituindo pelos valores reais.

Nos casos restantes, digamos um modelo em que você está gerando valores que podem ou não ser definidos por um controlador, você apenas terá que verificar:

<table>
    <?php if (!empty($foo) && is_array($foo)) : ?>
        <?php foreach ($foo as $bar) : ?>
            <tr>...</tr>
        <?php endforeach; ?>
    <?php else : ?>
        <tr><td>No Foo!</td></tr>
    <?php endif; ?>
</table>

Se você está usando regularmente array_key_exists, deve avaliar para que está usando. A única vez que faz diferença é aqui:

$array = array('key' => null);
isset($array['key']); // false
array_key_exists('key', $array); // true

Porém, conforme declarado acima, se você estiver inicializando corretamente suas variáveis, não precisa verificar se a chave existe ou não, porque você sabe que existe. Se você está recebendo a matriz a partir de uma fonte externa, o valor mais provável não será null, mas '', 0,'0' , falseou algo parecido, valor ou seja, um você pode avaliar com issetou empty, dependendo da sua intenção. Se você definir regularmente uma chave de array para nulle quiser que signifique qualquer coisa menos false, ou seja, se no exemplo acima os resultados divergentes issete array_key_existsfizerem diferença para a lógica do seu programa, você deve se perguntar por quê. A mera existência de uma variável não deve ser importante, apenas seu valor deve ser relevante. Se a chave for um sinalizador true/ false, usetrueou falsenão null. A única exceção a isso seriam bibliotecas de terceiros que querem nullsignificar alguma coisa, mas como nullé tão difícil de detectar no PHP, ainda não encontrei nenhuma biblioteca que faça isso.

deceze
fonte
4
Verdade, mas a maioria das tentativas de acesso com falha segue as linhas de em if ($array["xyz"])vez de isset()ou array_key_exists()que considero legítimas, certamente não são problemas estruturais (corrija-me se estiver enganado). Adicionar array_key_exists()parece um desperdício terrível para mim.
Pekka
9
Não consigo pensar em nenhum caso em que usaria em array_key_existsvez de um simples isset($array['key'])ou !empty($array['key']). Claro, ambos adicionam 7 ou 8 caracteres ao seu código, mas eu dificilmente consideraria isso um problema. Também ajuda a esclarecer seu código: if (isset($array['key']))significa que essa variável é realmente opcional e pode estar ausente, enquanto if ($array['key'])significa apenas "se verdadeiro". Se você receber um aviso para o último, sabe que sua lógica está ferrada em algum lugar.
deceze
6
Acredito que a diferença entre isset () e array_key_exists () é que o último retornará verdadeiro se o valor for NULL. isset () não.
Htbaa
1
É verdade, mas não consegui pensar em um caso de uso lógico em que preciso diferenciar entre uma variável inexistente e uma chave definida cujo valor é nulo. Se o valor for avaliado como FALSO, a distinção deve ser sem diferença. :)
deceze
1
Chaves de array são certamente mais irritantes do que variáveis ​​indefinidas. Mas se você não tiver certeza se um array contém uma chave ou não, isso significa que você não definiu o array sozinho ou o está puxando de uma fonte que não controla. Nenhum cenário deve acontecer com muita frequência; e se isso acontecer, você tem todos os motivos para verificar se o array contém o que você acha que ele contém. É uma medida de segurança IMO.
kijin
37

Basta escrever uma função para isso. Algo como:

function get_string($array, $index, $default = null) {
    if (isset($array[$index]) && strlen($value = trim($array[$index])) > 0) {
        return get_magic_quotes_gpc() ? stripslashes($value) : $value;
    } else {
        return $default;
    }
}

que você pode usar como

$username = get_string($_POST, 'username');

Faça o mesmo para coisas triviais como get_number(), get_boolean(), get_array()e assim por diante.

BalusC
fonte
5
Isso parece bom e faz a verificação de magic_quotes também. Agradável!
Pekka
Excelente função! Muito obrigado por compartilhar.
Mike Moore
3
Observe que $ _POST ['algo'] pode retornar um array, por exemplo, entradas com <input name="something[]" />. Isso causaria um erro (já que o corte não pode ser aplicado a matrizes) usando o código acima, neste caso, deve-se usar is_stringe, possivelmente strval. Este não é simplesmente um caso em que se deve usar get_arrayuma vez que a entrada do usuário (malicioso) talvez qualquer coisa e o analisador de entrada do usuário nunca deve gerar erro de qualquer maneira.
Ciantic de
1
Eu uso o mesmo tipo de função, mas definida como tal: function get_value (& $ item, $ default = NULL) {return isset ($ item)? $ item: $ default; } A vantagem desta função é que você pode chamá-la com arrays, variáveis ​​e objetos. A desvantagem é que o $ item é inicializado (para nulo) posteriormente, se não foi.
Mat
Você deve desligar as aspas mágicas globalmente, em vez de lidar com elas em uma função. Existem muitas fontes na Internet explicando citações mágicas.
Kayla de
13

Eu acredito que uma das melhores maneiras de lidar com esse problema é acessando valores de matrizes GET e POST (COOKIE, SESSION, etc.) por meio de uma classe.

Crie uma classe para cada uma dessas matrizes e declare os métodos __gete __set( sobrecarga ). __getaceita um argumento que será o nome de um valor. Este método deve verificar este valor na matriz global correspondente, usando isset()ou empty()e retornar o valor se existir ounull (ou algum outro valor padrão) caso contrário.

Depois disso, você pode acessar com segurança os valores do array desta maneira: $POST->usernamee fazer qualquer validação, se necessário, sem usar isset()s ou empty()s. Se usernamenão existir na matriz global correspondente, nullserá retornado, portanto, nenhum aviso ou notificação será gerado.

Jamol
fonte
1
É uma ótima ideia e algo para o qual estou pronto para reestruturar o código. +1
Pekka
Infelizmente, você não será capaz de tornar essas instâncias superglobais a menos que as atribua a $ _GET ou $ _POST, o que seria muito feio. Mas você poderia usar classes estáticas, é claro ...
ThiefMaster
1
Você não pode usar getters e setters em "classes estáticas". e escrever uma classe por variável é uma prática ruim, pois implica em duplicação de código, o que é ruim. Não acho que essa solução seja a mais adequada.
Mat
Um membro público estático de uma classe atua como uma superglobal, isto é: HTTP :: $ POST-> nome de usuário, onde você instancia HTTP :: $ POST em algum ponto antes de seu uso, isto é. Classe HTTP {public static $ POST = array (); ...}; HTTP :: $ POST = new someClass ($ _ POST); ...
velcrow
6

Não me importo de usar a array_key_exists()função. Na verdade, eu prefiro usar essa função específica em vez de depender de funções de hack , que podem mudar seu comportamento no futuro, como emptyeisset (eliminado para evitar suscetibilidades ).


No entanto, uso uma função simples que é útil nisso e em algumas outras situações ao lidar com índices de array :

function Value($array, $key, $default = false)
{
    if (is_array($array) === true)
    {
        settype($key, 'array');

        foreach ($key as $value)
        {
            if (array_key_exists($value, $array) === false)
            {
                return $default;
            }

            $array = $array[$value];
        }

        return $array;
    }

    return $default;
}

Digamos que você tenha as seguintes matrizes:

$arr1 = array
(
    'xyz' => 'value'
);

$arr2 = array
(
    'x' => array
    (
        'y' => array
        (
            'z' => 'value',
        ),
    ),
);

Como você obtém o "valor" dos arrays? Simples:

Value($arr1, 'xyz', 'returns this if the index does not exist');
Value($arr2, array('x', 'y', 'z'), 'returns this if the index does not exist');

Já temos matrizes uni e multidimensionais cobertas, o que mais podemos fazer?


Pegue o seguinte trecho de código, por exemplo:

$url = '/programming/1960509';
$domain = parse_url($url);

if (is_array($domain) === true)
{
    if (array_key_exists('host', $domain) === true)
    {
        $domain = $domain['host'];
    }

    else
    {
        $domain = 'N/A';
    }
}
else
{
    $domain = 'N/A';
}

Muito chato, não é? Aqui está outra abordagem usando a Value()função:

$url = '/programming/1960509';
$domain = Value(parse_url($url), 'host', 'N/A');

Como exemplo adicional, use a RealIP()função para um teste:

$ip = Value($_SERVER, 'HTTP_CLIENT_IP', Value($_SERVER, 'HTTP_X_FORWARDED_FOR', Value($_SERVER, 'REMOTE_ADDR')));

Legal, hein? ;)

Alix Axel
fonte
6
"Contando com funções de hack que podem mudar seu comportamento no futuro" ?! Desculpe, mas essa é a coisa mais ridícula que ouvi durante toda a semana. Em primeiro lugar, issete emptysão construções de linguagem , não funções. Em segundo lugar, se quaisquer funções / construções de linguagem da biblioteca central mudarem seu comportamento, você pode ou não estar ferrado. E se array_key_existsmudar seu comportamento? A resposta é que não, contanto que você esteja usando conforme documentado. E issetestá documentado para ser usado exatamente assim. As funções de pior caso são preteridas em uma ou duas versões principais. A síndrome NIH é ruim!
deceze
Me desculpe deceze, mas antes de tudo hack está em itálico caso você não tenha notado. =) Em segundo lugar, você quer dizer que não se deve confiar em array_key_exists()para verificar se existe uma chave em um array ?! array_key_exists()foi criado exatamente para isso , prefiro confiar nele para esse fim do que isset()e principalmente empty()cuja descrição oficial é: "determinar se uma variável está vazia", ​​não menciona nada se realmente existe. Seu comentário e voto negativo é um dos mais ridículos que testemunhei durante todo o mês .
Alix Axel
3
Estou dizendo issete emptynão são nem mais nem menos confiáveis ​​do que array_key_existse podem fazer exatamente o mesmo trabalho. Seu segundo exemplo extenso pode ser escrito $domain = isset($domain['host']) ? $domain['host'] : 'N/A';apenas com os recursos básicos da linguagem, sem chamadas de funções extras ou declarações necessárias (observe que eu não defendo necessariamente o uso do operador ternário; o)). Para variáveis ​​escalares comuns, você ainda precisará usar issetou emptye poderá usá-los para matrizes exatamente da mesma maneira. "Confiabilidade" é um mau motivo para não fazê-lo.
dezembro
1
Você demonstrou seu ponto de vista, embora eu não concorde com a maioria das coisas que você disse. Eu acho que você errou nos 90% + casos, por exemplo eu uso o valor "0" em campos ocultos em formulários o tempo todo. Ainda assim, acredito que a solução que forneci não merece uma votação negativa e pode muito bem ser de alguma utilidade para Pekka.
Alix Axel
2
Embora @deceze tenha um ponto com as funções personalizadas - geralmente eu adoto a mesma postura - a abordagem value () parece interessante o suficiente para que eu a analise. Acho que a resposta e o acompanhamento permitirão a todos que toparem com isso mais tarde, decidirem por si próprios. +1.
Pekka
3

Eu estou aqui com você. Mas os designers de PHP cometeram erros muito mais graves do que isso. Além de definir uma função personalizada para qualquer leitura de valor, não há como contornar isso.

vava
fonte
1
isset () coisas. Tornar tudo nulo por padrão evitaria muitos problemas.
vava
2
E o que é isso 'tudo'? Pareceria um desperdício para o PHP ter que imaginar todos os nomes de variáveis ​​concebíveis e definir cada um como NULL apenas para que um desenvolvedor preguiçoso possa evitar digitar 5 caracteres.
Lotus Notes
5
@Byron, olha, é realmente simples, várias outras linguagens fazem isso, Ruby e Perl como alguns exemplos. VM sabe se a variável foi usada antes ou não, não é? Ele sempre pode retornar nulo em vez de falhar com ou sem mensagem de erro. E não se trata de apenas 5 caracteres, é sobre escrever params["width"] = params["width"] || 5para definir padrões em vez de toda aquela bobagem com isset()chamadas.
vava de
3
Desculpe por ressuscitar um tópico antigo. Dois dos piores erros do PHP foram register_globalse magic_quotes. Os problemas que isso fomentam tornam as variáveis ​​não inicializadas quase inofensivas em comparação.
staticsan
3

Eu uso essas funções

function load(&$var) { return isset($var) ? $var : null; }
function POST($var) { return isset($_POST[$var]) ? $_POST[$var] : null; }

Exemplos

$y = load($x); // null, no notice

// this attitude is both readable and comfortable
if($login=POST("login") and $pass=POST("pass")) { // really =, not ==
  // executes only if both login and pass were in POST
  // stored in $login and $pass variables
  $authorized = $login=="root" && md5($pass)=="f65b2a087755c68586568531ad8288b4";
}
Jan Turoň
fonte
2
Eu também uso isso, mas lembre-se de que, em alguns casos, suas variáveis ​​serão inicializadas automaticamente: por exemplo, load ($ array ['FOO']) criaria uma chave FOO em $ array.
Mat
2

Bem-vindo ao operador de coalescência nulo (PHP> = 7.0.1):

$field = $_GET['field'] ?? null;

PHP diz:

O operador de coalescência nulo (??) foi adicionado como açúcar sintático para o caso comum de precisar usar um ternário em conjunto com isset (). Retorna seu primeiro operando se existir e não for NULL; caso contrário, ele retorna seu segundo operando.

Alexandre Thebaldi
fonte
1

Faça uma função que retorna falsese não for definida e, se especificada, falsese estiver vazia. Se válido, ele retorna a variável. Você pode adicionar mais opções como visto no código abaixo:

<?php
function isset_globals($method, $name, $option = "") {
    if (isset($method[$name])) {    // Check if such a variable
        if ($option === "empty" && empty($method[$name])) { return false; } // Check if empty 
        if ($option === "stringLength" && strlen($method[$name])) { return strlen($method[$name]); }    // Check length of string -- used when checking length of textareas
        return ($method[$name]);
    } else { return false; }
}

if (!isset_globals("$_post", "input_name", "empty")) {
    echo "invalid";
} else {
    /* You are safe to access the variable without worrying about errors! */
    echo "you uploaded: " . $_POST["input_name"];
}
?>
fogo de dragão
fonte
0

O software não funciona magicamente pela graça de Deus. Se você está esperando algo que está faltando, é necessário lidar com isso da maneira adequada.

Se você ignorá-lo, provavelmente estará criando brechas de segurança em seus aplicativos. Em linguagens estáticas, acessar uma variável não definida simplesmente não é possível. Ele não irá simplesmente compilar ou travar seu aplicativo se ele for nulo.

Além disso, torna seu aplicativo impossível de manter e você vai ficar louco quando coisas inesperadas acontecerem. O rigor da linguagem é uma obrigação e o PHP, por design, está errado em muitos aspectos. Isso o tornará um péssimo programador se você não souber.

knoopx
fonte
Estou bem ciente das deficiências do PHP. Como indiquei na pergunta, estou falando sobre a revisão de projetos mais antigos.
Pekka
Acordado. Sendo um desenvolvedor PHP em tempo integral, é muito difícil para mim me aventurar em novas linguagens como Java, onde você precisa declarar tudo.
Dzhuneyt
0

Não tenho certeza de qual é a sua definição de legibilidade, mas o uso adequado dos blocos empty (), isset () e try / throw / catch é muito importante para todo o processo.

Se seu E_NOTICE está vindo de $ _GET ou $ _POST, então eles devem ser verificados em relação a empty () junto com todas as outras verificações de segurança que esses dados devem passar.

Se estiver vindo de feeds externos ou bibliotecas, deve ser agrupado em try / catch.

Se estiver vindo do banco de dados, $ db_num_rows () ou seu equivalente deve ser verificado.

Se vier de variáveis ​​internas, elas devem ser inicializadas corretamente. Freqüentemente, esses tipos de avisos vêm da atribuição de uma nova variável ao retorno de uma função que retorna FALSE em uma falha. Eles devem ser incluídos em um teste que, no caso de uma falha, pode atribuir à variável um valor padrão aceitável que o código pode manipular ou lançar uma exceção que o código pode manipular.

Essas coisas tornam o código mais longo, adicionam blocos extras e adicionam testes extras, mas eu discordo de você porque acho que definitivamente adicionam valor extra.

Mlutz
fonte
-2

Que tal usar a @operadora?

Por exemplo:

if(@$foo) { /* Do something */ }

Você pode dizer que isso é ruim porque você não tem controle do que acontece "dentro" de $ foo (se for uma chamada de função que contém um erro de PHP, por exemplo), mas se você usar essa técnica apenas para variáveis, isso é equivalente a:

if(isset($foo) && $foo) { /* ... */ }
Esteira
fonte
if(isset($foo))é o suficiente, na verdade. Ele retornará TRUEse a expressão for avaliada como TRUE.
Dzhuneyt
2
@ ColorWP.com também retornará verdadeiro se a expressão for avaliada como falsa.
Jon Hulka
Você só deve usar o parâmetro @ (para ignorar o aviso) em código que não está realmente em desenvolvimento, ou em código único ou uma correção rápida em projetos existentes, que você não deseja mostrar a mais ninguém. Mas é uma solução alternativa comum para um hack rápido.
rubo77