A melhor maneira de testar a existência de uma variável em PHP; isset () está claramente quebrado

187

Dos isset()documentos :

isset() will return FALSE if testing a variable that has been set to NULL.

Basicamente, isset()não verifica se a variável está configurada, mas se está configurada para qualquer outra coisa NULL.

Dado isso, qual é a melhor maneira de realmente verificar a existência de uma variável? Eu tentei algo como:

if(isset($v) || @is_null($v))

( @é necessário evitar o aviso quando $vnão estiver definido), mas is_null()tem um problema semelhante ao isset(): retorna TRUEem variáveis não definidas ! Parece também que:

@($v === NULL)

funciona exatamente como @is_null($v), então também está fora.

Como devemos verificar com segurança a existência de uma variável no PHP?


Editar: existe claramente uma diferença no PHP entre variáveis ​​que não estão definidas e variáveis ​​definidas como NULL:

<?php
$a = array('b' => NULL);
var_dump($a);

O PHP mostra que $a['b']existe e tem um NULLvalor. Se você adicionar:

var_dump(isset($a['b']));
var_dump(isset($a['c']));

você pode ver a ambiguidade de que estou falando com a isset()função Aqui está a saída de todos os três var_dump()s:

array(1) {
  ["b"]=>
  NULL
}
bool(false)
bool(false)

Edição adicional: duas coisas.

Um, um caso de uso. Uma matriz sendo transformada nos dados de uma UPDATEinstrução SQL , em que as chaves da matriz são as colunas da tabela e os valores da matriz são os valores a serem aplicados a cada coluna. Qualquer uma das colunas da tabela pode conter um NULLvalor, significando a passagem de um NULLvalor na matriz. Você precisa de uma maneira de diferenciar entre uma chave de matriz não existente e o valor de uma matriz sendo definido como NULL; essa é a diferença entre não atualizar o valor da coluna e atualizar o valor da coluna para NULL.

Segundo, a resposta de Zoredache , array_key_exists()funciona corretamente, para o meu caso de uso acima e para quaisquer variáveis ​​globais:

<?php
$a = NULL;
var_dump(array_key_exists('a', $GLOBALS));
var_dump(array_key_exists('b', $GLOBALS));

saídas:

bool(true)
bool(false)

Como isso lida corretamente em praticamente todos os lugares, posso ver que existe alguma ambiguidade entre variáveis ​​que não existem e variáveis ​​definidas NULL, estou chamando array_key_exists()a maneira mais fácil oficial do PHP para realmente verificar a existência de uma variável .

(Apenas outro caso em que posso pensar é sobre propriedades de classe, para as quais há property_exists(), que, de acordo com seus documentos , funciona de maneira semelhante, array_key_exists()pois distingue corretamente entre não ser definido e definido como NULL).

chazomaticus
fonte
Você não pode verificar - mas por que precisa?
686 php too much php
12
NULL tem um significado muito específico no PHP, e é um conceito totalmente separado se uma variável está configurada ou não.
chazomaticus 6/01/09
33
Existem razões para diferenciar entre nulo e inexistente. Por exemplo, você está construindo um objeto para representar uma linha em uma tabela de banco de dados. Para cada coluna da linha, você cria uma variável privada, acessível apenas pelo método getter do objeto. Suponha que um valor da coluna seja nulo. Agora, como esse método getter sabe se não existe essa coluna na tabela ou se esse objeto possui apenas um valor nulo? Felizmente, no meu caso, a variável privada é realmente uma entrada em uma matriz privada, para que eu possa usar array_key_exists, mas esse é um problema real.
Nathan Long
1
Foi removido de novas versões do PHP, sim. Infelizmente, isso não ocorre em todas as implantações do PHP. Além disso, parece um detalhe semântico inútil questionar se estamos falando sobre elementos ou variáveis ​​de um array. Independentemente de quais padrões você acha que o código deve aderir, é útil saber como solucionar uma inconsistência na linguagem PHP.
chazomaticus
2
@chazomaticus Mas variáveis ​​e elementos de array são coisas fundamentalmente diferentes ; Só porque você pode fazer as mesmas coisas com elas não significa que elas sejam ou devam ser 100% intercambiáveis. Não existe "inconsistência na linguagem PHP" aqui, apenas algo que você não gosta / entende. Quanto a register_globals, ainda estou lutando para pensar em uma situação em que até isso exigiria essa distinção, pois qualquer coisa registrada na solicitação HTTP sempre seria uma string, não null.
IMSoP

Respostas:

97

Se a variável que você está verificando estiver no escopo global, você poderá:

array_key_exists('v', $GLOBALS) 
Zoredache
fonte
3
Ah ha! Agora você está falando! Como você faria isso por, digamos, propriedades de classe?
chazomaticus 6/01/09
22
Como variação, se a verificação precisar trabalhar também para variáveis ​​de escopo local, on pode fazer um $defined_vars = get_defined_vars();e depois testar via array_key_exists('v', $defined_vars);.
Henrik Opel
1
Isso parece um pouco feio para mim, mas no caso em que você está realmente verificar um elemento da matriz, faz muito mais sentido: isset($foo[$bar])torna-searray_key_exists($bar, $foo)
Arild
property_existsparece promissor, exceto por isso:> A função property_exists () não pode detectar propriedades que são magicamente acessíveis usando o método mágico __get.
alexw
@alexw Variáveis ​​"criadas" via __get realmente não existem. __get é um código arbitrário usado como substituto para variáveis ​​inexistentes, que podem retornar o que quiser, independentemente de algum dado relevante ter sido armazenado.
Brilliand
46

Tentativa de fornecer uma visão geral das várias discussões e respostas:

Não existe uma resposta única para a pergunta que possa substituir todas as maneiras issetpossíveis. Alguns casos de uso são tratados por outras funções, enquanto outros não resistem ao escrutínio ou têm valor duvidoso além do código de golfe. Longe de ser "quebrado" ou "inconsistente", outros casos de uso demonstram por que isseta reação danull é ao comportamento lógico.

Casos de uso reais (com soluções)

1. Teclas de matriz

As matrizes podem ser tratadas como coleções de variáveis, com unsete issettratando-as como se fossem. No entanto, como eles podem ser iterados, contados etc., um valor ausente não é o mesmo que aquele cujo valor énull .

A resposta neste caso, é usar em array_key_exists()vez deisset() .

Como isso leva a matriz a verificar como argumento de função, o PHP ainda emitirá "avisos" se a própria matriz não existir. Em alguns casos, pode-se argumentar validamente que cada dimensão deveria ter sido inicializada primeiro, portanto o aviso está cumprindo sua função. Para outros casos, uma array_key_existsfunção "recursiva" , que verificava cada dimensão da matriz por sua vez, evitaria isso, mas seria basicamente a mesma que @array_key_exists. Também é um pouco tangencial para a manipulação de nullvalores.

2. Propriedades do objeto

Na teoria tradicional da "Programação Orientada a Objetos", encapsulamento e polimorfismo são propriedades-chave dos objetos; em uma implementação OOP baseado em classes como PHP do, as propriedades encapsulados são declarados como parte da definição de classe, e dado os níveis de acesso ( public, protected, ou private).

Entretanto, o PHP também permite adicionar propriedades dinamicamente a um objeto, como você usaria para uma matriz, e algumas pessoas usam objetos sem classe (tecnicamente, instâncias do built-in stdClass, que não possui métodos ou funcionalidade privada) de maneira semelhante. maneira de matrizes associativas. Isso leva a situações em que uma função pode querer saber se uma propriedade específica foi adicionada ao objeto fornecido.

Assim como as chaves de matriz, uma solução para verificar as propriedades do objeto está incluída na linguagem, chamada razoavelmenteproperty_exists .

Casos de uso não justificáveis, com discussão

3. register_globalse outra poluição do espaço para nome global

O register_globalsrecurso adicionou variáveis ​​ao escopo global cujos nomes foram determinados por aspectos da solicitação HTTP (parâmetros GET e POST e cookies). Isso pode levar a códigos inseguros e com bugs, e é por isso que foi desativado por padrão desde o PHP 4.2, lançado em agosto de 2000 e removido completamente no PHP 5.4, lançado em março de 2012 . No entanto, é possível que alguns sistemas ainda estejam em execução com esse recurso ativado ou emulado. Também é possível "poluir" o espaço para nome global de outras maneiras, usando a globalpalavra - chave ou $GLOBALSmatriz.

Em primeiro lugar, register_globalsé improvável que ela produza inesperadamente uma nullvariável, pois os valores GET, POST e cookie sempre serão cadeias de caracteres ( ''ainda retornando truede isset) e as variáveis ​​na sessão devem estar inteiramente sob o controle do programador.

Em segundo lugar, a poluição de uma variável com o valor nullé apenas um problema se isso sobrescrever alguma inicialização anterior. "Sobrescrever" uma variável não inicializada nullapenas seria problemático se o código em outro lugar estivesse distinguindo entre os dois estados; portanto, essa possibilidade é um argumento contra essa distinção.

4. get_defined_varsecompact

Algumas funções raramente usadas no PHP, como get_defined_varse compact, permitem tratar nomes de variáveis ​​como se fossem chaves em uma matriz. Para variáveis ​​globais, a matriz super global$GLOBALS permite acesso semelhante e é mais comum. Esses métodos de acesso terão um comportamento diferente se uma variável não estiver definida no escopo relevante.

Depois de decidir tratar um conjunto de variáveis ​​como uma matriz usando um desses mecanismos, você pode executar as mesmas operações que em qualquer matriz normal. Consequentemente, veja 1.

A funcionalidade que existia apenas para prever como essas funções estão prestes a se comportar (por exemplo, "haverá uma chave 'foo' na matriz retornada por get_defined_vars?") É supérflua, pois você pode simplesmente executar a função e descobrir sem efeitos negativos.

4a Variáveis ​​variáveis ​​( $$foo)

Embora não sejam exatamente as funções que transformam um conjunto de variáveis ​​em uma matriz associativa, a maioria dos casos usando "variáveis ​​variáveis" ("atribuir a uma variável nomeada com base nessa outra variável") pode e deve ser alterada para usar uma matriz associativa .

Um nome de variável, fundamentalmente, é o rótulo dado a um valor pelo programador; se você está determinando em tempo de execução, não é realmente um rótulo, mas uma chave em algum armazenamento de valor-chave. Mais praticamente, ao não usar uma matriz, você está perdendo a capacidade de contar, repetir, etc; também pode ser impossível ter uma variável "fora" do armazenamento de valores-chave, pois ela pode ser substituída por $$foo.

Uma vez alterado para usar uma matriz associativa, o código será passível de solução 1. O acesso indireto à propriedade do objeto (por exemplo $foo->$property_name) pode ser tratado com a solução 2.

5. isseté muito mais fácil digitar do quearray_key_exists

Não tenho certeza se isso é realmente relevante, mas sim, os nomes das funções do PHP podem ser bastante demorados e inconsistentes às vezes. Aparentemente, as versões pré-históricas do PHP usavam o comprimento de um nome de função como uma chave de hash, então Rasmus inventou deliberadamente nomes de funções como htmlspecialcharspara que eles tivessem um número incomum de caracteres ...

Ainda assim, pelo menos não estamos escrevendo Java, não é? ;)

6. Variáveis ​​não inicializadas têm um tipo

A página de manual sobre conceitos básicos de variável inclui esta declaração:

Variáveis ​​não inicializadas têm um valor padrão de seu tipo, dependendo do contexto em que são usadas

Não tenho certeza se existe alguma noção no Zend Engine de "tipo não inicializado mas conhecido" ou se isso está lendo muito na declaração.

O que está claro é que não faz diferença prática para o comportamento deles, pois os comportamentos descritos nessa página para variáveis ​​não inicializadas são idênticos ao comportamento de uma variável cujo valor é null. Para escolher um exemplo, ambos $ae $bneste código terminarão como o número inteiro 42:

unset($a);
$a += 42;

$b = null;
$b += 42;

(O primeiro alertará sobre uma variável não declarada, na tentativa de fazer com que você escreva um código melhor, mas não fará diferença na maneira como o código realmente é executado.)

99. Detectando se uma função foi executada

(Mantendo este último, pois é muito mais longo que os outros. Talvez eu o edite mais tarde ...)

Considere o seguinte código:

$test_value = 'hello';
foreach ( $list_of_things as $thing ) {
    if ( some_test($thing, $test_value) ) {
        $result = some_function($thing);
    }
}
if ( isset($result) ) {
    echo 'The test passed at least once!';
}

Se some_functionpuder retornar null, existe a possibilidade de que o echoalcance não seja alcançado mesmo que some_testretornado true. A intenção do programador era detectar quando $resultnunca havia sido definido, mas o PHP não permite que eles o façam.

No entanto, existem outros problemas com essa abordagem, que ficam claros se você adicionar um loop externo:

foreach ( $list_of_tests as $test_value ) {
    // something's missing here...
    foreach ( $list_of_things as $thing ) {
        if ( some_test($thing, $test_value) ) {
            $result = some_function($thing);
        }
    }
    if ( isset($result) ) {
        echo 'The test passed at least once!';
    }
}

Como $resultnunca é inicializado explicitamente, ele assumirá um valor quando o primeiro teste for aprovado, tornando impossível saber se os testes subsequentes foram aprovados ou não. Este é realmente um erro extremamente comum quando as variáveis ​​não são inicializadas corretamente.

Para corrigir isso, precisamos fazer algo na linha em que comentei que algo está faltando. A solução mais óbvia é definir $resultum "valor terminal" que some_functionnunca pode retornar; se for esse o caso null, o restante do código funcionará bem. Se não houver candidato natural para um valor terminal, pois some_functionpossui um tipo de retorno extremamente imprevisível (que provavelmente é um sinal ruim), então um valor booleano adicional, por exemplo $found, poderia ser usado.

Experimento de pensamento um: a very_nullconstante

Teoricamente, o PHP poderia fornecer uma constante especial - assim como null- para ser usada como um valor terminal aqui; presumivelmente, seria ilegal retornar isso de uma função, ou seria coagido a isso null, e provavelmente o mesmo se aplicaria a passá-lo como argumento de função. Isso tornaria esse caso muito específico um pouco mais simples, mas assim que você decidisse redefinir o código - por exemplo, para colocar o loop interno em uma função separada - ele se tornaria inútil. Se a constante pudesse ser transmitida entre funções, não some_functionseria possível garantir que não a retornaria, de modo que não seria mais útil como um valor terminal universal.

O argumento para detectar variáveis ​​não inicializadas, neste caso, se resume ao argumento para essa constante especial: se você substituir o comentário por unset($result)e tratá-lo de maneira diferente $result = null, estará apresentando um "valor" para o $resultqual não pode ser repassado e só pode ser detectado por funções internas específicas.

Experimento de pensamento dois: contador de tarefas

Outra maneira de pensar sobre o que a última ifpergunta é é "alguma coisa foi feita $result?" Em vez de considerá-lo um valor especial $result, você pode pensar nisso como "metadados" sobre a variável, um pouco como a "contaminação de variáveis" do Perl. Então ao invés de issetque você pode chamá-lo has_been_assigned_to, e ao invés de unset, reset_assignment_state.

Mas se sim, por que parar em um booleano? E se você quiser saber quantas vezes o teste passou; você pode simplesmente estender seus metadados para um número inteiro e ter get_assignment_counte reset_assignment_count...

Obviamente, a adição de um recurso desse tipo teria uma compensação na complexidade e no desempenho do idioma; portanto, ele precisaria ser pesado com relação à sua utilidade esperada. Como com uma very_nullconstante, seria útil apenas em circunstâncias muito estreitas e seria similarmente resistente à re-fatoração.

A pergunta esperançosamente óbvia é por que o mecanismo de tempo de execução PHP deve assumir antecipadamente que você deseja acompanhar essas coisas, em vez de deixá-lo explicitamente, usando o código normal.

IMSoP
fonte
Com relação às classes e propriedades, infelizmente property_exists () não funciona quando a propriedade é array, por exemplo: Classe {public $ property = array ()}. Lança um erro.
Andrew
1
@ Andrew Parece funcionar bem para mim: 3v4l.org/TnAY5 Gostaria de fornecer um exemplo completo?
IMSOP
Sim, parece funcionar bem, algo de errado estava com minha configuração. Desculpem o alarme falso :)
Andrew
20

Às vezes me perco um pouco tentando descobrir qual operação de comparação usar em uma determinada situação. isset()aplica-se apenas a valores não inicializados ou explicitamente nulos. Passar / atribuir nulo é uma ótima maneira de garantir que uma comparação lógica funcione conforme o esperado.

Ainda assim, é um pouco difícil pensar, então aqui está uma matriz simples que compara como diferentes valores serão avaliados por diferentes operações:

|           | ===null | is_null | isset | empty | if/else | ternary | count>0 |
| -----     | -----   | -----   | ----- | ----- | -----   | -----   | -----   |
| $a;       | true    | true    |       | true  |         |         |         |
| null      | true    | true    |       | true  |         |         |         |
| []        |         |         | true  | true  |         |         |         |
| 0         |         |         | true  | true  |         |         | true    |
| ""        |         |         | true  | true  |         |         | true    |
| 1         |         |         | true  |       | true    | true    | true    |
| -1        |         |         | true  |       | true    | true    | true    |
| " "       |         |         | true  |       | true    | true    | true    |
| "str"     |         |         | true  |       | true    | true    | true    |
| [0,1]     |         |         | true  |       | true    | true    | true    |
| new Class |         |         | true  |       | true    | true    | true    |

Para caber na mesa, comprimi um pouco os rótulos:

  • $a; refere-se a uma variável declarada mas não atribuída
  • tudo o mais na primeira coluna se refere a um valor atribuído, como:
    • $a = null;
    • $a = [];
    • $a = 0;
    • ...
  • as colunas se referem a operações de comparação, como:
    • $a === null
    • isset($a)
    • empty($a)
    • $a ? true : false
    • ...

Todos os resultados são booleanos, truesão impressos e falseomitidos.

Você pode executar os testes você mesmo, verifique esta lista:
https://gist.github.com/mfdj/8165967

Mark Fox
fonte
Talvez fora do âmbito desta questão, mas você pode querer adicionar "0"à mesa, para a completude e clareza da emptyoperação
Rik Schaaf
17

Você pode usar a construção de linguagem compacta para testar a existência de uma variável nula. Variáveis ​​que não existem não serão exibidas no resultado, enquanto valores nulos serão exibidos.

$x = null;
$y = 'y';

$r = compact('x', 'y', 'z');
print_r($r);

// Output:
// Array ( 
//  [x] => 
//  [y] => y 
// ) 

No caso do seu exemplo:

if (compact('v')) {
   // True if $v exists, even when null. 
   // False on var $v; without assignment and when $v does not exist.
}

Obviamente, para variáveis ​​no escopo global, você também pode usar array_key_exists ().

Btw, pessoalmente, eu evitaria situações como a praga, onde há uma diferença semântica entre uma variável que não existe e a variável com um valor nulo. PHP e a maioria das outras linguagens simplesmente não pensam que existe.

Matijs
fonte
3
PHP não, mas eu não diria que a maioria das outras linguagens não. Quase todo idioma que declara variáveis ​​gera um erro se uma variável não tiver sido declarada, mas você pode configurá-las para NULL. Semanticamente, NULLdeve significar "nenhum recurso", mas não definir uma variável é um erro do programador.
6266 M Miller
1
@ MMiller Claro, mas escrever código que segue um caminho no caso de "nenhum recurso" e um caminho diferente no caso de "erro do programador" é um absurdo. Se você deseja detectar variáveis ​​não declaradas durante a depuração, use uma ferramenta de análise estática, como se fosse encontrar erros potenciais em qualquer idioma.
IMSOP
@ MMiller, Legal, como você pensou nisso.
Pacerier 26/01
1
@ MMiller Mas não funciona como uma refutação, porque a declaração na resposta é explicitamente sobre "uma variável não existente" e seu contra-exemplo é sobre uma propriedade de objeto / chave de hash não existente . A distinção entre esses casos não é apenas incidental.
IMSOP
1
@ MMiller - na verdade esse é um exemplo melhor. Mesmo assim, depois de mais de 20 anos programando em linguagens estritas, as situações em que eu precisava de distinções undefinede nullsão tão raras que não sinto falta. IMHO, o principal uso paraundefined "erro de programador em uma linguagem não estrita". Em uma linguagem estrita, se eu precisar de um estado distinto client did not state a value, declaro um valor apropriado para a situação e testo-o. Na pior das hipóteses, é necessário adicionar uma variável de flag separada. Mas fazer isso raramente é melhor do que ter que SEMPRE lidar com DOIS estados diferentes sem valor !!
Página
15

Explicando NULL, pensando logicamente

Eu acho que a resposta óbvia para tudo isso é ... Não inicialize suas variáveis ​​como NULL, inicialize-as como algo relevante para o que elas pretendem se tornar.

Trate NULL corretamente

NULL deve ser tratado como "valor inexistente", que é o significado de NULL. A variável não pode ser classificada como existente no PHP porque não foi informado que tipo de entidade ela está tentando ser. Pode também não existir, então o PHP apenas diz "Tudo bem, não existe, porque não faz sentido, e NULL é a minha maneira de dizer isso".

Um argumento

Vamos discutir agora. "Mas NULL é como dizer 0 ou FALSO ou ''.

Errado, 0-FALSE- '' ainda são classificados como valores vazios, mas SÃO especificados como algum tipo de valor ou resposta pré-determinada a uma pergunta. FALSE é a resposta para sim ou não '', é a resposta para o título que alguém enviou e 0 é a resposta para quantidade ou tempo etc. Eles são definidos como algum tipo de resposta / resultado que os torna válidos como sendo definidos.

NULL é apenas uma resposta do que nunca, não nos diz sim ou não e não nos diz o tempo e não nos diz que uma string em branco foi enviada. Essa é a lógica básica para entender o NULL.

Resumo

Não se trata de criar funções malucas para solucionar o problema, apenas mudar a maneira como seu cérebro olha para NULL. Se for NULL, assuma que não está definido como nada. Se você estiver pré-definindo variáveis, pré-defina-as como 0, FALSE ou "", dependendo do tipo de uso que você pretende para elas.

Sinta-se livre para citar isso. Está fora do topo da minha cabeça lógica :)

greatbigmassive
fonte
5
Ótima resposta. Muitas vezes vejo pessoas reclamando sobre como odeiam esse ou aquele recurso de um idioma. Mas eles parecem estar assumindo que "se não fizer do meu jeito, está quebrado". Sim, existem más decisões de design. Mas também existem desenvolvedores muito próximos!
Curtisdf
23
Há uma enorme diferença entre variável não definida e variável === nulo. Um não existe, o outro tem valor nulo. Argumentos que null significa que nenhum valor simplesmente não é verdadeiro. Nulo É um valor do tipo nulo. É um valor perfeitamente válido e não há razão para o php tratá-lo como um valor inexistente, o que infelizmente faz. Seria bom, se variáveis ​​não existentes fossem nulas e todas as variáveis ​​existentes não fossem nulas, e a atribuição de nulas à variável a desativaria. Mas existem MUITAS situações, nas quais as funções retornam nulo como valor real. Então estamos ferrados, porque não há uma maneira sangrenta de testar isso.
enrey
2
Eu sei que "não devemos" verificar a existência de variáveis ​​no php, inferno, não há nenhuma maneira real de verificar isso. Não vou escrever código que dependa disso, porque não é possível em php. Essa é uma limitação do php. Existe claramente uma diferença entre variável não definida e nula, mas o php não oferece maneiras de distingui-las. No entanto, muitas meta-funcionalidades dependem disso internamente: a leitura de var inexistente produz aviso, isset($a['x'])informa falso se xfor nulo, mas será exibido em count($a).. compactfuncionará em todas as variáveis ​​definidas, inclusive nulls, e assim por diante.
enrey
3
Essa resposta é falha de uma maneira importante: na programação OO, nulo é a opção lógica para significar "nenhum objeto". Por exemplo, em circunstâncias excepcionais, quando uma função pode retornar um objeto ou nenhum objeto, nulo é a escolha óbvia. Tecnicamente, no PHP, falso ou qualquer outro valor considerado falso no contexto booleano pode ser usado, mas você está perdendo alguma pureza semântica. Portanto, nulo é um valor perfeitamente razoável para inicializar uma variável e que deve conter um objeto eventualmente, porque é relevante para o que se pretende tornar.
chazomaticus
3
Enquanto o PHP gerar erros para variáveis ​​indefinidas, mas não para nulo, haverá uma diferença. Se nulo e indefinido eram realmente o mesmo conceito, o PHP deveria assumir que variáveis ​​padrão não definidas / não declaradas são nulas e nunca lançam um erro, mas ninguém quer isso porque é um pesadelo no desenvolvimento. Nulo e indefinido podem não ser realmente diferentes no contexto da semântica de valores, mas são muito diferentes quando se trata de escrever código claro e depurável.
Chris Middleton
9

As propriedades do objeto podem ser verificadas quanto à existência por property_exists

Exemplo de um teste de unidade:

function testPropertiesExist()
{
    $sl =& $this->system_log;
    $props = array('log_id',
                   'type',
                   'message',
                   'username',
                   'ip_address',
                   'date_added');

    foreach($props as $prop) {
        $this->assertTrue(property_exists($sl, $prop),
                           "Property <{$prop}> exists");
    }
}

fonte
4

Como um complemento à discussão de greatbigmassive sobre o que NULL significa , considere o que "a existência de uma variável" realmente significa.

Em muitos idiomas, você deve declarar explicitamente todas as variáveis ​​antes de usá-las ; isso pode determinar seu tipo, mas o mais importante é declarar seu escopo . Uma variável "existe" em todo lugar em seu escopo, e em nenhum lugar fora dela - seja uma função inteira ou um único "bloco".

Dentro de seu escopo, uma variável atribui algum significado a um rótulo que você, o programador, escolheu. Fora do escopo, esse rótulo não faz sentido (se você usa o mesmo rótulo em um escopo diferente é basicamente irrelevante).

No PHP, as variáveis ​​não precisam ser declaradas - elas ganham vida assim que você precisar. Quando você escreve para uma variável pela primeira vez, o PHP aloca uma entrada na memória para essa variável. Se você lê uma variável que atualmente não possui uma entrada, o PHP considera essa variável como tendo o valor NULL.

No entanto, os detectores automáticos de qualidade de código geralmente avisam se você usar uma variável sem "inicializá-la" primeiro. Em primeiro lugar, isso ajuda a detectar erros de digitação, como atribuir $thingIdmas ler $thing_id; mas, em segundo lugar, obriga a considerar o escopo sobre o qual essa variável tem significado, exatamente como faria uma declaração.

Qualquer código que se preocupe se uma variável "existe" faz parte do escopo dessa variável - tenha ou não sido inicializado, você como programador atribuiu esse significado à etiqueta nesse ponto do código. Como você o está usando, ele deve, em certo sentido, "existir" e, se existir, deve ter um valor implícito; no PHP, esse valor implícito é null.

Devido à maneira como o PHP funciona, é possível escrever um código que trate o espaço de nomes das variáveis ​​existentes, não como um escopo de rótulos aos quais você deu significado, mas como algum tipo de armazenamento de valores-chave. Você pode, por exemplo, o código executado como este: $var = $_GET['var_name']; $$var = $_GET['var_value'];. Só porque você pode, não significa que é uma boa ideia.

Acontece que o PHP tem uma maneira muito melhor de representar armazenamentos de valores-chave, chamados matrizes associativas. E embora os valores de uma matriz possam ser tratados como variáveis, você também pode executar operações na matriz como um todo. Se você tiver uma matriz associativa, poderá testar se ela contém uma chave usando array_key_exists().

Você também pode usar objetos de maneira semelhante, configurando propriedades dinamicamente; nesse caso, você pode usar property_exists()exatamente da mesma maneira. Claro, se você definir uma classe, você pode declarar quais as propriedades que tem - você ainda pode escolher entre public, privatee protectedescopo.

Embora exista uma diferença técnica entre uma variável (em oposição a uma chave de matriz ou propriedade de um objeto) que não foi inicializada (ou explicitamente unset()) e uma cujo valor é nullqualquer código que considere essa diferença significativa está usando variáveis ​​de uma maneira que elas não devem ser usadas.

IMSoP
fonte
1
Muito bons pontos, embora não exatamente uma resposta para a pergunta.
chazomaticus
1
À pergunta explícita "Como devemos verificar com segurança a existência de uma variável no PHP?" minha resposta é "você não é, e aqui está o porquê". Tanto essa resposta quanto a grande massa também respondem à pergunta implícita "por que isset()se comporta dessa maneira?".
IMSoP 03/09
"Se você lê de uma variável que atualmente não possui uma entrada, o PHP considera essa variável como tendo o valor NULL." Isto é falso. Uma variável indefinida é simplesmente indefinida. Pode retornar nulo quando você tenta acessá-lo, mas isso é irrelevante.
Hugo Zink
@HugoZink Irrelevante para o quê? Qualquer teste que você fizer do valor de uma variável indefinida informará que o valor é null. Se esse valor existe antes que você o veja é uma questão para os filósofos, mas no que diz respeito a qualquer comportamento observável, o valor é consistente null.
IMSoP
3

issetverifica se a variável está definida e, se houver, se seu valor não é NULL. A última parte não está (na minha opinião) fora do escopo desta função. Não há solução decente para determinar se uma variável é NULL porque não está definida ou porque está explicitamente definida como NULL .

Aqui está uma solução possível:

$e1 = error_get_last();
$isNULL = is_null(@$x);
$e2 = error_get_last();
$isNOTSET = $e1 != $e2;
echo sprintf("isNOTSET: %d, isNULL: %d", $isNOTSET, $isNULL);

// Sample output:
// when $x is not set: isNOTSET: 1, isNULL: 1
// when $x = NULL:     isNOTSET: 0, isNULL: 1
// when $x = false:    isNOTSET: 0, isNULL: 0

Outra solução alternativa é investigar a saída de get_defined_vars():

$vars = get_defined_vars();
$isNOTSET = !array_key_exists("x", $vars);
$isNULL = $isNOTSET ? true : is_null($x);
echo sprintf("isNOTSET: %d, isNULL: %d", $isNOTSET, $isNULL);

// Sample output:
// when $x is not set: isNOTSET: 1, isNULL: 1
// when $x = NULL:     isNOTSET: 0, isNULL: 1
// when $x = false:    isNOTSET: 0, isNULL: 0
Salman A
fonte
2

Não concordo com seu raciocínio sobre NULL , e dizer que você precisa mudar sua mentalidade sobre NULL é estranho.

Eu acho que isset () não foi projetado corretamente, isset () deve informar se a variável foi definida e não deve se preocupar com o valor real da variável.

E se você estiver verificando valores retornados de um banco de dados e uma das colunas tiver um valor NULL, você ainda deseja saber se ele existe mesmo que o valor seja NULL ... não confie em isset () aqui.

Da mesma forma

$a = array ('test' => 1, 'hello' => NULL);

var_dump(isset($a['test']));   // TRUE
var_dump(isset($a['foo']));    // FALSE
var_dump(isset($a['hello']));  // FALSE

isset () deveria ter sido projetado para funcionar assim:

if(isset($var) && $var===NULL){....

Dessa forma, deixamos ao programador verificar os tipos e não o isset () para assumir que não existe, porque o valor é NULL - seu design é estúpido

Christof Coetzee
fonte
Seu exemplo não está verificando a existência de uma variável, mas de uma chave de matriz. Uma solução para isso existe, na forma de array_key_exists. Você nunca deve estar em uma situação em que não sabe em tempo de execução se existe uma variável real.
IMSoP 30/08/13
@chazomaticus Bem, você nunca deve estar em uma situação em que register_globals está ativado, então eu mantenho essa afirmação.
IMSoP
Ah, eu concordo. Ainda assim, nem todos podem controlar onde seu código é implantado. É útil ter informações para todas as situações, seja como as coisas "deveriam" ser ou não.
chazomaticus
@chazomaticus Se o seu problema for register_globals, sua resposta não será alterada isset(). O manual do PHP menciona "geralmente é uma boa prática de programação inicializar variáveis ​​primeiro", que resolve register_globalsem tempo de design, e não em tempo de execução. Há também uma entrada de FAQ que fornece uma unregister_globals()função para lidar com isso em tempo de execução.
IMSoP 03/09
2

Vou adicionar dois centavos rápidos nisso. Um dos motivos pelos quais esse problema é confuso é porque esse cenário parece retornar o mesmo resultado com o relatório de erros não totalmente cheio:

$a = null;
var_dump($a); // NULL
var_dump($b); // NULL

Você poderia supor a partir desse resultado que a diferença entre $a = nulle não definir $bnada é nada.

Erro de manivela relatando:

NULL

Notice: Undefined variable: b in xxx on line n
NULL

Nota: gerou um erro variável indefinido, mas o valor de saída de var_dumpainda é NULL.

Obviamente, o PHP tem uma capacidade interna de distinguir entre uma variável nula e uma variável indefinida. Parece-me que deve haver uma função integrada para verificar isso.

Eu acho que a resposta aceita é boa para a maior parte, mas se eu fosse implementá-la, escreveria um wrapper para ela. Como mencionado anteriormente nesta resposta , tenho que concordar que realmente não encontrei uma situação em que isso tenha sido um problema. Parece que quase sempre acabo em um cenário em que minhas variáveis ​​são definidas e definidas ou não (indefinidas, não definidas, nulas, em branco etc.). Não quer dizer que uma situação como essa não ocorra no futuro, mas como parece ser um problema único, não estou surpreso que os desenvolvedores do PHP não tenham se incomodado em colocar isso em prática.

Robbie Averill
fonte
O aviso sobre variáveis ​​indefinidas é uma dica para o programador de que eles fizeram algo errado no código. Depuração externa (para a qual existem ferramentas fora da linguagem), nunca deve haver a necessidade de um programa detectar esse estado, porque o programador sempre deve saber quais variáveis ​​estão declarando.
IMSoP
1

Se eu executar o seguinte:

echo '<?php echo $foo; ?>' | php

Eu recebo um erro:

PHP Notice:  Undefined variable: foo in /home/altern8/- on line 1

Se eu executar o seguinte:

echo '<?php if ( isset($foo) ) { echo $foo; } ?>' | php

Eu não entendi o erro.

Se eu tenho uma variável que deve ser definida, geralmente faço algo como o seguinte.

$foo = isset($foo) ? $foo : null;

ou

if ( ! isset($foo) ) $foo = null;

Dessa forma, mais tarde no script, eu posso usar com segurança $ foo e saber que "está definido" e que o padrão é nulo. Depois eu possoif ( is_null($foo) ) { /* ... */ } se precisar e tiver certeza de que a variável existe, mesmo que seja nula.

A documentação completa do isset lê um pouco mais do que o que foi inicialmente colado. Sim, ele retorna false para uma variável que foi definida anteriormente, mas agora é nula, mas também retorna false se uma variável ainda não foi definida (sempre) e para qualquer variável que foi marcada como não definida. Ele também observa que o byte NULL ("\ 0") não é considerado nulo e retornará verdadeiro.

Determine se uma variável está definida.

Se uma variável foi desabilitada com unset (), ela não será mais definida. isset () retornará FALSE se estiver testando uma variável que foi definida como NULL. Observe também que um byte NULL ("\ 0") não é equivalente à constante NULL do PHP.

Beau Simensen
fonte
Ele tirou os documentos desse link. Leia a primeira frase, segundo parágrafo da seção de descrição no link que você forneceu. É exatamente o que ele citou acima.
Zoredache
Isso não é uma prática ruim para scripts simples, mas em projetos complexos (por exemplo, OO grandes), torna-se inviável. Além disso, como eu disse acima, is_null () retorna TRUE para variáveis ​​que não estão definidas, então não há realmente nenhuma razão para fazer o que você está dizendo, exceto para evitar um aviso do PHP.
chazomaticus 6/01/09
1
Para projetos "OO grandes" bem projetados, por que isso seria um problema? Por que você usaria $ foo em um corpo de método que pode não ter sido definido antes de seu primeiro uso?
Beau Simensen
1

Tente usar

unset($v)

Parece que a única vez que uma variável não é definida é quando ela é especificamente desmarcada ($ v). Parece que o seu significado de 'existência' é diferente da definição do PHP. NULL certamente existe, é NULL.

Joe Phillips
fonte
Não sei bem o que você quer dizer. Se você possui uma matriz, com um elemento 'a', não precisa desmarcar () o elemento 'b' para que o elemento 'b' não exista no PHP, ele simplesmente não existe. O mesmo ocorre com, por exemplo, variáveis ​​globais, que você pode considerar elementos da matriz $ GLOBALS.
chazomaticus 6/01/09
1
Mas concordo que existe uma variável com um valor NULL.
chazomaticus 6/01/09
0

Devo dizer que em todos os meus anos de programação em PHP, nunca encontrei um problema ao isset()retornar false em uma variável nula. OTOH, eu encontrei problemas com isset()falhas em uma entrada de matriz nula - mas array_key_exists()funciona corretamente nesse caso.

Para alguma comparação, o Icon define explicitamente uma variável não utilizada como retornando, &nullpara que você use o teste is-null no Icon para também verificar se há uma variável não configurada. Isso facilita as coisas. Por outro lado, o Visual BASIC possui vários estados para uma variável que não possui um valor (Nulo, Vazio, Nada, ...), e você geralmente precisa verificar mais de um deles. Isso é conhecido por ser uma fonte de bugs.

staticsan
fonte
0

De acordo com o Manual do PHP para a função empty (), "Determine se uma variável é considerada vazia. Uma variável é considerada vazia SE NÃO EXISTIR ou se seu valor for FALSE. Empty () não gera um aviso se o variável não existe ". (Minha ênfase.) Isso significa que a função empty () deve se qualificar como "a melhor maneira de testar a existência de uma variável em PHP", de acordo com o título Pergunta.

No entanto, isso não é bom o suficiente, porque a função empty () pode ser enganada por uma variável que existe e está definida como NULL.

Estou interrompendo minha resposta anterior para apresentar algo melhor, porque é menos complicado do que minha resposta original (que segue esta interrupção, para comparação).

  function undef($dnc) //do not care what we receive
  { $inf=ob_get_contents();             //get the content of the buffer
    ob_end_clean();                     //stop buffering outputs, and empty the buffer
    if($inf>"")                         //if test associated with the call to this function had an output
    { if(false!==strpos($inf, "Undef"); //if the word "Undefined" was part of the output
        return true;                    //tested variable is undefined
    }
    return false;                       //tested variable is not undefined
  }

Duas linhas simples de código podem usar a função acima para revelar se uma variável é indefinida:

  ob_start();                           //pass all output messages (including errors) to a buffer
  if(undef($testvar===null))            //in this case the variable being tested is $testvar

Você pode seguir essas duas linhas com qualquer coisa apropriada, como este exemplo:

    echo("variable is undefined");
  else
    echo("variable exists, holding some value");

Eu queria colocar a chamada para ob_start () e ($ testvar === null) dentro da função e simplesmente passar a variável para a função, mas ela não funciona. Mesmo se você tentar usar "passar por referência" da variável para a função, a variável BECOMES será definida e, em seguida, a função nunca poderá detectar que ela havia sido indefinida anteriormente. O que é apresentado aqui é um compromisso entre o que eu queria fazer e o que realmente funciona.

O anterior implica que existe outra maneira de sempre evitar a execução da mensagem de erro "Variável indefinida". (A suposição aqui é: impedir que uma mensagem seja essa é o motivo pelo qual você deseja testar para ver se uma variável está indefinida.)

   function inst(&$v) { return; }  //receive any variable passed by reference; instantiates the undefined

Basta chamar essa função antes de fazer algo no seu $ testvar:

   inst($testvar);                //The function doesn't affect any value of any already-existing variable

O valor da variável instanciada recentemente é definido como nulo, é claro!

(Interrupção termina)

Então, depois de estudar e experimentar, aqui está algo garantido para funcionar:

 function myHndlr($en, $es, $ef, $el)
 { global $er;
   $er = (substr($es, 0, 18) == "Undefined variable");
   return;
 }

 $er = false;
 if(empty($testvar))
 { set_error_handler("myHndlr");
   ($testvar === null);
   restore_error_handler();
 }
 if($er)  // will be 1 (true) if the tested variable was not defined.
 { ; //do whatever you think is appropriate to the undefined variable
 }

A explicação: Uma variável $ er é inicializada com um valor padrão de "sem erro". Uma "função manipuladora" é definida. Se o $ testvar (a variável que queremos saber se é ou não indefinida) passa no teste preliminar de função empty (), então fazemos o teste mais completo. Chamamos a função set_error_handler () para usar a função de manipulador definida anteriormente. Em seguida, fazemos uma comparação simples de identidade envolvendo $ testvar, que, se indefinido, provocará um erro. A função do manipulador captura o erro e testa especificamente para verificar se o motivo do erro é o fato de a variável não estar definida. O resultado é colocado na variável de informações de erro $ er, que podemos testar posteriormente para fazer o que quisermos, como resultado de termos certeza de que $ testvar foi ou não definido. Como precisamos apenas da função manipuladora para esse propósito limitado, restauramos a função original de tratamento de erros. A função "myHndlr" precisa ser declarada apenas uma vez; o outro código pode ser copiado para qualquer lugar apropriado, para $ testvar ou qualquer outra variável que desejamos testar dessa maneira.

vernonner3voltazim
fonte
1
Se a intenção é evitar um aviso de que suas variáveis ​​não foram declaradas, a solução é corrigir o seu código para declará-las corretamente. Sua instfunção é basicamente como o @operador de supressão de erro: "Eu sei que estou fazendo algo errado aqui, mas só quero que essa mensagem desapareça, sem alterar a operação do meu código de forma alguma".
IMSOP
Os métodos de detecção, por outro lado, são engenhosos, mas ainda acredito firmemente que você nunca deve ter nenhum uso para eles além de ecoar as próprias mensagens de aviso que estão captando. (Você provavelmente deve esclarecer que a sua versão buffer de saída requer error_reporting alta set e display_errors para ser ligado.)
IMSOP
0

Eu acho que a única solução completa é relatar avisos com

error_reporting(E_ALL); // Enables E_NOTICE

Mas você terá que corrigir todos os avisos gerados por variáveis ​​indefinidas, constantes, chaves de matriz, propriedades de classe, entre outras. Depois de fazer isso, você não precisará se preocupar com a diferença entre variáveis ​​nulas e não declaradas, e a ambiguidade desaparece.

A ativação do relatório de aviso pode não ser uma boa alternativa em todas as situações, mas existem bons motivos para habilitá-lo:

Por que devo corrigir erros de E_NOTICE?

No meu caso, havia mais de um ano trabalhando em um projeto sem ele, mas era usado para ter cuidado ao declarar variáveis, por isso era rápido a transição.

mikl
fonte
0

A única maneira de saber se uma variável é definida no escopo atual ( $GLOBALSnão é confiável) é array_key_exists( 'var_name', get_defined_vars() ).

Kostas Podias
fonte
1
Acho que foi o que muitas outras pessoas disseram antes, ou estou errado?
Stephan Vierkant
-1

Prefiro usar não vazio como o melhor método para verificar a existência de uma variável que a) exista eb) não seja nula.

if (!empty($variable)) do_something();
Hal
fonte
2
empty()não verifica se a variável é nulo, ele verifica se é falso-y, por exemplo, não é um dos ""(a cadeia vazia), 0(0, quando um número inteiro), 0.0(0, quando um flutuador), "0"(0, quando uma cadeia), NULL, FALSE, array()(uma matriz vazia) e $var;(uma variável declarada, mas sem valor). Digamos que você tenha um campo de rádio obrigatório em um formulário com duas entradas com os valores 0e 1. Se você usa empty()a validação e o usuário seleciona essa opção 0, você inadvertidamente errará "o campo obrigatório não pode estar vazio". Veja o manual php.net/manual/en/function.empty.php
Halil Özgür